@andares/pi-toolkits 0.6.0 → 0.7.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 +133 -129
- package/README.zh-CN.md +337 -0
- package/package.json +2 -1
- package/src/features/favorites/constants.ts +55 -5
- package/src/features/favorites/favorites.test.ts +24 -5
- package/src/features/favorites/index.ts +10 -5
- package/src/features/favorites/patch-selector.ts +7 -7
- package/src/index.ts +1 -1
package/README.md
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
<!-- markdownlint-disable MD033 -->
|
|
2
|
-
<!-- Inline <a name> anchors
|
|
2
|
+
<!-- Inline <a name> anchors keep the TOC targets stable and identical across README.md and README.zh-CN.md. -->
|
|
3
3
|
# ⚙️ @andares/pi-toolkits
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**English** · [中文](README.zh-CN.md)
|
|
6
|
+
|
|
6
7
|
> Integrated toolkit extension for [pi](https://github.com/earendil-works/pi-coding-agent) — one package, growing new capabilities over time.
|
|
7
8
|
|
|
8
9
|
[](https://www.npmjs.com/package/@andares/pi-toolkits)
|
|
@@ -11,101 +12,100 @@
|
|
|
11
12
|
[](https://github.com/earendil-works/pi)
|
|
12
13
|
[](https://github.com/andares/pi-toolkits)
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
Currently ships five ready-to-use capabilities — **ask mode**, **model favorites**, **prompt stash**, **third-party code review** and a **dedicated compaction model** — with more to come.
|
|
15
|
+
In a nutshell: six ready-to-use capabilities — **read-only consult mode** `ask`, **model favorites** `favorites`, **prompt stash** `stash`, **third-party code review** `third-review`, **dedicated compaction model** `compact-model` and **session startup automation** `autostart` — with more to come.
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
**Requirements**: pi `>=0.86.0 <2.0.0` (covering the four most recently released minor lines — 0.86.x / 0.87.x / 0.99.x / 1.0.x; 0.88–0.98 were never released). Every one of the 10 published versions inside that range was compared against the host `dist`: no differences in the extension APIs we use, and the two runtime patch targets used by favorites — `ModelSelectorComponent` / `AgentSession.cycleModel` — are byte-identical. `typecheck` plus the full test suite pass on each of 0.86.0, 0.86.1, 0.87.1, 0.99.2 and 1.0.2, and smoke-loading on real pi 1.0.2 produces no warnings. Note: pi ≥ 0.99 hosts require `@earendil-works/pi-tui` to be declared in `peerDependencies` as `"*"` (provided by the host); this package declares it as required.
|
|
18
18
|
|
|
19
19
|
---
|
|
20
20
|
|
|
21
|
-
## 📦
|
|
21
|
+
## 📦 Installation
|
|
22
22
|
|
|
23
23
|
<a name="install"></a>
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
|
-
#
|
|
26
|
+
# from npm (recommended)
|
|
27
27
|
pi install npm:@andares/pi-toolkits
|
|
28
28
|
|
|
29
|
-
#
|
|
29
|
+
# local development install
|
|
30
30
|
pi install .
|
|
31
31
|
|
|
32
|
-
#
|
|
32
|
+
# one-off session load, no install
|
|
33
33
|
pi -e ./src/index.ts
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
After installing, verify in any pi session:
|
|
37
37
|
|
|
38
|
-
- `/ask` →
|
|
39
|
-
- `/model`(
|
|
40
|
-
-
|
|
41
|
-
-
|
|
42
|
-
- `/compact-model
|
|
38
|
+
- `/ask` → a grey `ask` status appears in the footer, `write`/`edit` are hard-disabled
|
|
39
|
+
- `/model` (or `ctrl+l`) → a favorites hint line appears at the top of the selector
|
|
40
|
+
- press `ctrl+alt+y` in the input box → the current prompt is stashed and the input box is cleared
|
|
41
|
+
- after finishing a development task, type `/third-review` → switches to the review model and runs check + fix
|
|
42
|
+
- `/compact-model`, pick a cheap fast model → from then on both `/compact` and auto-compaction summarize with it (a grey `compact` status appears in the footer)
|
|
43
43
|
|
|
44
44
|
---
|
|
45
45
|
|
|
46
|
-
## 📚
|
|
46
|
+
## 📚 Contents
|
|
47
47
|
|
|
48
|
-
- [
|
|
49
|
-
- [
|
|
50
|
-
- [🛡️ ask —
|
|
51
|
-
- [⭐ favorites —
|
|
52
|
-
- [📥 stash —
|
|
53
|
-
- [🔎 review —
|
|
54
|
-
- [🗜️ compact —
|
|
55
|
-
- [🚀 autostart —
|
|
56
|
-
- [
|
|
57
|
-
- [
|
|
58
|
-
- [
|
|
48
|
+
- [Installation](#install)
|
|
49
|
+
- [Features at a glance](#features)
|
|
50
|
+
- [🛡️ ask — read-only consult mode](#ask)
|
|
51
|
+
- [⭐ favorites — model favorites](#favorites)
|
|
52
|
+
- [📥 stash — prompt stash](#stash)
|
|
53
|
+
- [🔎 review — third-party code review](#review)
|
|
54
|
+
- [🗜️ compact — dedicated compaction model](#compact)
|
|
55
|
+
- [🚀 autostart — session startup automation](#autostart)
|
|
56
|
+
- [Development](#development)
|
|
57
|
+
- [Contributing](#contributing)
|
|
58
|
+
- [License](#license)
|
|
59
59
|
|
|
60
60
|
---
|
|
61
61
|
|
|
62
|
-
## ✨
|
|
62
|
+
## ✨ Features at a glance
|
|
63
63
|
|
|
64
64
|
<a name="features"></a>
|
|
65
65
|
|
|
66
|
-
|
|
|
66
|
+
| Feature | Entry point | One-line description |
|
|
67
67
|
| --- | --- | --- |
|
|
68
|
-
| 🛡️ **ask**
|
|
69
|
-
| ⭐ **favorites**
|
|
70
|
-
| 📥 **stash**
|
|
71
|
-
| 🔎 **review**
|
|
72
|
-
| 🗜️ **compact**
|
|
73
|
-
| 🚀 **autostart**
|
|
68
|
+
| 🛡️ **ask** read-only consult mode | `/ask` | enter read-only Q&A mode: `write`/`edit` hard-disabled, `bash` runs in a read-only sandbox |
|
|
69
|
+
| ⭐ **favorites** model favorites | `ctrl+A` / `ctrl+J` inside `/model` | favorite the models you use most (marked in bold bright yellow), `ctrl+p` cycles only among favorites |
|
|
70
|
+
| 📥 **stash** prompt stash | `ctrl+alt+y` | a stash slot for one prompt: press to swap, press twice to stash and clear the input box |
|
|
71
|
+
| 🔎 **review** third-party code review | `/third-review` | summon the configured review model to check + fix the task just finished, focusing on "is this round's requirement implemented / is anything missing / is anything wrong" |
|
|
72
|
+
| 🗜️ **compact** dedicated compaction model | `/compact-model` | give `/compact` and auto-compact a cheap fast model to summarize with, leaving the main model free to focus on the conversation; zero overhead when unconfigured, falls back to pi's default on any failure |
|
|
73
|
+
| 🚀 **autostart** session startup automation | `.pi/autostart.toml` | declare slash commands to run automatically at session start, per project (first use case: mounting multi-repo directories with `/add-dir`); never runs in untrusted directories, unregistered commands are skipped instead of being sent to the model |
|
|
74
74
|
|
|
75
75
|
---
|
|
76
76
|
|
|
77
|
-
## 🛡️ ask —
|
|
77
|
+
## 🛡️ ask — read-only consult mode
|
|
78
78
|
|
|
79
79
|
<a name="ask"></a>
|
|
80
80
|
|
|
81
|
-
|
|
81
|
+
Enter with `/ask` (press it again to exit). Meant for the case where you only want to "ask questions and read code", and absolutely never want the model to modify any file.
|
|
82
82
|
|
|
83
|
-
|
|
83
|
+
**While active**:
|
|
84
84
|
|
|
85
|
-
- **`write` / `edit`
|
|
86
|
-
- **`bash`
|
|
87
|
-
-
|
|
88
|
-
-
|
|
85
|
+
- **`write` / `edit` hard-disabled** — removed from the active tool set; pi rejects calls to inactive tools outright ("Tool not found"), so file modification becomes mechanically impossible
|
|
86
|
+
- **`bash` kept, but sandboxed to read-only** — based on shell tokenization plus write-intent detection (blocks `curl -o/-O`, `wget -O`, `dd of=`, `tee`, redirections, and the like). Heuristic, not a security boundary
|
|
87
|
+
- **System prompt appendix** — a mode description is appended so the model behaves along the read-only constraint
|
|
88
|
+
- **Grey `ask` footer status** — a permanent reminder that read-only mode is on
|
|
89
89
|
|
|
90
|
-
|
|
90
|
+
**Exit**: press `/ask` again; the tool-set snapshot taken on entry is fully restored.
|
|
91
91
|
|
|
92
92
|
---
|
|
93
93
|
|
|
94
|
-
## ⭐ favorites —
|
|
94
|
+
## ⭐ favorites — model favorites
|
|
95
95
|
|
|
96
96
|
<a name="favorites"></a>
|
|
97
97
|
|
|
98
|
-
|
|
98
|
+
Mark the models you use often so that model switching "sticks" to them. Favorites are **persisted globally, across sessions and projects**.
|
|
99
99
|
|
|
100
|
-
|
|
100
|
+
**Inside the `/model` selector** (open it with `/model` or `ctrl+l`):
|
|
101
101
|
|
|
102
|
-
-
|
|
103
|
-
- `ctrl+
|
|
104
|
-
- `ctrl+J`
|
|
102
|
+
- The top hint line shows the keys and the current filter state
|
|
103
|
+
- `ctrl+A` — toggle the favorite flag on the currently selected model; favorite models are rendered in **bold bright yellow**
|
|
104
|
+
- `ctrl+J` — toggle the "show only favorite models" filter (composable with the built-in search)
|
|
105
105
|
|
|
106
|
-
|
|
106
|
+
**Model cycling**: as long as ≥1 favorite exists, `ctrl+p` / `ctrl+shift+p` cycle **only among favorite models**. When the current model is not a favorite, `ctrl+p` jumps to the first favorite and `ctrl+shift+p` to the last; with exactly one favorite it stays put (`Only one model available`). `ctrl+l` / `/model` always allow picking any model freely, favorites or not. Set `"cycleOnlyFavorites": false` in the state file to restore full cycling.
|
|
107
107
|
|
|
108
|
-
|
|
108
|
+
**State file**: `<agent-dir>/pi-toolkits-favorites.json` (defaults to `~/.pi/agent/`, the same directory as the auto-naming-session config):
|
|
109
109
|
|
|
110
110
|
```json
|
|
111
111
|
{
|
|
@@ -114,123 +114,125 @@ pi -e ./src/index.ts
|
|
|
114
114
|
}
|
|
115
115
|
```
|
|
116
116
|
|
|
117
|
-
`/favorites`
|
|
117
|
+
The `/favorites` command lists the current favorites and the cycling mode.
|
|
118
118
|
|
|
119
|
-
>
|
|
119
|
+
> **Implementation notes**: pi's `app.model.cycleForward` (`ctrl+p`) and `cycleBackward` are **reserved keybindings** that extension shortcuts cannot override; extension shortcuts also only fire while the editor is focused. This feature therefore rebinds nothing; instead it patches two built-in prototypes at runtime (version-guarded, idempotent under `/reload`): `ModelSelectorComponent` (hint line, `ctrl+A`/`ctrl+J`, bright-yellow rendering, favorites-only filter — the keys are consumed only inside the selector, so in the editor `ctrl+j` still inserts a newline) and `AgentSession.cycleModel` (favorites-only cycling). If a pi upgrade makes the patch inapplicable, it warns and degrades gracefully without affecting the other features.
|
|
120
|
+
>
|
|
121
|
+
> **Why `ctrl+A` and not `ctrl+F`**: since pi 1.0 the default `tuiMode` is `fullscreen` (0.86–0.99 defaulted to `regular`), and on Windows/WSL the host resolves `tui.altScreen.search` to `ctrl+f`; `TuiAltScreen.handleViewportInput()` checks that binding **before** handing input to the focused overlay, so `ctrl+f` opened the transcript search instead of reaching the selector. An interim pick, `ctrl+alt+f`, was clean at the pi layer but turned out to be grabbed by a Windows screenshot tool — a key free in the host table is not necessarily free on the machine. The final pick takes a letter from "favorite" and audits each: `v` is Windows Terminal's paste, `o`/`r`/`t` are host app actions, `i` is byte-identical to Tab (the selector's scope toggle), `e` works but carries no mnemonic; `a` only shadows the search box's jump-to-line-start, reads as *add favorite*, and nothing upstream of the selector claims it. Full audit: `src/features/favorites/constants.ts` and `pnpm check:keybindings`.
|
|
120
122
|
|
|
121
123
|
---
|
|
122
124
|
|
|
123
|
-
## 📥 stash —
|
|
125
|
+
## 📥 stash — prompt stash
|
|
124
126
|
|
|
125
127
|
<a name="stash"></a>
|
|
126
128
|
|
|
127
|
-
|
|
129
|
+
One hotkey plus one stash slot, shuffling between "the prompt I am writing" and "the prompt I stashed": park a prompt to one side, free the input box for a new one, and take the old one back when you need it.
|
|
128
130
|
|
|
129
|
-
|
|
|
131
|
+
| Action | Effect |
|
|
130
132
|
| --- | --- |
|
|
131
|
-
|
|
|
132
|
-
|
|
|
133
|
-
| **400ms
|
|
133
|
+
| Press `ctrl+alt+y` once | current prompt → stash; stashed content → input box (**swap**) |
|
|
134
|
+
| Press again after a while | **toggle back and forth** between the two prompts |
|
|
135
|
+
| Press twice **within 400ms** | current prompt goes to the stash + **input box is cleared** (the old stash is discarded), so you can start writing the new prompt right away; later press once to take it back |
|
|
134
136
|
|
|
135
|
-
|
|
137
|
+
When the stash is empty, a single press means "stash + clear". The stash is **in-process memory and never written to disk** (prompts may contain sensitive content); `/new` switches sessions without clearing the stash, `/reload` clears it. While a selector (`/model`, `/tree`, …) is open the key does nothing (focus is on the component, not the editor).
|
|
136
138
|
|
|
137
|
-
|
|
139
|
+
**Why `ctrl+alt+y`** (the conclusion of a four-layer compatibility survey):
|
|
138
140
|
|
|
139
|
-
|
|
|
141
|
+
| Layer | Status | Notes |
|
|
140
142
|
| --- | --- | --- |
|
|
141
|
-
| pi
|
|
142
|
-
| Linux
|
|
143
|
-
| Windows Terminal | ✅ |
|
|
144
|
-
| Windows
|
|
143
|
+
| pi keybindings | ✅ | no default `ctrl+alt+<letter>` binding; extension shortcuts are dispatched first inside the editor |
|
|
144
|
+
| Linux terminals | ✅ | `alt` is encoded as an ESC prefix (`ctrl+alt+y` = `ESC + ctrl+y`), distinguishable without the kitty protocol |
|
|
145
|
+
| Windows Terminal | ✅ | no default `ctrl+alt` binding |
|
|
146
|
+
| Windows OS | ✅ | only `ctrl+alt+del` is reserved system-wide |
|
|
145
147
|
|
|
146
|
-
|
|
148
|
+
**Rejected candidates**: `ctrl+shift+<letter>` — terminal emulators on both Linux and Windows grab copy/paste/tabs; and on terminals without the kitty protocol it collapses into `ctrl+<letter>` (`ctrl+shift+m` is Enter, which would submit the prompt immediately — dangerous). Plain `ctrl+<letter>` — pi occupies almost all of them, and the only free one, `ctrl+q`, is the XON flow-control character, with historical baggage.
|
|
147
149
|
|
|
148
150
|
---
|
|
149
151
|
|
|
150
|
-
## 🔎 review —
|
|
152
|
+
## 🔎 review — third-party code review
|
|
151
153
|
|
|
152
154
|
<a name="review"></a>
|
|
153
155
|
|
|
154
|
-
|
|
156
|
+
After a development task is done, type the `/third-review` command to summon **a different model** as a third party to "check + fix" the code just written: the review model has **full tool permissions**, and confirmed problems are edited in place — this is not a read-only audit. The review turn stays in the current session transcript, and after the review **the session keeps the review model** (it does not switch back automatically).
|
|
155
157
|
|
|
156
|
-
>
|
|
158
|
+
> **Command-triggered only**: only `/third-review` starts a review. Typing `third-review` as chat text does not trigger it (that is sent to the model as an ordinary message), which avoids accidental launches.
|
|
157
159
|
|
|
158
|
-
|
|
160
|
+
**The review prompt focuses on three checks** (each gets an explicit verdict):
|
|
159
161
|
|
|
160
|
-
1.
|
|
161
|
-
2.
|
|
162
|
-
3.
|
|
162
|
+
1. **Is this round's requirement implemented** — is the requirement fully and correctly implemented, consistent with the requirement description
|
|
163
|
+
2. **Is anything missing** — feature points, edge cases, error handling, compatibility, tests, etc.
|
|
164
|
+
3. **Is anything wrong** — logic errors, potential bugs, type/compile errors, security and performance hazards, etc.
|
|
163
165
|
|
|
164
|
-
|
|
166
|
+
**Strict scope constraint** — the review is limited to **the changes made in the current round**, to avoid scope creep; only problems confirmed inside this round's changes are fixed directly, while pre-existing or unrelated issues outside that range are only recorded as notes and left untouched.
|
|
165
167
|
|
|
166
|
-
|
|
168
|
+
**Choosing the review model** (every `/third-review` opens a picker):
|
|
167
169
|
|
|
168
|
-
- `/third-review` →
|
|
169
|
-
-
|
|
170
|
-
-
|
|
171
|
-
-
|
|
170
|
+
- `/third-review` → opens the **same selector as `/model`** (height-limited scrolling + fuzzy search): **the currently configured model is selected by default** (✓ marker), so pressing **Enter** starts the review with it
|
|
171
|
+
- **Pick another model** → written back to the config immediately (**both on disk and in memory**), and **that model becomes the default next time**
|
|
172
|
+
- **Cancel** (Esc) → no review is run, the config is unchanged
|
|
173
|
+
- The config is persisted in `<agent-dir>/pi-toolkits-review.json` (defaults to `~/.pi/agent/`):
|
|
172
174
|
|
|
173
175
|
```json
|
|
174
176
|
{ "model": "anthropic/claude-sonnet-4" }
|
|
175
177
|
```
|
|
176
178
|
|
|
177
|
-
- `/review-model`
|
|
178
|
-
-
|
|
179
|
-
-
|
|
180
|
-
-
|
|
179
|
+
- `/review-model` — a separate config entry point: opens the same list (without changing the session model, only updating the default config)
|
|
180
|
+
- **Unconfigured / the configured model is no longer available** (e.g. it was deleted from pi's models.json) → a notice is shown and the list opens as usual (with no default entry in that case)
|
|
181
|
+
- Triggered while a task is still in progress (the agent is not idle) → you are told to wait and nothing runs; only one review may be active at a time
|
|
182
|
+
- A grey `review` status in the footer means a review model is configured
|
|
181
183
|
|
|
182
184
|
---
|
|
183
185
|
|
|
184
|
-
## 🗜️ compact —
|
|
186
|
+
## 🗜️ compact — dedicated compaction model
|
|
185
187
|
|
|
186
188
|
<a name="compact"></a>
|
|
187
189
|
|
|
188
|
-
pi
|
|
190
|
+
pi's `/compact` and auto-compact summarize the context **with the current session model** by default — the main model is usually expensive and slow, while compaction summarization is a textbook "a cheap fast model does this well" job. This feature configures a dedicated model to take over summarization for **all three compaction triggers**:
|
|
189
191
|
|
|
190
|
-
|
|
|
192
|
+
| Trigger | After takeover |
|
|
191
193
|
| --- | --- |
|
|
192
|
-
|
|
|
193
|
-
| auto-compact
|
|
194
|
-
| context overflow
|
|
194
|
+
| Manual `/compact [instructions]` | summarized by the compaction model, custom focus is injected as usual |
|
|
195
|
+
| auto-compact threshold reached | summarized by the compaction model |
|
|
196
|
+
| context overflow recovery | summarized by the compaction model, retry behavior unchanged |
|
|
195
197
|
|
|
196
|
-
|
|
198
|
+
**Parts that are identical to pi's built-in behavior after takeover** (the built-in implementation is mirrored verbatim — byte-identical since 0.87.1, verified up to 1.0.2 — and only the executing model changes):
|
|
197
199
|
|
|
198
|
-
-
|
|
199
|
-
-
|
|
200
|
-
-
|
|
201
|
-
-
|
|
202
|
-
-
|
|
203
|
-
-
|
|
200
|
+
- **Summary format** — the structured Goal / Progress / Key Decisions checkpoint; when a summary already exists, the iterative-update prompt is used so information carries over instead of being lost
|
|
201
|
+
- **Split behavior** — the `keepRecentTokens` retention policy, and `firstKeptEntryId` / `tokensBefore` passed back unchanged
|
|
202
|
+
- **Output budget** — the same formula as the built-in (history segment 0.8×, split-prefix segment 0.5× of `reserveTokens`, then capped by the model's max output)
|
|
203
|
+
- **File-list tail** — `<read-files>` / `<modified-files>` tags are appended to the summary as usual, with details maintained in the same shape
|
|
204
|
+
- **Usage accounting** — the compaction model's tokens/cost are counted in the session statistics
|
|
205
|
+
- **Cancellable** — `Esc` during compaction cancels cleanly (the signal is passed through to the underlying call)
|
|
204
206
|
|
|
205
|
-
|
|
207
|
+
**Configuration** (`<agent-dir>/pi-toolkits-compact.json`, defaults to `~/.pi/agent/`):
|
|
206
208
|
|
|
207
209
|
```json
|
|
208
210
|
{ "model": "google/gemini-2.5-flash" }
|
|
209
211
|
```
|
|
210
212
|
|
|
211
|
-
- `/compact-model`
|
|
212
|
-
-
|
|
213
|
+
- `/compact-model` — view/replace: the **same selector as `/model`** (height-limited scrolling + fuzzy search), with **the currently configured model selected by default**; Enter confirms, picking another model writes it, cancelling leaves it unchanged (to clear the config: edit `pi-toolkits-compact.json` and set `model` to `null`)
|
|
214
|
+
- Unconfigured = feature disabled, everything goes through pi's default at zero overhead; a grey `compact` status in the footer means it is configured
|
|
213
215
|
|
|
214
|
-
|
|
216
|
+
**Lossless fallback** (this feature can never "break" compaction):
|
|
215
217
|
|
|
216
|
-
|
|
|
218
|
+
| Situation | Behavior |
|
|
217
219
|
| --- | --- |
|
|
218
|
-
|
|
|
219
|
-
|
|
|
220
|
-
|
|
|
221
|
-
|
|
|
220
|
+
| Not configured | falls straight back to pi's default compaction |
|
|
221
|
+
| Configured model unavailable (e.g. deleted from models.json) | warning + fallback |
|
|
222
|
+
| Summarization failed / empty summary / token limit hit | notice + fallback to pi's default compaction |
|
|
223
|
+
| User cancelled (`Esc`) | clean cancel, no error spam |
|
|
222
224
|
|
|
223
|
-
>
|
|
225
|
+
> **Implementation notes**: the takeover point is the official `session_before_compact` extension event (all three triggers pass through it); returning a compaction result takes over completely, returning `undefined` falls back to the built-in. The auto-compact path never shows any dialog (the handler only calls `notify`). `/tree` branch summarization (`session_before_tree`) is a different event and is not taken over in this iteration — it is left for later.
|
|
224
226
|
|
|
225
227
|
---
|
|
226
228
|
|
|
227
|
-
## 🚀 autostart —
|
|
229
|
+
## 🚀 autostart — session startup automation
|
|
228
230
|
|
|
229
231
|
<a name="autostart"></a>
|
|
230
232
|
|
|
231
|
-
|
|
233
|
+
A first step for multi-repo projects: declare inside the project which slash commands should run automatically "when a new session starts" — the first use case being `/add-dir` mounting sibling repositories, so a new session does not have to replay them by hand.
|
|
232
234
|
|
|
233
|
-
|
|
235
|
+
**Configuration** (`<project root>/.pi/autostart.toml`, committed with the project; `.pi/` is pi's project-level config convention, following `.pi/mcp.json`):
|
|
234
236
|
|
|
235
237
|
```toml
|
|
236
238
|
version = 1
|
|
@@ -242,29 +244,29 @@ run = [
|
|
|
242
244
|
]
|
|
243
245
|
```
|
|
244
246
|
|
|
245
|
-
|
|
247
|
+
No file = feature disabled, zero overhead, silently skipped.
|
|
246
248
|
|
|
247
|
-
|
|
249
|
+
**When it runs**:
|
|
248
250
|
|
|
249
|
-
| session_start reason |
|
|
251
|
+
| session_start reason | Replayed | Why |
|
|
250
252
|
| --- | --- | --- |
|
|
251
|
-
| `startup` / `new` | ✅ |
|
|
252
|
-
| `fork` | ✅ |
|
|
253
|
-
| `resume` | ❌ |
|
|
254
|
-
| `reload` | ❌ |
|
|
253
|
+
| `startup` / `new` | ✅ | a new session, replay as declared |
|
|
254
|
+
| `fork` | ✅ | the branch point may predate the config's appearance |
|
|
255
|
+
| `resume` | ❌ | the restored branch already carries the mount state from its time, replaying would conflict |
|
|
256
|
+
| `reload` | ❌ | extensions are rebuilt, replaying would duplicate side effects |
|
|
255
257
|
|
|
256
|
-
|
|
258
|
+
**Safety gates** (in order):
|
|
257
259
|
|
|
258
|
-
1. **project trust**
|
|
259
|
-
2.
|
|
260
|
+
1. **project trust** — no command runs automatically in an untrusted directory (a warning is shown); a config parse failure likewise prevents execution (an error is shown)
|
|
261
|
+
2. **Command pre-validation** — only commands **registered by extensions** are executed; an unregistered command name is **skipped with a warning** and never sent to the model (for an unhandled `/x`, `sendUserMessage` treats the text as an ordinary message and sends it to the LLM — this is exactly why pre-validation is mandatory)
|
|
260
262
|
|
|
261
|
-
|
|
263
|
+
**Key constraint: extension commands only**. Commands registered via `registerCommand` such as `/add-dir` (pi-add-dir) and `/cd` (pi-cd) work; built-in commands (`/compact`, `/reload`, `/resume`, …) are dispatched in the TUI layer and cannot be triggered from the extension layer; skill commands (`/skill:name`) are an expansion mechanism rather than an execution mechanism. The target extension you depend on (e.g. pi-add-dir) must be installed first.
|
|
262
264
|
|
|
263
|
-
>
|
|
265
|
+
> **Implementation notes**: execution uses `sendUserMessage(cmd, { expandPromptTemplates: true })` — that option routes `/cmd args` into command dispatch instead of the model prompt path (the wrapper defaults to `false`; verified against pi 0.86–1.0.4 sources, see the host-compatibility discipline in AGENTS.md). Pre-validation uses `pi.getCommands()` (accepting only `source: "extension"`). Depends on `smol-toml` (zero-dependency, ~10KB, TOML 1.0).
|
|
264
266
|
|
|
265
267
|
---
|
|
266
268
|
|
|
267
|
-
## 🛠️
|
|
269
|
+
## 🛠️ Development
|
|
268
270
|
|
|
269
271
|
<a name="development"></a>
|
|
270
272
|
|
|
@@ -276,7 +278,7 @@ pnpm typecheck # tsc --noEmit
|
|
|
276
278
|
pnpm test # vitest (all feature unit tests)
|
|
277
279
|
```
|
|
278
280
|
|
|
279
|
-
###
|
|
281
|
+
### Project structure
|
|
280
282
|
|
|
281
283
|
```text
|
|
282
284
|
src/
|
|
@@ -286,6 +288,8 @@ src/
|
|
|
286
288
|
└── features/ # feature modules — one directory per feature
|
|
287
289
|
├── ask/ # ask mode: command + state machine, bash sandbox,
|
|
288
290
|
│ # system prompt banner, tests
|
|
291
|
+
├── autostart/ # session startup automation: autostart.toml parsing,
|
|
292
|
+
│ # command pre-validation + replay, tests
|
|
289
293
|
├── compact/ # dedicated compaction model: session_before_compact
|
|
290
294
|
│ # takeover + /compact-model, store, prompts,
|
|
291
295
|
│ # tests
|
|
@@ -296,9 +300,9 @@ src/
|
|
|
296
300
|
└── stash/ # prompt stash: hotkey state machine + tests
|
|
297
301
|
```
|
|
298
302
|
|
|
299
|
-
|
|
303
|
+
**Adding a feature**: create `src/features/<name>/` exporting `registerXxx(pi)` and call it from `src/index.ts`. Existing modules stay untouched. Each feature ships its own `*.test.ts` (vitest) — keep them deterministic (inject clocks/state, no real agent-dir writes).
|
|
300
304
|
|
|
301
|
-
|
|
305
|
+
**Publishing** (pnpm-only, one command):
|
|
302
306
|
|
|
303
307
|
```bash
|
|
304
308
|
pnpm release patch # 0.1.2 → 0.1.3
|
|
@@ -307,11 +311,11 @@ pnpm release major # 0.1.2 → 1.0.0 (minor + patch zeroed)
|
|
|
307
311
|
pnpm release patch --dry-run # preview without changing anything
|
|
308
312
|
```
|
|
309
313
|
|
|
310
|
-
`release`
|
|
314
|
+
`release` requires exactly one of `major | minor | patch`; incrementing a higher level zeroes the lower ones. The chain is: **the working tree must be clean (any uncommitted change aborts before anything happens, guaranteeing tag content == published content)** → `typecheck + test` gate → version bump → git commit + `vX.Y.Z` tag (self-check that tag == HEAD and the tree is clean) → `pnpm publish` (with `prepublishOnly` as a second gate) → push only the current branch + the current tag and verify the remote hashes match (if the remote tag differs, GitHub Release creation is skipped).
|
|
311
315
|
|
|
312
316
|
---
|
|
313
317
|
|
|
314
|
-
## 🤝
|
|
318
|
+
## 🤝 Contributing
|
|
315
319
|
|
|
316
320
|
<a name="contributing"></a>
|
|
317
321
|
|
|
@@ -322,7 +326,7 @@ PRs and issues welcome. A few conventions:
|
|
|
322
326
|
- **Tests** — every feature ships vitest cases; keep them deterministic (injectable clocks/state), no writes to the real agent dir
|
|
323
327
|
- **Built-in patches** (favorites) must stay **version-guarded and idempotent** — verify the target prototype members exist before patching, guard against double-apply, degrade with a `console.warn` instead of breaking the extension
|
|
324
328
|
- **Built-in prompt sync** (compact) — the summarization prompts in `src/features/compact/prompt.ts` are copied verbatim from pi's built-in compaction; when upgrading the pi dependency, diff `dist/core/compaction/compaction.js` (and `utils.js`) and update the strings + assembly helpers to match, keeping takeover output byte-compatible with stock
|
|
325
|
-
- **Keybindings** — before picking a new shortcut, check pi defaults + reserved keys, terminal control chars, and OS/terminal-emulator grabs; prefer `ctrl+alt+<letter>` for cross-platform safety
|
|
329
|
+
- **Keybindings** — before picking a new shortcut, check pi defaults + reserved keys, terminal control chars, and OS/terminal-emulator grabs; prefer `ctrl+alt+<letter>` for cross-platform safety. Do not stop at "it works on my machine in the main screen": in `fullscreen` mode (pi's default since 1.0) the alt-screen layer consumes `tui.altScreen.*` **before** dialogs see the key, and on Windows/WSL several of those bindings differ from the Linux defaults. Run `pnpm check:keybindings [candidate...]` (and `pnpm check:keybindings --all` after a pi upgrade) — it probes the installed host's resolved keybinding table with the host's own `matchesKey`. A key free in the host table can still be taken at the machine layer: Windows Terminal binds `ctrl+v` (paste) and `ctrl+shift+f` (find), and third-party Windows tools commonly grab `ctrl+alt+<letter>` (a screenshot tool once took `ctrl+alt+f`) — so try the key for real on the target machine before shipping it.
|
|
326
330
|
- **Quality gate** — `pnpm typecheck && pnpm test` must pass before submitting
|
|
327
331
|
|
|
328
332
|
---
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
<!-- markdownlint-disable MD033 -->
|
|
2
|
+
<!-- Inline <a name> anchors are the only reliable TOC targets for CJK headers on GitHub. -->
|
|
3
|
+
# ⚙️ @andares/pi-toolkits
|
|
4
|
+
|
|
5
|
+
[English](README.md) · **中文**
|
|
6
|
+
|
|
7
|
+
> pi 编码代理的集成扩展工具包 — 一个包,持续生长新能力。
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/@andares/pi-toolkits)
|
|
10
|
+
[](LICENSE)
|
|
11
|
+
[](https://pnpm.io)
|
|
12
|
+
[](https://github.com/earendil-works/pi)
|
|
13
|
+
[](https://github.com/andares/pi-toolkits)
|
|
14
|
+
|
|
15
|
+
一句话介绍:目前包含六个开箱即用的功能——**只读咨询模式** `ask`、**模型收藏** `favorites`、**提示词暂存** `stash`、**第三方代码评审** `third-review`、**独立压缩模型** `compact-model`、**会话启动自动化** `autostart`,后续持续追加。
|
|
16
|
+
|
|
17
|
+
**版本要求**:pi `>=0.86.0 <2.0.0`(覆盖最近 4 个中版本线 0.86.x / 0.87.x / 0.99.x / 1.0.x —— 0.88–0.98 从未发布。区间内全部 10 个已发布版本逐版本比对宿主 dist:用到的扩展 API 无差异,favorites 两处运行时 patch 目标 `ModelSelectorComponent` / `AgentSession.cycleModel` 字节级一致;并在 0.86.0、0.86.1、0.87.1、0.99.2、1.0.2 上逐一通过 typecheck + 全量测试,在真实 pi 1.0.2 上冒烟加载无警告)。注:pi ≥ 0.99 的宿主要求 `@earendil-works/pi-tui` 声明为 peerDependencies `"*"`(由宿主提供),本包已按要求声明。
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 📦 安装
|
|
22
|
+
|
|
23
|
+
<a name="install"></a>
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# 从 npm 安装(推荐)
|
|
27
|
+
pi install npm:@andares/pi-toolkits
|
|
28
|
+
|
|
29
|
+
# 本地开发安装
|
|
30
|
+
pi install .
|
|
31
|
+
|
|
32
|
+
# 单次会话加载,不安装
|
|
33
|
+
pi -e ./src/index.ts
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
安装后在任意 pi 会话中验证:
|
|
37
|
+
|
|
38
|
+
- `/ask` → footer 出现灰色 `ask` 状态,`write`/`edit` 被硬禁用
|
|
39
|
+
- `/model`(或 `ctrl+l`)→ 选择器顶部出现收藏提示行
|
|
40
|
+
- 输入框内按 `ctrl+alt+y` → 当前提示词被暂存并清空输入框
|
|
41
|
+
- 完成一个开发任务后输入 `/third-review` → 切换到评审模型执行查+修
|
|
42
|
+
- `/compact-model` 选一个便宜快模型 → 之后 `/compact` 与自动压缩的总结都由它执行(footer 出现灰色 `compact` 状态)
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## 📚 目录
|
|
47
|
+
|
|
48
|
+
- [安装](#install)
|
|
49
|
+
- [功能总览](#features)
|
|
50
|
+
- [🛡️ ask — 只读咨询模式](#ask)
|
|
51
|
+
- [⭐ favorites — 模型收藏](#favorites)
|
|
52
|
+
- [📥 stash — 提示词暂存](#stash)
|
|
53
|
+
- [🔎 review — 第三方代码评审](#review)
|
|
54
|
+
- [🗜️ compact — 独立压缩模型](#compact)
|
|
55
|
+
- [🚀 autostart — 会话启动自动化](#autostart)
|
|
56
|
+
- [开发](#development)
|
|
57
|
+
- [贡献](#contributing)
|
|
58
|
+
- [许可](#license)
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## ✨ 功能总览
|
|
63
|
+
|
|
64
|
+
<a name="features"></a>
|
|
65
|
+
|
|
66
|
+
| 功能 | 入口 | 一句话说明 |
|
|
67
|
+
| --- | --- | --- |
|
|
68
|
+
| 🛡️ **ask** 只读咨询模式 | `/ask` | 进入只读问答模式,`write`/`edit` 硬禁用,`bash` 只读沙箱 |
|
|
69
|
+
| ⭐ **favorites** 模型收藏 | `/model` 内 `ctrl+A` / `ctrl+J` | 收藏常用模型(加粗亮黄标记),`ctrl+p` 仅在收藏间循环 |
|
|
70
|
+
| 📥 **stash** 提示词暂存 | `ctrl+alt+y` | 一段提示词的暂存槽,按键交换 / 连按两次暂存并清空输入框 |
|
|
71
|
+
| 🔎 **review** 第三方代码评审 | `/third-review` | 召唤配置的评审模型,对刚完成的任务做查+修,重点检查「本轮需求实现 / 是否有遗漏 / 是否有错误」 |
|
|
72
|
+
| 🗜️ **compact** 独立压缩模型 | `/compact-model` | 给 `/compact` 与 auto-compact 配一个便宜快模型做总结,主模型专注对话;未配置零开销、任何异常回落 pi 默认 |
|
|
73
|
+
| 🚀 **autostart** 会话启动自动化 | `.pi/autostart.toml` | 项目内声明启动时自动执行的斜杠命令(首批用例:`/add-dir` 多仓库挂载);未信任不执行、未注册命令跳过不发给模型 |
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## 🛡️ ask — 只读咨询模式
|
|
78
|
+
|
|
79
|
+
<a name="ask"></a>
|
|
80
|
+
|
|
81
|
+
用 `/ask` 进入(再按一次退出)。适合只想要"问问题、读代码",绝不希望模型改动任何文件的场景。
|
|
82
|
+
|
|
83
|
+
**进入后**:
|
|
84
|
+
|
|
85
|
+
- **`write` / `edit` 硬禁用** —— 从活跃工具集中移除,pi 运行时对未激活工具的调用直接拒绝("Tool not found"),文件修改在机制上不可能发生
|
|
86
|
+
- **`bash` 保留但只读沙箱** —— 基于 shell 分词 + 写盘意图检测(`curl -o/-O`、`wget -O`、`dd of=`、`tee`、重定向等一律拦截)。启发式,非安全边界
|
|
87
|
+
- **系统提示词追加** —— 追加一段模式说明,模型会遵守只读约束行事
|
|
88
|
+
- **灰色 `ask` footer 状态** —— 常驻提示当前处于只读模式
|
|
89
|
+
|
|
90
|
+
**退出**:再按 `/ask`,完整恢复进入前的工具集快照。
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## ⭐ favorites — 模型收藏
|
|
95
|
+
|
|
96
|
+
<a name="favorites"></a>
|
|
97
|
+
|
|
98
|
+
标记常用模型,让模型切换「粘」在收藏上。收藏**跨会话、跨项目全局持久化**。
|
|
99
|
+
|
|
100
|
+
**在 `/model` 选择器内**(`/model` 或 `ctrl+l` 打开):
|
|
101
|
+
|
|
102
|
+
- 顶部提示行显示按键与当前过滤状态
|
|
103
|
+
- `ctrl+A` —— 对当前选中模型开/关收藏;收藏的模型文字显示为**加粗亮黄**
|
|
104
|
+
- `ctrl+J` —— 开/关「仅显示收藏模型」过滤(可与内置搜索叠加使用)
|
|
105
|
+
|
|
106
|
+
**模型循环**:只要存在 ≥1 个收藏,`ctrl+p` / `ctrl+shift+p` 就**只在收藏模型间循环**。当前模型不是收藏时,`ctrl+p` 跳到第一个收藏、`ctrl+shift+p` 跳到最后一个;收藏仅 1 个时停在原地(`Only one model available`)。`ctrl+l` / `/model` 始终可自由选择任意模型(含非收藏)。在状态文件中设 `"cycleOnlyFavorites": false` 可恢复全量循环。
|
|
107
|
+
|
|
108
|
+
**状态文件**:`<agent-dir>/pi-toolkits-favorites.json`(默认 `~/.pi/agent/`,与 auto-naming-session 配置同目录):
|
|
109
|
+
|
|
110
|
+
```json
|
|
111
|
+
{
|
|
112
|
+
"favorites": ["anthropic/claude-sonnet-4"],
|
|
113
|
+
"config": { "cycleOnlyFavorites": true }
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`/favorites` 命令列出当前收藏与循环模式。
|
|
118
|
+
|
|
119
|
+
> **实现说明**:pi 的 `app.model.cycleForward`(`ctrl+p`)与 `cycleBackward` 是**保留键位**,扩展快捷键无法覆盖;且扩展快捷键只在编辑器聚焦时生效。因此本功能不重绑任何按键,而是在运行时 patch 两个内置原型(带版本守卫、`/reload` 幂等):`ModelSelectorComponent`(提示行、`ctrl+A`/`ctrl+J`、亮黄渲染、仅收藏过滤——按键仅在选择器内消费,编辑器里 `ctrl+j` 换行不受影响)与 `AgentSession.cycleModel`(收藏循环)。若 pi 升级导致 patch 无法应用,会 warn 并优雅降级,不影响其它功能。
|
|
120
|
+
>
|
|
121
|
+
> **为何是 `ctrl+A` 而不是 `ctrl+F`**:pi 自 1.0 起 `tuiMode` 默认 `fullscreen`(0.86–0.99 默认 `regular`),而在 Windows/WSL 上宿主把 `tui.altScreen.search` 解析为 `ctrl+f`;`TuiAltScreen.handleViewportInput()` 在**把输入交给聚焦覆盖层之前**先检查该绑定,于是 `ctrl+f` 打开了会话记录搜索、根本到不了选择器。曾过渡性地选过 `ctrl+alt+f`——pi 层完全空闲,却被 Windows 侧截图工具作为全局热键截走:**宿主表空闲 ≠ 机器层空闲**。最终从 "favorite" 一词里逐字母审计:`v` 是 Windows Terminal 的粘贴键,`o`/`r`/`t` 是宿主 app 层动作,`i` 与 Tab 字节恒等(选择器的 scope 切换),`e` 可用但无记忆点;`a` 仅遮蔽搜索框内的行首跳转、读作 *add favorite*,且选择器上游无任何消费者。完整依据见 `src/features/favorites/constants.ts` 与 `pnpm check:keybindings`。
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## 📥 stash — 提示词暂存
|
|
126
|
+
|
|
127
|
+
<a name="stash"></a>
|
|
128
|
+
|
|
129
|
+
一个热键 + 一个暂存槽,在「当前提示词」与「暂存提示词」之间腾挪,方便把一段提示词先放一边、空出输入框写新的,需要时再取回。
|
|
130
|
+
|
|
131
|
+
| 操作 | 效果 |
|
|
132
|
+
| --- | --- |
|
|
133
|
+
| 按一次 `ctrl+alt+y` | 当前提示词 → 入缓存;缓存内容 → 填入输入框(**交换**) |
|
|
134
|
+
| 间隔再按 | 在两段提示词之间**来回切换** |
|
|
135
|
+
| **400ms 内连按两次** | 当前提示词入缓存 + **清空输入框**(旧暂存被丢弃),直接开写新提示词;之后按一次取回 |
|
|
136
|
+
|
|
137
|
+
缓存为空时,单按一次即「暂存 + 清空」。缓存为**进程内内存、不落盘**(提示词可能含敏感内容);`/new` 切会话不清缓存,`/reload` 清空。选择器(`/model`、`/tree` 等)打开时按键不生效(焦点在组件,不在编辑器)。
|
|
138
|
+
|
|
139
|
+
**为什么是 `ctrl+alt+y`**(四级兼容性调研结论):
|
|
140
|
+
|
|
141
|
+
| 层面 | 状态 | 说明 |
|
|
142
|
+
| --- | --- | --- |
|
|
143
|
+
| pi 键位 | ✅ | 无默认 `ctrl+alt+字母` 绑定;扩展快捷键在编辑器内最先分发 |
|
|
144
|
+
| Linux 终端 | ✅ | `alt` 以 ESC 前缀编码(`ctrl+alt+y` = `ESC + ctrl+y`),不依赖 kitty 协议即可区分 |
|
|
145
|
+
| Windows Terminal | ✅ | 无默认 `ctrl+alt` 绑定 |
|
|
146
|
+
| Windows 操作系统 | ✅ | 系统级仅保留 `ctrl+alt+del` |
|
|
147
|
+
|
|
148
|
+
**被排除的候选**:`ctrl+shift+字母` —— Linux 与 Windows 的终端 emulator 都占用复制/粘贴/标签页;且无 kitty 协议的终端上会坍缩成 `ctrl+字母`(`ctrl+shift+m` 即回车,会直接提交提示词,危险)。纯 `ctrl+字母` —— pi 几乎全部占用,唯一空闲的 `ctrl+q` 是 XON 流控字符,有历史包袱。
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## 🔎 review — 第三方代码评审
|
|
153
|
+
|
|
154
|
+
<a name="review"></a>
|
|
155
|
+
|
|
156
|
+
开发任务完成后,输入 `/third-review` 指令召唤**另一个模型**以第三方身份,对刚完成的代码做「查 + 修」:评审模型拥有**完整工具权限**,确认的问题直接改,不是只读审计。评审回合留在当前会话记录里,评审完成后**会话保持评审模型**(不自动切回)。
|
|
157
|
+
|
|
158
|
+
> **仅指令触发**:只有 `/third-review` 能发起评审。聊天中直接输入 `third-review` 不会触发(作为普通消息发给模型),避免误发。
|
|
159
|
+
|
|
160
|
+
**评审提示词重点检查三点**(逐项给出结论):
|
|
161
|
+
|
|
162
|
+
1. **本轮需求实现** —— 需求是否已完整、正确地实现,与需求描述是否一致
|
|
163
|
+
2. **是否有遗漏** —— 功能点、边界情况、异常处理、兼容性、测试等
|
|
164
|
+
3. **是否有错误** —— 逻辑错误、潜在 bug、类型/编译错误、安全与性能隐患等
|
|
165
|
+
|
|
166
|
+
**范围强约束** —— 评审仅限**当前本轮修改内容**,避免扩大范围;本轮改动范围内确认的问题才直接修复,范围外的历史遗留/无关问题只记录提示、不动手改。
|
|
167
|
+
|
|
168
|
+
**评审模型选择**(每次 `/third-review` 都会弹出选择界面):
|
|
169
|
+
|
|
170
|
+
- `/third-review` → 弹出 **`/model` 同款选择器**(限高滚动 + 模糊搜索):**默认选中当前配置的模型**(✓ 标记),直接**回车**即用它开始评审
|
|
171
|
+
- **换选其他模型** → 立即写回配置(**落盘 + 内存**),**下次默认使用该模型**
|
|
172
|
+
- **取消**(Esc)→ 不执行评审,配置不变
|
|
173
|
+
- 配置持久化于 `<agent-dir>/pi-toolkits-review.json`(默认 `~/.pi/agent/`):
|
|
174
|
+
|
|
175
|
+
```json
|
|
176
|
+
{ "model": "anthropic/claude-sonnet-4" }
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
- `/review-model` —— 单独的配置入口:弹出同一列表(不改会话模型,只更新默认配置)
|
|
180
|
+
- **未配置 / 配置的模型已不可用**(如在 pi 的 models.json 里删掉了该模型)→ 提示后照常弹出列表(此时无默认项)
|
|
181
|
+
- 任务仍在进行中(agent 未空闲)时触发 → 提示等待,不执行;同一时间只允许一个评审
|
|
182
|
+
- footer 出现灰色 `review` 状态 = 已配置评审模型
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## 🗜️ compact — 独立压缩模型
|
|
187
|
+
|
|
188
|
+
<a name="compact"></a>
|
|
189
|
+
|
|
190
|
+
pi 的 `/compact` 与 auto-compact 默认**用当前会话模型**做上下文总结——主模型往往贵且慢,而压缩总结是典型的「便宜快模型就能干好」的任务。本功能配置一个独立模型接管**全部三种压缩触发**的总结:
|
|
191
|
+
|
|
192
|
+
| 触发 | 接管后 |
|
|
193
|
+
| --- | --- |
|
|
194
|
+
| 手动 `/compact [instructions]` | 压缩模型总结,自定义 focus 照常注入 |
|
|
195
|
+
| auto-compact 阈值触发 | 压缩模型总结 |
|
|
196
|
+
| context overflow 恢复 | 压缩模型总结,重试行为不变 |
|
|
197
|
+
|
|
198
|
+
**接管后与 pi 内建行为完全一致的部分**(逐字同步 pi 内建实现,0.87.1 起字节级不变、已核对至 1.0.2,只换执行模型):
|
|
199
|
+
|
|
200
|
+
- **摘要格式** —— Goal / Progress / Key Decisions 结构化 checkpoint;已有摘要时用迭代更新版 prompt,信息延续不丢
|
|
201
|
+
- **切分行为** —— 保留最近 `keepRecentTokens` 的策略、`firstKeptEntryId` / `tokensBefore` 原样回传,不动
|
|
202
|
+
- **输出预算** —— 与内建同公式(历史段 0.8×、切分前缀段 0.5× `reserveTokens`,再按模型输出上限收敛)
|
|
203
|
+
- **文件清单尾部** —— `<read-files>` / `<modified-files>` 标签照常拼在摘要尾部,details 同形状维护
|
|
204
|
+
- **用量统计** —— 压缩模型的 token/cost 计入 session 统计
|
|
205
|
+
- **可取消** —— 压缩进行中 `Esc` 干净取消(信号透传到底层调用)
|
|
206
|
+
|
|
207
|
+
**配置**(`<agent-dir>/pi-toolkits-compact.json`,默认 `~/.pi/agent/`):
|
|
208
|
+
|
|
209
|
+
```json
|
|
210
|
+
{ "model": "google/gemini-2.5-flash" }
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
- `/compact-model` —— 查看/更换:`/model` 同款选择器(限高滚动 + 模糊搜索),**默认选中当前配置的模型**,回车即确认;换选即写入;取消则不变(清除配置:编辑 `pi-toolkits-compact.json` 把 `model` 置为 `null`)
|
|
214
|
+
- 未配置 = 功能未启用,全部走 pi 默认,零开销;footer 出现灰色 `compact` 状态 = 已配置
|
|
215
|
+
|
|
216
|
+
**无损回落**(本功能永远不会把压缩「做挂」):
|
|
217
|
+
|
|
218
|
+
| 情形 | 行为 |
|
|
219
|
+
| --- | --- |
|
|
220
|
+
| 未配置 | 直接回落 pi 默认压缩 |
|
|
221
|
+
| 配置的模型不可用(如从 models.json 删除) | warning 提示 + 回落 |
|
|
222
|
+
| 总结失败 / 摘要为空 / 命中 token 上限 | 提示 + 回落 pi 默认压缩 |
|
|
223
|
+
| 用户取消(`Esc`) | 干净取消,无错误刷屏 |
|
|
224
|
+
|
|
225
|
+
> **实现说明**:接管点是官方的 `session_before_compact` 扩展事件(三种触发都会先经过);返回压缩结果即完全接管,返回 `undefined` 即回落内建。auto-compact 路径绝不弹任何对话框(handler 内只 notify)。`/tree` 分支总结(`session_before_tree`)是另一个事件,本期不接管,留作后续迭代。
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## 🚀 autostart — 会话启动自动化
|
|
230
|
+
|
|
231
|
+
<a name="autostart"></a>
|
|
232
|
+
|
|
233
|
+
多仓库项目的第一步:在项目内声明「开新会话时要自动执行的斜杠命令」,首批用例是 `/add-dir` 挂载兄弟仓库——新会话无需手动重放。
|
|
234
|
+
|
|
235
|
+
**配置**(`<项目根>/.pi/autostart.toml`,随项目进 git;`.pi/` 是 pi 的项目级配置惯例,先例 `.pi/mcp.json`):
|
|
236
|
+
|
|
237
|
+
```toml
|
|
238
|
+
version = 1
|
|
239
|
+
|
|
240
|
+
[command]
|
|
241
|
+
run = [
|
|
242
|
+
"/add-dir ../shop-frontend",
|
|
243
|
+
"/add-dir ../shop-api",
|
|
244
|
+
]
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
无文件 = 功能未启用,零开销、静默跳过。
|
|
248
|
+
|
|
249
|
+
**执行时机**:
|
|
250
|
+
|
|
251
|
+
| session_start reason | 是否重放 | 原因 |
|
|
252
|
+
| --- | --- | --- |
|
|
253
|
+
| `startup` / `new` | ✅ | 新会话,按声明重放 |
|
|
254
|
+
| `fork` | ✅ | 分支点可能早于配置出现 |
|
|
255
|
+
| `resume` | ❌ | 恢复的分支已携带当时的挂载状态,重放反而会冲突 |
|
|
256
|
+
| `reload` | ❌ | 扩展重建,重放会重复副作用 |
|
|
257
|
+
|
|
258
|
+
**安全门**(按序):
|
|
259
|
+
|
|
260
|
+
1. **project trust** —— 未信任目录不自动执行任何命令(warning 提示);配置解析失败同样不执行(error 提示)
|
|
261
|
+
2. **命令预校验** —— 只执行「扩展注册的命令」;未注册的命令名**跳过并警告**,绝不发给模型(sendUserMessage 对未处理的 `/x` 会当作普通消息发给 LLM,这是必须预校验的原因)
|
|
262
|
+
|
|
263
|
+
**关键约束:只支持扩展命令**。`/add-dir`(pi-add-dir)、`/cd`(pi-cd)等 `registerCommand` 注册的命令可以;内置命令(`/compact`、`/reload`、`/resume`…)在 TUI 层分发,扩展层触发不了;skill 命令(`/skill:name`)是展开机制而非执行机制。依赖的目标扩展(如 pi-add-dir)需要先安装。
|
|
264
|
+
|
|
265
|
+
> **实现说明**:执行用 `sendUserMessage(cmd, { expandPromptTemplates: true })` —— 该选项把 `/cmd args` 路由到命令分发而不是模型 prompt 路径(包装层默认 `false`,已对照 pi 0.86–1.0.4 源码核实,见 AGENTS.md 宿主兼容纪律)。预校验用 `pi.getCommands()`(只认 `source: "extension"`)。依赖 `smol-toml`(零依赖、~10KB、TOML 1.0)。
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## 🛠️ 开发
|
|
270
|
+
|
|
271
|
+
<a name="development"></a>
|
|
272
|
+
|
|
273
|
+
**pnpm 是本仓库唯一受支持的包管理器** —— 本仓库内不得使用 `npm install` / `npm publish`。
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
pnpm install # 安装依赖
|
|
277
|
+
pnpm typecheck # tsc --noEmit
|
|
278
|
+
pnpm test # vitest(全部功能单元测试)
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### 项目结构
|
|
282
|
+
|
|
283
|
+
```text
|
|
284
|
+
src/
|
|
285
|
+
├── index.ts # 入口:聚合各功能模块
|
|
286
|
+
├── lib/ # 共享基础设施(工具集快照/恢复、
|
|
287
|
+
│ # review 与 compact 共用的模型 key 解析)
|
|
288
|
+
└── features/ # 功能模块 —— 一个功能一个目录
|
|
289
|
+
├── ask/ # ask 模式:命令 + 状态机、bash 沙箱、
|
|
290
|
+
│ # 系统提示词横幅、测试
|
|
291
|
+
├── autostart/ # 会话启动自动化:autostart.toml 解析、
|
|
292
|
+
│ # 命令预校验 + 重放、测试
|
|
293
|
+
├── compact/ # 独立压缩模型:session_before_compact 接管 +
|
|
294
|
+
│ # /compact-model、配置存储、prompt、测试
|
|
295
|
+
├── favorites/ # 模型收藏:选择器与循环 patch、
|
|
296
|
+
│ # 持久化存储、测试
|
|
297
|
+
├── review/ # 第三方评审:输入/指令触发、
|
|
298
|
+
│ # 评审模型配置存储、提示词、测试
|
|
299
|
+
└── stash/ # 提示词暂存:热键状态机 + 测试
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
**新增功能**:新建 `src/features/<name>/`,导出 `registerXxx(pi)`,在 `src/index.ts` 中调用;已有模块保持不变。每个功能自带 `*.test.ts`(vitest)——保持确定性(时钟/状态可注入,不写真实 agent-dir)。
|
|
303
|
+
|
|
304
|
+
**发布**(仅 pnpm 一条命令):
|
|
305
|
+
|
|
306
|
+
```bash
|
|
307
|
+
pnpm release patch # 0.1.2 → 0.1.3
|
|
308
|
+
pnpm release minor # 0.1.2 → 0.2.0 (patch 清零)
|
|
309
|
+
pnpm release major # 0.1.2 → 1.0.0 (minor + patch 清零)
|
|
310
|
+
pnpm release patch --dry-run # 预览,不做任何改动
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
`release` 要求且仅要求 `major | minor | patch` 之一;上级递增清零下级。执行链:**工作区必须干净(有未提交变更直接中止,保证 tag 内容 == 发布内容)** → `typecheck + test` 门禁 → 改版本 → git commit + `vX.Y.Z` tag(自检 tag==HEAD 且工作区干净) → `pnpm publish`(`prepublishOnly` 二次门禁) → 只推当前分支 + 当前 tag 并校验远端哈希一致(远端 tag 不一致则跳过 Release 创建)。
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
## 🤝 贡献
|
|
318
|
+
|
|
319
|
+
<a name="contributing"></a>
|
|
320
|
+
|
|
321
|
+
欢迎提交 PR 与 issue。几条约定:
|
|
322
|
+
|
|
323
|
+
- **只用 pnpm** —— 不得 `npm install` / `npm publish`;用 `pnpm add` / `pnpm publish`
|
|
324
|
+
- **功能模块模式** —— 一个功能一个目录(`src/features/<name>/`),导出 `registerXxx(pi)`,在 `src/index.ts` 注册;共享基础设施放 `src/lib/`
|
|
325
|
+
- **测试** —— 每个功能自带 vitest 用例;保持确定性(时钟/状态可注入),不写真实 agent dir
|
|
326
|
+
- **内建 patch**(favorites)必须保持**版本守卫 + 幂等** —— 打 patch 前先确认目标原型成员存在,防止重复应用,失败时用 `console.warn` 降级而不是把扩展搞挂
|
|
327
|
+
- **内建提示词同步**(compact) —— `src/features/compact/prompt.ts` 里的总结 prompt 逐字抄自 pi 内建压缩;升级 pi 依赖时 diff `dist/core/compaction/compaction.js`(及 `utils.js`),同步字符串与组装辅助函数,保持接管输出与内建字节级一致
|
|
328
|
+
- **键位** —— 定新快捷键前先查 pi 默认键位 + 保留键、终端控制字符、OS/终端模拟器占用;跨平台安全优先选 `ctrl+alt+<字母>`。**不得只验证「主屏下我这台机器能用」**:自 pi 1.0 起默认 `fullscreen`,alt-screen 层在对话框看到按键**之前**就消费 `tui.altScreen.*`,且 Windows/WSL 上多个绑定与 Linux 默认值不同。用 `pnpm check:keybindings [候选键...]` 核查(pi 升级后跑 `pnpm check:keybindings --all`),它用宿主自己的 `matchesKey` 探测已安装宿主的**已解析**键位表。**宿主表空闲 ≠ 机器层空闲**:Windows Terminal 默认占 `ctrl+v`(粘贴)与 `ctrl+shift+f`(查找),第三方 Windows 工具常绑 `ctrl+alt+<字母>`(截图工具占过 `ctrl+alt+f`)——定键后必须在真机试按,确认按键真的到得了应用。
|
|
329
|
+
- **质量门禁** —— 提交前必须通过 `pnpm typecheck && pnpm test`
|
|
330
|
+
|
|
331
|
+
---
|
|
332
|
+
|
|
333
|
+
## 📄 许可
|
|
334
|
+
|
|
335
|
+
<a name="license"></a>
|
|
336
|
+
|
|
337
|
+
MIT © 2026 Andares Cui. 见 [LICENSE](LICENSE)。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@andares/pi-toolkits",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Integrated toolkit extension for pi — ask mode (read-only Q&A), model favorites, prompt stash, third-party code review, and a dedicated compaction model",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
"scripts": {
|
|
52
52
|
"typecheck": "tsc --noEmit",
|
|
53
53
|
"test": "vitest run",
|
|
54
|
+
"check:keybindings": "node ./scripts/check-keybindings.mjs",
|
|
54
55
|
"release": "node ./scripts/publish.mjs",
|
|
55
56
|
"tag-current": "node ./scripts/tag-current.mjs"
|
|
56
57
|
}
|
|
@@ -4,17 +4,67 @@
|
|
|
4
4
|
* Keybindings are only active INSIDE the /model model selector (the
|
|
5
5
|
* ModelSelectorComponent consumes input before the search box, see
|
|
6
6
|
* patch-selector.ts). Outside the selector the default pi bindings are
|
|
7
|
-
* untouched: ctrl+
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* untouched: ctrl+j stays "new line". ctrl+p/ctrl+shift+p keep their
|
|
8
|
+
* bindings — only the cycle semantics change (patch-cycle.ts), so no
|
|
9
|
+
* keybinding conflicts arise.
|
|
10
|
+
*
|
|
11
|
+
* Verified against pi 0.86.0 / 0.87.0 / 0.99.0 / 1.0.2 / 1.0.4 keybinding
|
|
12
|
+
* tables + pi-tui input dispatch on WSL2 + Windows Terminal; re-run
|
|
13
|
+
* `pnpm check:keybindings` after every host upgrade before trusting this.
|
|
10
14
|
*/
|
|
11
15
|
export const FAVORITES_STATUS_KEY = "favorites";
|
|
12
16
|
|
|
13
17
|
/** State file, written next to the auto-naming-session config convention. */
|
|
14
18
|
export const FAVORITES_FILE = "pi-toolkits-favorites.json";
|
|
15
19
|
|
|
16
|
-
/**
|
|
17
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Selector-scoped keybindings (only honored while the model selector is open).
|
|
22
|
+
*
|
|
23
|
+
* Toggle key history: ctrl+f (through v0.6.0) → ctrl+alt+f (discarded before
|
|
24
|
+
* release) → ctrl+a (current). Each step is a real layer the previous pick
|
|
25
|
+
* missed — audit trail below; re-run `pnpm check:keybindings` on upgrades.
|
|
26
|
+
*
|
|
27
|
+
* Why ctrl+f is dead: fullscreen became pi's default `tuiMode` in 1.0
|
|
28
|
+
* (0.86–0.99 defaulted to `regular`, where the binding is inert). On
|
|
29
|
+
* Windows/WSL `tui.altScreen.search` resolves to ctrl+f, and
|
|
30
|
+
* `TuiAltScreen.handleViewportInput()` checks it before
|
|
31
|
+
* `shouldDeferViewportInputToOverlay()`, so the keystroke opens transcript
|
|
32
|
+
* search and the selector never sees it (verified in pi-tui 1.0.4
|
|
33
|
+
* `dist/tui-alt-screen.js`: the search check is the first key branch and
|
|
34
|
+
* returns `{ consume: true }` unconditionally).
|
|
35
|
+
*
|
|
36
|
+
* Why ctrl+alt+f was discarded: clean at the pi layer across 0.86–1.0 (only
|
|
37
|
+
* ctrl+alt+] is taken from that family) and byte-distinguishable without
|
|
38
|
+
* kitty — but grabbed at the OS/app layer on the author's machine by a
|
|
39
|
+
* Windows screenshot tool, so it never reached the terminal. Lesson: a key
|
|
40
|
+
* that is free in the host table is not necessarily free on the machine.
|
|
41
|
+
*
|
|
42
|
+
* Why ctrl+a (letter taken from "favorite", audited with the host's own
|
|
43
|
+
* matchesKey on WSL2 + Windows Terminal):
|
|
44
|
+
* - f: consumed upstream by `tui.altScreen.search` in fullscreen (above).
|
|
45
|
+
* - v: free at the pi layer on WSL (`app.clipboard.pasteImage` is alt+v
|
|
46
|
+
* there) but Windows Terminal binds ctrl+v to paste — and pasting a model
|
|
47
|
+
* name into the search box is a feature, not a slot to steal.
|
|
48
|
+
* - o / r / t: host-owned at the app layer (`app.tools.expand`,
|
|
49
|
+
* `app.session.rename`, `app.thinking.toggle`).
|
|
50
|
+
* - i: byte-identical to Tab on non-kitty terminals ("\t"), and Tab is the
|
|
51
|
+
* selector's built-in scope toggle — a guaranteed fight.
|
|
52
|
+
* - e: viable (shadows only `tui.editor.cursorLineEnd` inside the selector)
|
|
53
|
+
* but carries no mnemonic; a was preferred.
|
|
54
|
+
* - a: shadows only `tui.editor.cursorLineStart` (readline home) inside the
|
|
55
|
+
* selector — our patch consumes it before the search box, exactly like
|
|
56
|
+
* ctrl+j shadows newLine. No upstream consumer, nothing bound at the
|
|
57
|
+
* Windows Terminal / Windows OS layer, no other loaded extension claims
|
|
58
|
+
* it, and "a" reads as *add favorite*.
|
|
59
|
+
* (Rejected elsewhere: alt+f = cursorWordRight + tree unfold; shift+f is
|
|
60
|
+
* the literal character "F" without kitty, stealing typed search text;
|
|
61
|
+
* ctrl+shift+f collapses to ctrl+f on Windows/WSL and is Windows Terminal's
|
|
62
|
+
* own Find.)
|
|
63
|
+
*
|
|
64
|
+
* ctrl+j is kept: no host layer consumes it before the selector, so it only
|
|
65
|
+
* shadows `tui.input.newLine` inside the search box.
|
|
66
|
+
*/
|
|
67
|
+
export const FAVORITE_TOGGLE_KEY = "ctrl+a";
|
|
18
68
|
export const FAVORITES_ONLY_KEY = "ctrl+j";
|
|
19
69
|
|
|
20
70
|
/**
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* - cycle patch: favorites-only cycling (fallback to stock when disabled or
|
|
7
7
|
* no favorites; favorites-only pick; single-favorite jump; not-a-favorite
|
|
8
8
|
* → forward first / backward last)
|
|
9
|
-
* - selector patches: ctrl+j favorites-only filter, ctrl+
|
|
10
|
-
* injection, idempotent apply
|
|
9
|
+
* - selector patches: ctrl+j favorites-only filter, ctrl+a toggle, hint
|
|
10
|
+
* injection, idempotent apply, plain ctrl+f left to the host
|
|
11
11
|
*
|
|
12
12
|
* The patches read the shared store via getFavoritesStore(); each test gets an
|
|
13
13
|
* isolated store backed by a temp file via setSharedFavoritesStore(). The
|
|
@@ -408,18 +408,37 @@ describe("selector patches", () => {
|
|
|
408
408
|
expect(selector.filteredModels).toHaveLength(3);
|
|
409
409
|
});
|
|
410
410
|
|
|
411
|
-
it("ctrl+
|
|
411
|
+
it("ctrl+a toggles favorite for the selected model", () => {
|
|
412
412
|
const selector = fakeSelector({
|
|
413
413
|
filteredModels: models.map(item),
|
|
414
414
|
selectedIndex: 1, // opus-4
|
|
415
415
|
activeModels: models.map(item),
|
|
416
416
|
});
|
|
417
|
-
selector.handleInput("\
|
|
417
|
+
selector.handleInput("\x01"); // ctrl+a
|
|
418
418
|
expect(getFavoritesStore().has(models[1])).toBe(true);
|
|
419
|
-
selector.handleInput("\
|
|
419
|
+
selector.handleInput("\x01");
|
|
420
420
|
expect(getFavoritesStore().has(models[1])).toBe(false);
|
|
421
421
|
});
|
|
422
422
|
|
|
423
|
+
it("does not intercept the retired keys ctrl+f / ctrl+alt+f (host/machine-owned)", () => {
|
|
424
|
+
const forwarded: string[] = [];
|
|
425
|
+
getFavoritesStore().toggle(models[1]);
|
|
426
|
+
const selector = fakeSelector({
|
|
427
|
+
filteredModels: models.map(item),
|
|
428
|
+
selectedIndex: 1,
|
|
429
|
+
activeModels: models.map(item),
|
|
430
|
+
searchInput: {
|
|
431
|
+
getValue: () => "",
|
|
432
|
+
handleInput: (d: string) => forwarded.push(d),
|
|
433
|
+
},
|
|
434
|
+
});
|
|
435
|
+
selector.handleInput("\x06"); // stock ctrl+f — upstream transcript search
|
|
436
|
+
selector.handleInput("\x1b\x06"); // retired ctrl+alt+f — machine-level hotkey
|
|
437
|
+
// Both reached the search box instead of our toggle, so the store is unchanged.
|
|
438
|
+
expect(forwarded).toEqual(["\x06", "\x1b\x06"]);
|
|
439
|
+
expect(getFavoritesStore().has(models[1])).toBe(true);
|
|
440
|
+
});
|
|
441
|
+
|
|
423
442
|
it("injects the hint row once (children.length grows by exactly one)", () => {
|
|
424
443
|
const selector = fakeSelector({
|
|
425
444
|
children: [1, 2, 3, 4] as unknown[],
|
|
@@ -4,11 +4,15 @@
|
|
|
4
4
|
* What it does:
|
|
5
5
|
* - Inside the /model selector (opened via /model or ctrl+l):
|
|
6
6
|
* * a hint row at the top shows the keys and the favorites-only state
|
|
7
|
-
* * ctrl+
|
|
7
|
+
* * ctrl+a toggles favorite on the currently selected model
|
|
8
8
|
* * ctrl+j toggles the "only favorites" filter
|
|
9
9
|
* * favorited model ids render in bold bright yellow
|
|
10
10
|
* The keys are consumed by the selector component only, so pi's editor
|
|
11
|
-
* bindings (ctrl+
|
|
11
|
+
* bindings (ctrl+j newline) are untouched elsewhere. (The toggle moved off
|
|
12
|
+
* ctrl+f after v0.6.0 — pi >= 1.0 defaults to fullscreen, where ctrl+f is
|
|
13
|
+
* consumed by the alt-screen transcript search before the selector sees
|
|
14
|
+
* it — and an interim ctrl+alt+f pick turned out to be grabbed by a
|
|
15
|
+
* Windows screenshot tool; see constants.ts for the full key audit.)
|
|
12
16
|
* - ctrl+p / ctrl+shift+p keep their bindings but, once favorites exist
|
|
13
17
|
* (and cycleOnlyFavorites is not false), cycle only among favorited
|
|
14
18
|
* models instead of all models. ctrl+l / /model remain the escape hatch
|
|
@@ -19,7 +23,7 @@
|
|
|
19
23
|
* Implementation: pi's extension API has no hook to render inside the
|
|
20
24
|
* built-in model selector and cannot rebind the reserved ctrl+p actions, so
|
|
21
25
|
* the feature patches two built-in prototypes at runtime:
|
|
22
|
-
* - ModelSelectorComponent (hint row,
|
|
26
|
+
* - ModelSelectorComponent (hint row, favorites keys, styling, filter)
|
|
23
27
|
* - AgentSession.cycleModel (favorites-only cycling)
|
|
24
28
|
* Both patches are version-guarded (skip + warn if the API changed) and
|
|
25
29
|
* idempotent across /reload.
|
|
@@ -91,13 +95,14 @@ export function registerFavorites(pi: ExtensionAPI): void {
|
|
|
91
95
|
applyCyclePatch();
|
|
92
96
|
|
|
93
97
|
pi.registerCommand("favorites", {
|
|
94
|
-
description:
|
|
98
|
+
description:
|
|
99
|
+
"List favorited models (add/remove with ctrl+A in /model)",
|
|
95
100
|
handler: async (_args: string, ctx: ExtensionContext) => {
|
|
96
101
|
captureTheme(ctx);
|
|
97
102
|
const list = getFavoritesStore().list();
|
|
98
103
|
if (list.length === 0) {
|
|
99
104
|
ctx.ui.notify(
|
|
100
|
-
"No favorited models. Open /model and press ctrl+
|
|
105
|
+
"No favorited models. Open /model and press ctrl+A on a model to favorite it.",
|
|
101
106
|
"info",
|
|
102
107
|
);
|
|
103
108
|
return;
|
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
* pi's extension API has no hook to render inside the built-in model
|
|
5
5
|
* selector, so we patch the component prototype at runtime. Three hooks:
|
|
6
6
|
*
|
|
7
|
-
* 1. handleInput — intercept
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
7
|
+
* 1. handleInput — intercept the favorites keys (see constants.ts for why
|
|
8
|
+
* they are ctrl+a / ctrl+j) BEFORE the original
|
|
9
|
+
* forwards them to the search box. Scoped to the
|
|
10
|
+
* selector: outside it, ctrl+j keeps pi's default editor
|
|
11
|
+
* binding untouched.
|
|
12
12
|
* 2. updateList — inject a hint row at the top of the selector and render
|
|
13
13
|
* favorited model ids in bold bright yellow.
|
|
14
14
|
* 3. filterModels — when "only favorites" is on, temporarily swap
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* keeps loading and ctrl+p falls back to stock behavior. The prototype
|
|
23
23
|
* shape touched here (handleInput/updateList/filterModels and the instance
|
|
24
24
|
* fields below) is byte-identical across every published version in
|
|
25
|
-
* pi 0.86.0 → 1.0.
|
|
25
|
+
* pi 0.86.0 → 1.0.4.
|
|
26
26
|
*/
|
|
27
27
|
import {
|
|
28
28
|
ModelSelectorComponent,
|
|
@@ -138,7 +138,7 @@ export function applyModelSelectorPatches(): boolean {
|
|
|
138
138
|
const originalUpdateList = proto.updateList;
|
|
139
139
|
const originalFilterModels = proto.filterModels;
|
|
140
140
|
|
|
141
|
-
// ── 1. Key interception (
|
|
141
|
+
// ── 1. Key interception (favorites keys) ────────────────────────────
|
|
142
142
|
proto.handleInput = function (
|
|
143
143
|
this: SelectorPatchTarget,
|
|
144
144
|
keyData: string,
|
package/src/index.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Current features:
|
|
8
8
|
* - ask — read-only Q&A mode (/ask)
|
|
9
|
-
* - favorites — model favorites (ctrl+
|
|
9
|
+
* - favorites — model favorites (ctrl+A/ctrl+J in /model, favorites-only cycling)
|
|
10
10
|
* - stash — prompt stash (ctrl+alt+y swap / double-press stash-and-clear)
|
|
11
11
|
* - review — third-party code review with a configured review model (third-review)
|
|
12
12
|
* - compact — dedicated compact model for /compact + auto-compact summaries
|