@foolsecret/pi-prompt 0.4.0 → 0.4.9
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/CHANGELOG.md +203 -0
- package/README.md +43 -5
- package/package.json +55 -55
- package/src/auto.ts +109 -13
- package/src/command.ts +10 -2
- package/src/compare.ts +228 -0
- package/src/config.ts +178 -1
- package/src/context.ts +136 -0
- package/src/draft.ts +189 -0
- package/src/info-page.ts +115 -0
- package/src/prompt-extension.ts +350 -70
- package/src/prompts.ts +48 -6
- package/src/stats.ts +50 -5
- package/src/tool-output.ts +128 -0
- package/src/ui.ts +598 -96
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,209 @@
|
|
|
4
4
|
格式参考 Keep a Changelog;版本号遵循 semver,与 `package.json` 一致。
|
|
5
5
|
变更类型:✨新增 / 🐛修复 / 🏗️重构 / 🧹清理 / 🎨样式 / 📄文档。
|
|
6
6
|
|
|
7
|
+
## v0.4.9 (2026-09-13)
|
|
8
|
+
|
|
9
|
+
**压缩指令可配置:引导摘要保留“工作状态”**
|
|
10
|
+
|
|
11
|
+
- ✨ **新增 `compactInstructions`**(config + env `PI_PROMPT_COMPACT_INSTRUCTIONS`):
|
|
12
|
+
传给 `ctx.compact({ customInstructions })`,引导 pi 的摘要保留
|
|
13
|
+
**当前任务 / 已完成到哪 / 下一步 / 阻塞**,避免压缩后丢失工作状态、
|
|
14
|
+
导致后续回合重新勘测与重复劳动。
|
|
15
|
+
- 📄 **默认内置**(用户可覆盖):
|
|
16
|
+
> 摘要时请额外保留:当前正在进行的任务、已完成到哪一步、下一步计划、未解决的阻塞。
|
|
17
|
+
> 不要只总结历史对话,要保留工作状态。
|
|
18
|
+
|
|
19
|
+
显式空串(配置或 env)= 不追加,回到 pi 原生行为。
|
|
20
|
+
- ✨ **设置抽屉**:“上下文”组新增“压缩指令(保留工作状态)”行,
|
|
21
|
+
submenu 选预设档(内置默认 / 关闭);自定义文案请改配置文件(长文本无自由输入)。
|
|
22
|
+
- 📄 **勘误**:pi-prompt 本身**不生成**压缩提示词(那是 pi 宿主的),
|
|
23
|
+
只提供向 `compact()` 追加的**自定义指令**。三轴注入与此无关,一直正常工作。
|
|
24
|
+
- 🧹 测试 93 → **96 全绿**(含“压缩确实带上 customInstructions”端到端);`tsc` 0 错误。
|
|
25
|
+
|
|
26
|
+
## v0.4.8 (2026-09-13)
|
|
27
|
+
|
|
28
|
+
**设置抽屉改为“草稿 + Ctrl+S” + 轴重命名 + Markdown 只读页 + 删除无参 toggle**
|
|
29
|
+
|
|
30
|
+
- ✨ **设置抽屉改为草稿保存模型**(对齐 pi-pricer):抽屉内编辑只改内存草稿,
|
|
31
|
+
状态栏常驻 `● 未保存改动(面)`;**Ctrl+S** 落盘后转 `✓ 已保存`,**Ctrl+R** 丢弃。
|
|
32
|
+
根层 Esc 且有未保存改动时**拦截退出**(首次提示、二次才放弃)。
|
|
33
|
+
- 新增 `src/draft.ts`(纯逻辑):`save()` 会**同时写配置与会话三轴**——
|
|
34
|
+
否则“未保存”语义是假的(轴改了却不落)。
|
|
35
|
+
- 键盘拦截用 `matchesKey` 而非裸字节:pi-tui 在增强键盘协议下把 Ctrl+S
|
|
36
|
+
编码为 CSI-u / modifyOtherKeys,裸比较 `\x13` 永远不命中(保存静默失效)。
|
|
37
|
+
- ✨ **轴重命名**(英文键不变,配置文件兼容):`show→风格`、`write→代码`、`do→行动`。
|
|
38
|
+
理由:原中文名“输出风格/代码量/行为力度”不齐整,且“工具”已被分类名占用。
|
|
39
|
+
- ✨ **新增 `src/info-page.ts`**(移植自 pi-pricer):Markdown + 固定 16 行窗口 +
|
|
40
|
+
↑↓/PgUp/PgDn 滚动 + Esc 返回。`/prompt status`、`usage`、`check` 改用它(TUI),
|
|
41
|
+
无 TUI 回退纯文本 notify。
|
|
42
|
+
- 🐛 **`###` 显示为字面量的根因**:pi-tui 的 Markdown 只对 **h1/h2** 去前缀
|
|
43
|
+
(`markdown.js:350` 对 `depth>=3` 保留了 `### ` 字面量)。故本项目的 markdown
|
|
44
|
+
**只用 `#` 与 `##`,禁用 `###`**(已加回归测试锁死)。
|
|
45
|
+
- 🗑️ **删除 `/prompt` 无参的 toggle**:v0.1/v0.2 遗留,与抽屉重复且易误触
|
|
46
|
+
(会一键把三轴全置 normal)。现在无参 = **显示帮助**。
|
|
47
|
+
- 🧹 测试 83 → **85 全绿**(新增 InfoPage 契约 + 草稿态/Ctrl+S 端到端);`tsc` 0 错误。
|
|
48
|
+
|
|
49
|
+
## v0.4.7 (2026-09-13)
|
|
50
|
+
|
|
51
|
+
**修复设置抽屉:改值后不刷新、无保存提示、toolOutput 开关写错字段**
|
|
52
|
+
|
|
53
|
+
- 🐛 **改值后根层摘要不刷新**(核心):`buildRootItems`/`buildGroupItems` 都是
|
|
54
|
+
**构造时快照**,`SettingsList.items` 也冻结。旧实现下改值后返回根层,摘要仍显示旧值,
|
|
55
|
+
而重新进入子层却是新值(因为 `submenu` 每次都重建)——即用户看到的
|
|
56
|
+
“返回上一层和进入后完全不一样”。修:变更后**重建当前层与根层列表**,
|
|
57
|
+
并用 `selectItem(id)` 把光标放回刚改的行。
|
|
58
|
+
- 🐛 **无保存提示**:抽屉内没有任何状态/提示行,且 `ctx.ui.notify` 在浮层
|
|
59
|
+
(`ui.custom`)打开时会被遮挡。修:新增**状态栏**(对齐 pi-pricer 形,语义相反),
|
|
60
|
+
固定显示 `✓ 已自动保存(无需 Ctrl+S) Enter 修改 · Esc 返回`。
|
|
61
|
+
- 🐛 **toolOutput 开关写错字段**:`flag-toolOutput` 走了顶层 `writeFlag`,但它是
|
|
62
|
+
嵌套配置 `toolOutput.enabled`。修:单独路由到 `writeToolOutput({ enabled })`。
|
|
63
|
+
- ✨ 回调签名改为返回 `boolean`(写盘成败),状态栏据此显示“已保存/写入失败”。
|
|
64
|
+
- 📄 明确**不提供 Ctrl+S**:pi-prompt 是**即改即存**(与 pi-pricer 的草稿+保存
|
|
65
|
+
语义不同),提示 Ctrl+S 会暗示“有未保存改动”而与实现矛盾。
|
|
66
|
+
- 🧹 测试 82 → **83 全绿**(新增“状态栏 + 根层摘要刷新”端到端用例);`tsc` 0 错误。
|
|
67
|
+
|
|
68
|
+
## v0.4.6 (2026-09-13)
|
|
69
|
+
|
|
70
|
+
**设置抽屉全量配置项 + 分类钻取(对齐 pi-pricer)+ 修复配置读档丢字段**
|
|
71
|
+
|
|
72
|
+
- 🐛 **修复(重要)**:`config.load()` 只白名单了 6 个字段,导致
|
|
73
|
+
`peakUpgrade` / `maxTokensCap` / `autoSample*` / `hysteresis` / `autoCompact*` /
|
|
74
|
+
`toolOutput` 从配置文件写入后**重读会被静默丢弃**(之前只有 env 与内存注入生效)。
|
|
75
|
+
改为 `{...parsed}` 全量保留。
|
|
76
|
+
- ✨ **设置抽屉覆盖全部配置项**(之前仅 6 行三轴):新增开关 5 项、数值 7 项、
|
|
77
|
+
工具上限 4 项,共 20+ 行。
|
|
78
|
+
- ✨ **分类钻取**(对齐 pi-pricer 的 submenu 范式):根层 4 分类
|
|
79
|
+
(**提示词 / 工具 / 上下文 / 其他**)→ Enter 钻入子层 → Esc 逐级返回,
|
|
80
|
+
面包屑标题 `pi-prompt 设置 · 工具`;根层行显示分类摘要(如 `show=auto`)。
|
|
81
|
+
- ✨ **数值行用预设档子菜单**(`SettingItem` 无自由文本输入):
|
|
82
|
+
如 hysteresis `0/0.25/0.5/1`、压缩上限 `100K/200K/400K/800K`。
|
|
83
|
+
- ✨ config 新增类型化写入 `writeFlag` / `writeNumber` / `writeToolOutput`
|
|
84
|
+
(均走 `persist()` 合并写,不丢未展示字段)。
|
|
85
|
+
- 📄 `DESIGN.md` 同步:front matter 加 `levels` token,正文改为四分类钻取契约。
|
|
86
|
+
- 🧹 测试 76 → **82 全绿**;`tsc --noEmit` 0 错误。
|
|
87
|
+
|
|
88
|
+
## v0.4.5 (2026-09-13)
|
|
89
|
+
|
|
90
|
+
**工具输出截断(默认关、按工具分层、缓存友好)**
|
|
91
|
+
|
|
92
|
+
- ✨ **新增工具输出截断**(config `toolOutput.enabled`,**默认关**):
|
|
93
|
+
- 背景:实测工具输出占上下文 **27~65%**(大会话),是输入成本的隐性大项。
|
|
94
|
+
- pi 自己已把工具输出截到 **50KB**(read/grep/bash/find 全覆盖),但 50KB
|
|
95
|
+
之上仍有冗余(实测 grep 均 13.6KB、read p95 9.5KB)。
|
|
96
|
+
- ✨ **按工具分层**(对齐 Reasonix per-tool 思路):grep 8K / bash 8K /
|
|
97
|
+
read 16K / 兵底 8K。read 高的原因:模型主动要看,砍很会引发重读。
|
|
98
|
+
- ✨ **在 `tool_result` hook 创建时截断一次**(此后字节不变 → **缓存友好**);
|
|
99
|
+
未超限**原样返回**(零影响);错误结果不截;图片不截。
|
|
100
|
+
- 📄 **为何默认关**:改工具输出会改变模型看到的内容(行为风险),由用户显式开。
|
|
101
|
+
- 📀 **勘测结论(认知 #48~#50)**:pi 已有 50KB;**只看单一模型会误判 11 倍**
|
|
102
|
+
(v4-pro/glm 的 hit 是 flash 的 7.5~11.5 倍);Reasonix 的“32KB”是触发
|
|
103
|
+
阀值,实际净保留仅 11~16KB,照搬数字无效。
|
|
104
|
+
- 🧹 测试 68 → **76 全绿**;`tsc --noEmit` 0 错误。
|
|
105
|
+
|
|
106
|
+
## v0.4.4 (2026-09-12)
|
|
107
|
+
|
|
108
|
+
**自动上下文压缩(默认关)+ 勘测:1M 窗口下 pi 自带压缩形同虚设**
|
|
109
|
+
|
|
110
|
+
- ✨ **新增自动上下文压缩**(config `autoCompact`,**默认关**):
|
|
111
|
+
- 背景:pi 自带压缩阈值为 `contextWindow − 16384`。本用户的
|
|
112
|
+
deepseek-v4-pro / glm-5.3-flash 窗口均为 **1,000,000** → 阈值 **983,616**,
|
|
113
|
+
正常使用下**永远不会触发**(实测长会话上下文涨到 33 万仍在涨)。
|
|
114
|
+
- ✨ **三重触发条件**(非单一阈值):① 绝对上限 `min(autoCompactMaxTokens,
|
|
115
|
+
percent×窗口)`;② 会话已跑 ≥ `minTurns` 轮;③ **成本划算**(用真实价表算)。
|
|
116
|
+
- 📄 **为何默认关**:压缩是“投资”——不压只付缓存读价(hit),
|
|
117
|
+
压缩需整段重算(miss−hit 价差)。DeepSeek hit=0.02 极便宜,
|
|
118
|
+
平衡点分析显示**剩 ≤30 轮时压缩永远亏**;仅长会话(≥100 轮、上下文 ≥4 万)
|
|
119
|
+
才值。GLM(hit=0.23)则明显划算。
|
|
120
|
+
- 新增 `src/context.ts`(纯函数:`decideCompact` / `compactionSaving`);
|
|
121
|
+
config/env:`autoCompact` / `autoCompactMaxTokens`(40万) /
|
|
122
|
+
`autoCompactPercent`(0.6) / `autoCompactMinTurns`(50)。
|
|
123
|
+
- `/prompt check` 显示上下文用量、触发阈值、pi 阈值、本会话压缩次数。
|
|
124
|
+
- 🧹 测试 63 → **68 全绿**;`tsc --noEmit` 0 错误。
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
## v0.4.3 (2026-09-12)
|
|
128
|
+
|
|
129
|
+
**台账加版本标记(v/pv)+ 版本考古:修正一个错误的分析结论**
|
|
130
|
+
|
|
131
|
+
- ✨ **台账新增 `v`(schema 版本)与 `pv`(插件版本)字段**(只增不改):
|
|
132
|
+
历史记录读取时补 `v=0` / `pv=legacy`(不写回文件)。目的:解决"台账跨版本
|
|
133
|
+
累积导致无法按版本归类"的问题——这是分析准确性所需的事实标记。
|
|
134
|
+
- 📄 **版本考古结论(重要修正)**:台账 1306 条**全部是 `pv=legacy`**
|
|
135
|
+
(来自 v0.1/v0.2,无版本标记),**v0.4.1+ 干净样本 = 0 条**。
|
|
136
|
+
因此之前对比面板给出的 **-19.7% 实测缩减率作废**——它是
|
|
137
|
+
"跨版本混淆 + 选择偏差"的双重假象:
|
|
138
|
+
- v0.1 无 auto、v0.2 无迟滞(振荡 bug),行为不兼容;
|
|
139
|
+
- auto 选档非随机:`normal` 组 259/284 是冷缓存(会话早期),
|
|
140
|
+
`ultra` 组 870/930 是热缓存(会话中段),组间不同质。
|
|
141
|
+
- 📄 **诚实结论**:当前台账**无法回答"auto 省没省"**;需受控 A/B
|
|
142
|
+
(`autoSample` 的用途,目前默认关)。`/prompt compare` 实测列已加
|
|
143
|
+
非因果免责声明。
|
|
144
|
+
- 🧹 新增 `scripts/usage-filter.mjs`(版本过滤)与 `scripts/analyze-versions.mjs`
|
|
145
|
+
(版本考古 + 分层对比)。
|
|
146
|
+
- 🧹 测试 61 → **63 全绿**;`tsc --noEmit` 0 错误。
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
## v0.4.2 (2026-09-12)
|
|
150
|
+
|
|
151
|
+
**footer 显示修正 + /prompt compare 对比面板**
|
|
152
|
+
|
|
153
|
+
- 🐛 **修 footer 显示歧义(用户报障)**:切换 show=auto 后 footer 只显示解析后的档位
|
|
154
|
+
(如 `normal`),看起来像 auto 设置丢了。现改为 `auto→normal`,
|
|
155
|
+
让"设置是 auto、本轮解析成 normal"一目了然。纯显示改动,不动注入字节。
|
|
156
|
+
(根因:非 pi-usager 问题,pi-usager 只是原样转发 pi-prompt 的 status。
|
|
157
|
+
参见 AGENTS.md 认知修正 #39。)
|
|
158
|
+
- ✨ **新增 `/prompt compare` 对比面板**:对比"注入 vs 不注入"的成本,
|
|
159
|
+
**双轨展示、分别标注口径**:
|
|
160
|
+
- **估算**:从台账反推"若无注入"会怎样(依赖 `REDUCTION_BY_SHOW` 先验)——
|
|
161
|
+
标注"基于先验缩减率,非实测"。
|
|
162
|
+
- **实测**:用 show=normal(不注入)样本做对照(≥5 样本才展示)——
|
|
163
|
+
标注样本偏差(normal 样本多来自小任务,非随机)。
|
|
164
|
+
- 样本不足时提示跑 `/prompt check --calibrate` 采集对照组。
|
|
165
|
+
- ✨ 新增 `src/compare.ts`(纯函数层:`compareInjection` / `renderCompare`),
|
|
166
|
+
不依赖 TUI,node 单测直接断言。
|
|
167
|
+
- 📄 **实数据发现**:本机台账实测缩减率为 **-19.7%**(注入组中位输出反而更高),
|
|
168
|
+
而估算声称省 ¥6.33——正是样本偏差所致。这就是面板必须双轨标注的原因:
|
|
169
|
+
单看估算会误以为"肯定省了钱"。
|
|
170
|
+
- 🧹 测试 54 → **61 全绿**;`tsc --noEmit` 0 错误。
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
## v0.4.1 (2026-09-12)
|
|
174
|
+
|
|
175
|
+
**缓存命中优化:auto 换档迟滞 + 三轴独立注入块(对齐 Reasonix 的 cache-first 纪律)**
|
|
176
|
+
|
|
177
|
+
- ✨ **auto 换档迟滞(核心)**:auto 选档从"无状态 argmax"改为"带记忆的迟滞"——
|
|
178
|
+
新最优档的净收益必须扣除**换档的缓存失效成本**后仍更优,才允许换档;否则维持
|
|
179
|
+
上一轮档位。换档失效成本按 `上一轮 prompt token × (miss−hit) 价差` 估算:
|
|
180
|
+
**会话越长/前缀越大,换档门槛越高**。
|
|
181
|
+
- ✨ **三轴独立注入块**:注入文案从单串 `[PI-PROMPT show|write|do]` 改为三块
|
|
182
|
+
`[PI-PROMPT:SHOW <mode>]` / `[PI-PROMPT:WRITE <mode>]` / `[PI-PROMPT:DO <mode>]`,
|
|
183
|
+
每块字节只随自己的档位变 —— 调 write/do 不再改变 show 块的字节。
|
|
184
|
+
新增 `PromptRegistry.composeBlock(axis, mode)` 供按轴拼接。
|
|
185
|
+
- ✨ **迟滞可配置**:配置 `hysteresis`(默认 0.5)/ env `PI_PROMPT_HYSTERESIS`;
|
|
186
|
+
设为 0 即退回旧的无状态 argmax(向后兼容)。
|
|
187
|
+
- ✨ **命中率与前缀可观测**:`/prompt usage` 增加缓存命中率列(按天×模型 +
|
|
188
|
+
会话累计);`/prompt check` 增加**注入前缀哈希**(学 Reasonix 的 CaptureShape,
|
|
189
|
+
一眼看出前缀有没有变)与迟滞设置;启动时提示当前配置对缓存是否友好。
|
|
190
|
+
- 📄 **实测依据(P0)**:本机台账 1055 条显示——输入占成本 75%,命中率已达 97.9%,
|
|
191
|
+
但输入成本中 miss 贡献约一半;12 次 auto 换档累计浪费 719K token(≈¥0.70,占总成本 8.3%),
|
|
192
|
+
且浪费与当时前缀大小成正比(23 万 token 前缀换一次 ≈¥0.22)。迟滞回放显示可将其全部压掉。
|
|
193
|
+
- 📄 **勘测结论**:pi 宿主已原生实现 Reasonix 的"历史只追加不改写"(session-manager
|
|
194
|
+
的 append-only tree)、"单一改写点"(compaction,pi 的 cache-stats 已把非压缩造成
|
|
195
|
+
的 miss 单独记为 cache waste)、"大内容有界化"(bash 输出截断)。pi-prompt 的注入
|
|
196
|
+
位于系统提示**最尾部**(`before_agent_start` 返回的 systemPrompt 是最终完整串,
|
|
197
|
+
追加在其末尾),故只需保证注入字节恒定。
|
|
198
|
+
- 🐛 **实跑验证发现并修复两个集成缺陷**(纯函数单测看不到):
|
|
199
|
+
- **自振荡**:换档→缓存失效→cacheState 翻冷→又换回,闭环振荡。
|
|
200
|
+
修法:switchCost **不再依赖当轮 cached**(任何换档都让下回合失效)。
|
|
201
|
+
- **三块隔离被击穿**:`join("\n")` 让最后一块少一个换行,show 块字节
|
|
202
|
+
随其后有无 write/do 而变。修法:每块自带尾部换行 + 直接拼接。
|
|
203
|
+
- 🧹 实跑验证(`scripts/verify-v041.mjs`,驱动真实扩展跑长会话):
|
|
204
|
+
迟滞关闭 23 次换档 → 迟滞开启 **0 次**(hold 标记 12 次);
|
|
205
|
+
show 块隔离成功;注入确认在 system prompt 末尾。
|
|
206
|
+
- 🧹 测试 41 → **54 全绿**(+13:缓存不变量 4 + 迟滞 5 + 命中率/哈希 3 + 启动提示 1);
|
|
207
|
+
`tsc --noEmit` 0 错误。
|
|
208
|
+
|
|
209
|
+
|
|
7
210
|
## v0.4.0 (2026-09-11)
|
|
8
211
|
|
|
9
212
|
**价格来源交给用户:auto 选档与成本全面走 pi-pricer JSON 价表(价格不再写死)**
|
package/README.md
CHANGED
|
@@ -30,7 +30,11 @@
|
|
|
30
30
|
`/prompt check --calibrate`(3 轮不注入的对照探针)。
|
|
31
31
|
- **台账与自检**。`/prompt usage` 给你按天×模型聚合 + 会话明细的 token/金额表,
|
|
32
32
|
并标注节省估算;`/prompt check` 看台账完整性、校准状态与当前价格来源。
|
|
33
|
-
-
|
|
33
|
+
- **对比面板**。`/prompt compare` 回答"插件到底省了多少"——分**估算**(基于先验缩减率)
|
|
34
|
+
与**实测**(不注入对照样本)两轨展示,各自标注口径,不让假设听起来像事实。
|
|
35
|
+
- **恒定注入语义 + 换档迟滞**。注入文案按档位编号、字节稳定,且三轴各自成块
|
|
36
|
+
(调 write/do 不动 show 块字节);auto 换档带**迟滞**——新档优势须扣掉"换档的
|
|
37
|
+
缓存失效成本"后仍更优才换,避免为省一点输出费而丢掉整段前缀缓存。
|
|
34
38
|
|
|
35
39
|
---
|
|
36
40
|
|
|
@@ -45,7 +49,8 @@ pi extension add @foolsecret/pi-pricer
|
|
|
45
49
|
/price # 打开价格面板,改成你实际付费的价
|
|
46
50
|
|
|
47
51
|
# 3. 打开三轴设置抽屉
|
|
48
|
-
/prompt config #
|
|
52
|
+
/prompt config # 设置抽屉(4 分类钻取;Ctrl+S 保存 · Ctrl+R 丢弃)
|
|
53
|
+
/prompt # 显示帮助
|
|
49
54
|
|
|
50
55
|
# 4. 看看花了多少、省了多少
|
|
51
56
|
/prompt usage
|
|
@@ -58,9 +63,11 @@ pi extension add @foolsecret/pi-pricer
|
|
|
58
63
|
| 命令 | 干嘛的 |
|
|
59
64
|
|---|---|
|
|
60
65
|
| `/prompt` | 无参 = 全局开关(关停/恢复注入) |
|
|
61
|
-
| `/prompt config` |
|
|
66
|
+
| `/prompt config` | 设置抽屉:根层 4 分类(提示词/工具/上下文/其他)钻取;**Ctrl+S 保存 / Ctrl+R 丢弃**,未保存时显示 `● 未保存改动` |
|
|
67
|
+
| `/prompt`(无参) | 显示帮助 |
|
|
62
68
|
| `/prompt status` | 当前三轴状态 |
|
|
63
69
|
| `/prompt usage` | token/金额台账(按天×模型 + 会话明细 + 节省估算) |
|
|
70
|
+
| `/prompt compare` | 对比注入 vs 不注入的成本(估算 + 实测双轨) |
|
|
64
71
|
| `/prompt check` | 自检:台账完整性 / 校准状态 / 价格来源;`--calibrate` 跑探针 |
|
|
65
72
|
| `/prompt-review` | 调用 `prompt-review` 技能审计上一条回复的冗余 |
|
|
66
73
|
|
|
@@ -79,12 +86,43 @@ pi extension add @foolsecret/pi-pricer
|
|
|
79
86
|
"defaultDo": "normal",
|
|
80
87
|
"peakUpgrade": true,
|
|
81
88
|
"maxTokensCap": null,
|
|
82
|
-
"autoSample": false
|
|
89
|
+
"autoSample": false,
|
|
90
|
+
"hysteresis": 0.5,
|
|
91
|
+
"autoCompact": false,
|
|
92
|
+
"autoCompactMaxTokens": 400000,
|
|
93
|
+
"autoCompactPercent": 0.6,
|
|
94
|
+
"autoCompactMinTurns": 50,
|
|
95
|
+
"compactInstructions": "摘要时请额外保留:当前正在进行的任务、已完成到哪一步、下一步计划、未解决的阻塞。不要只总结历史对话,要保留工作状态。",
|
|
96
|
+
"toolOutput": {
|
|
97
|
+
"enabled": false,
|
|
98
|
+
"grep": 8192,
|
|
99
|
+
"read": 16384,
|
|
100
|
+
"bash": 8192,
|
|
101
|
+
"default": 8192
|
|
102
|
+
}
|
|
83
103
|
}
|
|
84
104
|
```
|
|
85
105
|
|
|
86
106
|
环境变量(优先级高于配置):`PI_PROMPT_SHOW` / `PI_PROMPT_WRITE` / `PI_PROMPT_DO` /
|
|
87
|
-
`PI_PROMPT_PEAK` / `PI_PROMPT_MAX_TOKENS` / `PI_PROMPT_AUTO_SAMPLE
|
|
107
|
+
`PI_PROMPT_PEAK` / `PI_PROMPT_MAX_TOKENS` / `PI_PROMPT_AUTO_SAMPLE` /
|
|
108
|
+
`PI_PROMPT_HYSTERESIS` / `PI_PROMPT_AUTO_COMPACT` / `PI_PROMPT_AUTO_COMPACT_MAX_TOKENS` /
|
|
109
|
+
`PI_PROMPT_AUTO_COMPACT_PERCENT` / `PI_PROMPT_AUTO_COMPACT_MIN_TURNS` /
|
|
110
|
+
`PI_PROMPT_TOOL_OUTPUT` / `PI_PROMPT_TOOL_CAP_GREP` / `PI_PROMPT_TOOL_CAP_READ` /
|
|
111
|
+
`PI_PROMPT_TOOL_CAP_BASH` / `PI_PROMPT_TOOL_CAP_DEFAULT` / `PI_PROMPT_COMPACT_INSTRUCTIONS`。
|
|
112
|
+
|
|
113
|
+
以上配置项**均可在 `/prompt config` 抽屉里修改**(根层 4 分类钻取:
|
|
114
|
+
提示词 / 工具 / 上下文 / 其他),无需手改 JSON。
|
|
115
|
+
|
|
116
|
+
> **关于 `autoCompact`(默认关)**:pi 自带的压缩阈值是「窗口 − 16K」,在 1M 窗口
|
|
117
|
+
> 模型上 ≈ 98 万,实际很难触发,上下文会一直涨。本插件可在**更早**触发压缩,
|
|
118
|
+
> 但压缩本身有代价(整段重算一次),而缓存读很便宜——算下来只有**长会话**才划算,
|
|
119
|
+
> 所以默认关闭,仅在「超阈值 + 会话已够长 + 成本确划算」三重条件同时满足时才会压。
|
|
120
|
+
>
|
|
121
|
+
> **关于 `toolOutput`(默认关)**:pi 已把工具输出截到 50KB;本项提供**更激进**的
|
|
122
|
+
> 分层上限(grep/bash 8K、read 16K),在工具结果**创建时**截断一次(不改历史,
|
|
123
|
+
> 缓存友好)。它能省的是**每轮重发的缓存读**:以你的真实模型单价(deepseek-v4-pro
|
|
124
|
+
> hit=0.15、glm-5.3-flash hit=0.23)估算,8K 上限可省 **15~25%** 总成本;但会改变
|
|
125
|
+
> 模型看到的内容,故默认关。
|
|
88
126
|
|
|
89
127
|
历史数据兼容:旧档名 `off` 读取时自动归一为 `normal`,不影响既有台账。
|
|
90
128
|
|
package/package.json
CHANGED
|
@@ -1,57 +1,57 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
2
|
+
"name": "@foolsecret/pi-prompt",
|
|
3
|
+
"version": "0.4.9",
|
|
4
|
+
"description": "为 Pi Agent 打造的输出精简提示插件 — 多档力度压缩输出 token 省钱 | A prompt-verbosity plugin for Pi Agent — tiered output compression to save tokens & cost",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"pi-package",
|
|
7
|
+
"pi-extension",
|
|
8
|
+
"prompt",
|
|
9
|
+
"verbosity",
|
|
10
|
+
"concise",
|
|
11
|
+
"token",
|
|
12
|
+
"savings"
|
|
13
|
+
],
|
|
14
|
+
"license": "AGPL-3.0-only",
|
|
15
|
+
"author": "Titor-Z",
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/Titor-Z/pi-prompt.git"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"src/",
|
|
25
|
+
"extensions/",
|
|
26
|
+
"prompts/",
|
|
27
|
+
"README.md",
|
|
28
|
+
"CHANGELOG.md",
|
|
29
|
+
"LICENSE"
|
|
30
|
+
],
|
|
31
|
+
"scripts": {
|
|
32
|
+
"test": "node --test --test-force-exit test/*.test.mjs",
|
|
33
|
+
"typecheck": "tsc --noEmit",
|
|
34
|
+
"prepublishOnly": "npm run typecheck && npm test"
|
|
35
|
+
},
|
|
36
|
+
"pi": {
|
|
37
|
+
"extensions": [
|
|
38
|
+
"./extensions/index.ts"
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
43
|
+
"@earendil-works/pi-tui": "*",
|
|
44
|
+
"@foolsecret/pi-pricer": "^0.12.0"
|
|
45
|
+
},
|
|
46
|
+
"peerDependenciesMeta": {
|
|
47
|
+
"@foolsecret/pi-pricer": {
|
|
48
|
+
"optional": true
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
53
|
+
"@types/node": "^22.20.2",
|
|
54
|
+
"jiti": "^2.4.2",
|
|
55
|
+
"typescript": "^7.0.2"
|
|
56
|
+
}
|
|
57
57
|
}
|
package/src/auto.ts
CHANGED
|
@@ -33,6 +33,12 @@ export interface TierMathInput {
|
|
|
33
33
|
injectionTokens: (mode: RuntimeShowMode) => number;
|
|
34
34
|
/** 同类样本量(平局阈值收缩依据) */
|
|
35
35
|
sampleCount: number;
|
|
36
|
+
/**
|
|
37
|
+
* 换档的预期损失(¥):使当轮前缀缓存失效、下轮重新计费的代价。
|
|
38
|
+
* 缺省 0(向后兼容)。P0 实测:注入块位于系统提示尾,但仍是同一前缀的
|
|
39
|
+
* 一部分 —— 换档会让整段 prompt 缓存失效,损失 ∝ 前缀大小 ×(miss−hit) 价差。
|
|
40
|
+
*/
|
|
41
|
+
switchCost?: number;
|
|
36
42
|
}
|
|
37
43
|
|
|
38
44
|
/** 平局阈值的基准比例(相对最优净收益):净收益差距小于该比例视为"差不多" */
|
|
@@ -54,8 +60,29 @@ const CONSERVATISM: readonly RuntimeShowMode[] = ["normal", "lite", "full", "ult
|
|
|
54
60
|
* 2. 取净收益最高的档;
|
|
55
61
|
* 3. 在 {最优 × (1 − 平局比例)} 范围内选最保守的一档。
|
|
56
62
|
* 注意平局阈值必须是相对比例(绝对 ¥ 在微元量级上会把一切判成平局)。
|
|
63
|
+
*
|
|
64
|
+
* 迟滞(v0.4.1,缓存优化):传入 previous 上一轮实际档位 + hysteresis 迟滞比例后,
|
|
65
|
+
* 只有新最优档的净收益比"上一轮档位"高出 |bestNet|×hysteresis + switchCost 才换档;
|
|
66
|
+
* 否则维持 previous。理由(P0 实测):注入块变动会让整段前缀缓存失效,
|
|
67
|
+
* 浪费与当时前缀大小成正比(23 万 token 前缀换一次 ≈ ¥0.22)。换档必须"值得"。
|
|
68
|
+
*/
|
|
69
|
+
export function chooseAuto(
|
|
70
|
+
input: TierMathInput,
|
|
71
|
+
previous?: RuntimeShowMode,
|
|
72
|
+
hysteresis: number = 0,
|
|
73
|
+
): RuntimeShowMode {
|
|
74
|
+
return chooseAutoDetailed(input, previous, hysteresis).mode;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 选档 + 归因:mode 为最终档位;held 为 true 表示"迟滞压住了本次换档"
|
|
79
|
+
* (即无迟滞时本会切到 candidate,但被 previous 绊住)——用于台账决策标记。
|
|
57
80
|
*/
|
|
58
|
-
export function
|
|
81
|
+
export function chooseAutoDetailed(
|
|
82
|
+
input: TierMathInput,
|
|
83
|
+
previous?: RuntimeShowMode,
|
|
84
|
+
hysteresis: number = 0,
|
|
85
|
+
): { mode: RuntimeShowMode; held: boolean } {
|
|
59
86
|
const priceIn = input.cached ? input.inputHitPrice : input.inputMissPrice;
|
|
60
87
|
const nets = new Map<RuntimeShowMode, number>();
|
|
61
88
|
let bestNet = Number.NEGATIVE_INFINITY;
|
|
@@ -68,11 +95,22 @@ export function chooseAuto(input: TierMathInput): RuntimeShowMode {
|
|
|
68
95
|
if (net > bestNet) bestNet = net;
|
|
69
96
|
}
|
|
70
97
|
const tie = Math.abs(bestNet) * tieFraction(input.sampleCount);
|
|
98
|
+
let candidate: RuntimeShowMode = "normal";
|
|
71
99
|
for (const mode of CONSERVATISM) {
|
|
72
100
|
// 升序遍历:第一个进入平局带的档 = 最保守的接近最优
|
|
73
|
-
if ((nets.get(mode) ?? Number.NEGATIVE_INFINITY) >= bestNet - tie)
|
|
101
|
+
if ((nets.get(mode) ?? Number.NEGATIVE_INFINITY) >= bestNet - tie) {
|
|
102
|
+
candidate = mode;
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
74
105
|
}
|
|
75
|
-
return
|
|
106
|
+
if (previous === undefined || previous === candidate) return { mode: candidate, held: false };
|
|
107
|
+
// 迟滞:换档要扣掉"缓存失效损失"(switchCost,¥)后仍更优才换。
|
|
108
|
+
// 这是有原则的门槛(而非拍脑袋的固定比例):会话越长/前缀越大,换档越贵。
|
|
109
|
+
const prevNet = nets.get(previous) ?? Number.NEGATIVE_INFINITY;
|
|
110
|
+
const switchCost = input.switchCost ?? 0;
|
|
111
|
+
const margin = Math.abs(bestNet) * hysteresis + switchCost;
|
|
112
|
+
if (bestNet - prevNet <= margin) return { mode: previous, held: true };
|
|
113
|
+
return { mode: candidate, held: false };
|
|
76
114
|
}
|
|
77
115
|
|
|
78
116
|
/** 请求规模 → 预期输出 µ 的粗糙分桶(先验;可被校准的 normal 基线中位数覆盖) */
|
|
@@ -118,6 +156,10 @@ export class AutoTierChooser {
|
|
|
118
156
|
* 峰谷不再由调用方按硬编码时段推算,而是取价格源返回的 isPeak —— 这样用户在
|
|
119
157
|
* pi-pricer 配置里改了峰时段,auto 立即跟随(兜底价格源才退回 DeepSeek 固定时段)。
|
|
120
158
|
* peakUpgrade=false 时忽略 isPeak(关闭高峰升档,保持基线定价)。
|
|
159
|
+
*
|
|
160
|
+
* 迟滞(v0.4.1):previous 为上一轮实际生效档位;hysteresis 为迟滞比例;
|
|
161
|
+
* prevPromptTokens 为上一轮 prompt 总 token(用于估算换档的缓存失效成本)。
|
|
162
|
+
* 默认缺省 → 退回无状态纯 argmax,与旧行为一致。
|
|
121
163
|
*/
|
|
122
164
|
choose(
|
|
123
165
|
provider: string,
|
|
@@ -127,20 +169,74 @@ export class AutoTierChooser {
|
|
|
127
169
|
cached: boolean,
|
|
128
170
|
ts: number,
|
|
129
171
|
peakUpgrade: boolean = true,
|
|
172
|
+
previous?: RuntimeShowMode,
|
|
173
|
+
hysteresis: number = 0,
|
|
174
|
+
prevPromptTokens: number = 0,
|
|
130
175
|
): RuntimeShowMode {
|
|
176
|
+
const prices = this.priceFor(provider, model, ts);
|
|
177
|
+
const sampleCount = this.sampleCountFor(provider, bin);
|
|
178
|
+
const peek = peakUpgrade && prices.isPeak === true;
|
|
179
|
+
const outputPrice = peek ? prices.output * 2 : prices.output;
|
|
180
|
+
// 换档代价:任何换档都会让注入块变→整段前缀缓存失效,下一回合重计费。
|
|
181
|
+
// **不依赖当前 cached 标志**:P0 实测发现"换档→缓存失效→cacheRead=0→
|
|
182
|
+
// cacheState 翻冷→又换回"的自振荡循环,根因就是只在本轮 cached 时
|
|
183
|
+
// 才算 switchCost。改为只要有前缀(prevPromptTokens>0)就计代价。
|
|
184
|
+
// hysteresis<=0 视为完全关闭(含 switchCost),保证向后兼容纯 argmax。
|
|
185
|
+
const switchCost = hysteresis > 0 ? (prevPromptTokens * (prices.miss - prices.hit)) / 1e6 : 0;
|
|
186
|
+
return chooseAuto(
|
|
187
|
+
{
|
|
188
|
+
estMu,
|
|
189
|
+
outputPrice,
|
|
190
|
+
inputMissPrice: prices.miss,
|
|
191
|
+
inputHitPrice: prices.hit,
|
|
192
|
+
cached,
|
|
193
|
+
reduction: (mode) => this.reductionFor(provider, bin, mode),
|
|
194
|
+
injectionTokens: (mode) => INJECTION_TOKENS_BY_SHOW[mode],
|
|
195
|
+
sampleCount,
|
|
196
|
+
switchCost,
|
|
197
|
+
},
|
|
198
|
+
previous,
|
|
199
|
+
hysteresis,
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* 同 choose,但额外返回"迟滞是否压住了换档"(held)—— 供台账决策归因。
|
|
205
|
+
* 与 choose 共用同一套数学,不重复实现。
|
|
206
|
+
*/
|
|
207
|
+
chooseDetailed(
|
|
208
|
+
provider: string,
|
|
209
|
+
model: string,
|
|
210
|
+
bin: TaskBin,
|
|
211
|
+
estMu: number,
|
|
212
|
+
cached: boolean,
|
|
213
|
+
ts: number,
|
|
214
|
+
peakUpgrade: boolean = true,
|
|
215
|
+
previous?: RuntimeShowMode,
|
|
216
|
+
hysteresis: number = 0,
|
|
217
|
+
prevPromptTokens: number = 0,
|
|
218
|
+
): { mode: RuntimeShowMode; held: boolean } {
|
|
131
219
|
const prices = this.priceFor(provider, model, ts);
|
|
132
220
|
const sampleCount = this.sampleCountFor(provider, bin);
|
|
133
221
|
const peak = peakUpgrade && prices.isPeak === true;
|
|
134
222
|
const outputPrice = peak ? prices.output * 2 : prices.output;
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
223
|
+
// 与 choose 同源:任何换档都让前缀缓存失效,代价按前缀大小计(不依赖 cached)。
|
|
224
|
+
// hysteresis<=0 时归零(关闭迟滞即纯 argmax)。
|
|
225
|
+
const switchCost = hysteresis > 0 ? (prevPromptTokens * (prices.miss - prices.hit)) / 1e6 : 0;
|
|
226
|
+
return chooseAutoDetailed(
|
|
227
|
+
{
|
|
228
|
+
estMu,
|
|
229
|
+
outputPrice,
|
|
230
|
+
inputMissPrice: prices.miss,
|
|
231
|
+
inputHitPrice: prices.hit,
|
|
232
|
+
cached,
|
|
233
|
+
reduction: (mode) => this.reductionFor(provider, bin, mode),
|
|
234
|
+
injectionTokens: (mode) => INJECTION_TOKENS_BY_SHOW[mode],
|
|
235
|
+
sampleCount,
|
|
236
|
+
switchCost,
|
|
237
|
+
},
|
|
238
|
+
previous,
|
|
239
|
+
hysteresis,
|
|
240
|
+
);
|
|
145
241
|
}
|
|
146
242
|
}
|
package/src/command.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* /prompt config 打开三轴设置抽屉(复刻 /settings:会话档 + 默认档)
|
|
7
7
|
* /prompt status 显示三轴现状与台账估算
|
|
8
8
|
* /prompt usage 显示 token/金额台账(DeepSeek 风格表)
|
|
9
|
+
* /prompt compare 对比“注入 vs 不注入”的成本(估算 + 实测对照)
|
|
9
10
|
* /prompt check [--calibrate] 健康自检;--calibrate 才跑成对探针并重拟合
|
|
10
11
|
* 其余输入一律判非法(提示用 /prompt config)。
|
|
11
12
|
* review 不再作为档位,请用 /prompt-review 技能
|
|
@@ -18,10 +19,11 @@ export type AxisName = "show" | "write" | "do";
|
|
|
18
19
|
|
|
19
20
|
/** 解析结果的判别联合 */
|
|
20
21
|
export type PromptCommand =
|
|
21
|
-
| { type: "
|
|
22
|
+
| { type: "help" }
|
|
22
23
|
| { type: "config" }
|
|
23
24
|
| { type: "status" }
|
|
24
25
|
| { type: "usage" }
|
|
26
|
+
| { type: "compare" }
|
|
25
27
|
| { type: "check"; calibrate: boolean }
|
|
26
28
|
| { type: "invalid"; arg: string };
|
|
27
29
|
|
|
@@ -35,13 +37,19 @@ export function parsePromptCommand(args: string): PromptCommand {
|
|
|
35
37
|
|
|
36
38
|
switch (primary) {
|
|
37
39
|
case "":
|
|
38
|
-
return { type: "
|
|
40
|
+
return { type: "help" };
|
|
41
|
+
case "help":
|
|
42
|
+
return { type: "help" };
|
|
43
|
+
case "toggle":
|
|
44
|
+
return { type: "invalid", arg: "toggle 已移除,请用 /prompt config 抽屉" };
|
|
39
45
|
case "config":
|
|
40
46
|
return { type: "config" };
|
|
41
47
|
case "status":
|
|
42
48
|
return { type: "status" };
|
|
43
49
|
case "usage":
|
|
44
50
|
return { type: "usage" };
|
|
51
|
+
case "compare":
|
|
52
|
+
return { type: "compare" };
|
|
45
53
|
case "check":
|
|
46
54
|
return { type: "check", calibrate: parts.includes("--calibrate") };
|
|
47
55
|
default:
|