pi-thinking-header 0.3.4 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -9
- package/index.js +162 -23
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -42,17 +42,26 @@ pi install ./pi-thinking-header
|
|
|
42
42
|
所有功能的统一入口:
|
|
43
43
|
|
|
44
44
|
```
|
|
45
|
-
/thinking-header # 状态总览(模式/补丁状态/decimals)+ 用法
|
|
45
|
+
/thinking-header # 状态总览(模式/补丁状态/autoPatch/decimals)+ 用法
|
|
46
46
|
/thinking-header patch # 应用/修复完整模式补丁(确认后执行)
|
|
47
|
+
/thinking-header autopatch # 查看启动自动补丁状态
|
|
48
|
+
/thinking-header autopatch on|off# 启动时自动检查并应用/迁移补丁(默认 on)
|
|
47
49
|
/thinking-header decimals # 查看当前小数位
|
|
48
50
|
/thinking-header decimals <0-6> # 设置小数位并持久化(默认 2)
|
|
49
51
|
```
|
|
50
52
|
|
|
53
|
+
启动自动补丁(`autopatch`,默认开启):每次会话启动(`session_start`)自动探测磁盘上的
|
|
54
|
+
bundle——未打补丁时自动应用、检测到旧版本 patch 时自动迁移(installer 自带备份恢复逻辑),
|
|
55
|
+
已是最新的则静默跳过。补丁写入磁盘后需重启 pi 才生效:当前会话仍用降级/旧渲染,notify
|
|
56
|
+
会提示。因此 `pi update` 之后补丁丢失、或插件包更新带来新 patch 版本时,都无需手动
|
|
57
|
+
处理,下次启动自动修复。关闭方式:`/thinking-header autopatch off`,或直接编辑配置文件
|
|
58
|
+
(`{"autoPatch": false}`)。
|
|
59
|
+
|
|
51
60
|
补丁安装(`patch` 子命令,内部运行 `node install-patch.mjs`):
|
|
52
61
|
|
|
53
|
-
-
|
|
54
|
-
-
|
|
55
|
-
- `pi update` 之后补丁丢失时,extension
|
|
62
|
+
- 已是当前版本 → 提示 already applied(含当前 decimals)
|
|
63
|
+
- 未打补丁(或补丁版本较旧 → 迁移)→ 确认后执行安装/迁移,输出结果并提醒重启 pi(当前会话仍是旧代码,重启后生效)
|
|
64
|
+
- `pi update` 之后补丁丢失时,extension 自动进入降级模式;启动自动补丁(默认开)会在下次会话开始时自动修复,也可用该命令立即修复
|
|
56
65
|
|
|
57
66
|
小数位配置(`decimals` 子命令):
|
|
58
67
|
|
|
@@ -67,7 +76,8 @@ pi install ./pi-thinking-header
|
|
|
67
76
|
|
|
68
77
|
pi 的 extension API 无法接管内置 thinking 块的折叠渲染,完整效果需要附带
|
|
69
78
|
的 bundle patch。推荐直接在 pi 会话内运行 `/thinking-header patch`
|
|
70
|
-
|
|
79
|
+
(自动定位安装位置);也可什么都不做——默认开启的启动自动补丁会在首次
|
|
80
|
+
会话开始时自动应用。手动执行:
|
|
71
81
|
|
|
72
82
|
```bash
|
|
73
83
|
# npm 安装:~/.pi/agent/npm/pi-thinking-header/install-patch.mjs
|
|
@@ -84,8 +94,9 @@ node <包目录>/install-patch.mjs
|
|
|
84
94
|
- 小数点位数渲染时实时读取配置文件(mtime 缓存),与降级模式共享同一份配置
|
|
85
95
|
- 幂等;自动从 v1/v2/v3/v3.1/v3.2 patch 迁移(恢复原始备份后重新应用;早期 v3
|
|
86
96
|
折叠组件缺 `invalidate()`、退出/切换模式时触发 `this.child.invalidate is not
|
|
87
|
-
a function` 崩溃且无备份时,会先原地热修复);`pi update`
|
|
88
|
-
|
|
97
|
+
a function` 崩溃且无备份时,会先原地热修复);`pi update` 后无需手动处理——
|
|
98
|
+
启动自动补丁(默认开)会在下次会话开始时重新应用/迁移;插件包更新后同样如此
|
|
99
|
+
(patch 版本随插件更新时自动迁移到新版)
|
|
89
100
|
- 回滚:
|
|
90
101
|
```bash
|
|
91
102
|
cp <chunk路径>.pre-thinking-header.bak <chunk路径>
|
|
@@ -95,8 +106,9 @@ node <包目录>/install-patch.mjs
|
|
|
95
106
|
### 纯插件模式:extension 自动降级
|
|
96
107
|
|
|
97
108
|
extension 启动时探测运行中的 pi bundle:**检测到任意版本的 patch 时**(含旧版本)
|
|
98
|
-
extension
|
|
99
|
-
|
|
109
|
+
extension 保持惰性(避免双标题;旧版本会在下次会话启动时被自动迁移到当前版本,
|
|
110
|
+
或运行 `/thinking-header patch` 立即迁移);**未检测到 patch 时**自动进入降级模式,
|
|
111
|
+
并在会话启动时自动应用补丁(默认行为,可关闭):
|
|
100
112
|
|
|
101
113
|
- 展开的 thinking:通过 markdown transformer 前置 `Thinking (~N tokens)` 标题行
|
|
102
114
|
(按块正确计数,`pi update` 后依然有效,无需维护)。流式期间同样保持稳定
|
package/index.js
CHANGED
|
@@ -36,6 +36,14 @@
|
|
|
36
36
|
* older transcript messages will show the most recent label. Run
|
|
37
37
|
* /thinking-header patch for per-message fidelity.
|
|
38
38
|
*
|
|
39
|
+
* Startup auto-patch (autoPatch, default on): every session_start re-probes
|
|
40
|
+
* the bundle — unpatched → apply the patch, older family version → migrate —
|
|
41
|
+
* so a lost patch after `pi update` (or a newer extension release) heals on
|
|
42
|
+
* the next launch with zero manual steps. The patched file matters for the
|
|
43
|
+
* NEXT restart; the running session keeps its fallback (or old-patch)
|
|
44
|
+
* renderer and a notify explains that. Disable: /thinking-header autopatch
|
|
45
|
+
* off, or {"autoPatch": false} in the config file.
|
|
46
|
+
*
|
|
39
47
|
* Token counts are local estimates: ceil(chars / 4). The k-token decimal places
|
|
40
48
|
* are configurable (default 2): /thinking-header decimals <0-6>, persisted to
|
|
41
49
|
* <agentDir>/thinking-header.json ({"decimals": 2}). Both modes re-read the
|
|
@@ -56,9 +64,16 @@ const INSTALLER_PATH = fileURLToPath(new URL("./install-patch.mjs", import.meta.
|
|
|
56
64
|
|
|
57
65
|
// Family-wide marker: ANY patch version owns header rendering end-to-end, so
|
|
58
66
|
// the extension must stay inert for all of them (avoids double headers). The
|
|
59
|
-
// /thinking-header patch
|
|
67
|
+
// /thinking-header patch (and the startup auto-patch) migrates older patch
|
|
68
|
+
// versions to the current one.
|
|
60
69
|
const MARKER = "pi-thinking-header";
|
|
61
70
|
|
|
71
|
+
// Current patch version — keep in sync with CURRENT_MARKER in
|
|
72
|
+
// install-patch.mjs. A disk bundle carrying only an older family version is
|
|
73
|
+
// "patched" (extension stays inert) but not "current" (auto-migrates).
|
|
74
|
+
const PATCH_VERSION = "v3.3";
|
|
75
|
+
const CURRENT_MARKER = MARKER + ":" + PATCH_VERSION;
|
|
76
|
+
|
|
62
77
|
// ---------------- decimals config (shared with the bundle patch) -------------
|
|
63
78
|
// <agentDir>/thinking-header.json: { "decimals": 2 } — k-token decimal places.
|
|
64
79
|
// The bundle patch reads the same file at render time (mtime-cached), so both
|
|
@@ -100,7 +115,10 @@ const getDecimals = () => {
|
|
|
100
115
|
return typeof d === "number" && Number.isInteger(d) && d >= 0 && d <= 6 ? d : DEFAULT_DECIMALS;
|
|
101
116
|
};
|
|
102
117
|
|
|
103
|
-
|
|
118
|
+
// Startup auto-patch: only an explicit `false` disables it (absent key = on).
|
|
119
|
+
const getAutoPatch = () => loadConfig().autoPatch !== false;
|
|
120
|
+
|
|
121
|
+
function saveConfigKey(key, value) {
|
|
104
122
|
let cfg = {};
|
|
105
123
|
try {
|
|
106
124
|
const parsed = JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
|
|
@@ -108,7 +126,7 @@ function saveDecimals(n) {
|
|
|
108
126
|
} catch {
|
|
109
127
|
// no/invalid config → start fresh, keep nothing
|
|
110
128
|
}
|
|
111
|
-
cfg
|
|
129
|
+
cfg[key] = value;
|
|
112
130
|
mkdirSync(dirname(CONFIG_PATH), { recursive: true });
|
|
113
131
|
writeFileSync(CONFIG_PATH, JSON.stringify(cfg, null, 2) + "\n");
|
|
114
132
|
configCache.mtime = -1n; // force re-read on next loadConfig()
|
|
@@ -119,7 +137,9 @@ const COMPONENT_ANCHOR =
|
|
|
119
137
|
|
|
120
138
|
/**
|
|
121
139
|
* Locate the running pi's bundle chunk that contains AssistantMessageComponent.
|
|
122
|
-
* Returns { found: true, patched
|
|
140
|
+
* Returns { found: true, patched, current } or { found: false }:
|
|
141
|
+
* - patched: ANY pi-thinking-header family version is on disk
|
|
142
|
+
* - current: exactly the version this extension ships (PATCH_VERSION)
|
|
123
143
|
*/
|
|
124
144
|
function probeBundle() {
|
|
125
145
|
try {
|
|
@@ -134,7 +154,11 @@ function probeBundle() {
|
|
|
134
154
|
const path = join(chunksDir, file);
|
|
135
155
|
const src = readFileSync(path, "utf8");
|
|
136
156
|
if (!src.includes(COMPONENT_ANCHOR)) continue;
|
|
137
|
-
return {
|
|
157
|
+
return {
|
|
158
|
+
found: true,
|
|
159
|
+
patched: src.includes(MARKER),
|
|
160
|
+
current: src.includes(CURRENT_MARKER),
|
|
161
|
+
};
|
|
138
162
|
}
|
|
139
163
|
} catch {
|
|
140
164
|
// pi binary not resolvable — fall through
|
|
@@ -210,10 +234,58 @@ const thinkingLabel = (blocks, running) => {
|
|
|
210
234
|
return label;
|
|
211
235
|
};
|
|
212
236
|
|
|
237
|
+
/**
|
|
238
|
+
* Startup auto-patch: on session_start, re-probe the bundle and run the
|
|
239
|
+
* installer when the disk is unpatched (apply) or carries an older family
|
|
240
|
+
* version (migrate). The patched file only matters for the NEXT pi start —
|
|
241
|
+
* the running session keeps whichever renderer it booted with — so the
|
|
242
|
+
* notify says so. Best-effort by design: failures degrade to the manual
|
|
243
|
+
* /thinking-header patch command, never to a broken startup.
|
|
244
|
+
*/
|
|
245
|
+
function registerAutoPatch(pi) {
|
|
246
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
247
|
+
try {
|
|
248
|
+
if (!getAutoPatch()) return;
|
|
249
|
+
const before = probeBundle();
|
|
250
|
+
if (!before.found || before.current) return; // up to date / not resolvable
|
|
251
|
+
const migrating = before.patched; // older family version on disk
|
|
252
|
+
const { stdout, stderr } = await execFileAsync(
|
|
253
|
+
process.execPath,
|
|
254
|
+
[INSTALLER_PATH],
|
|
255
|
+
{ timeout: 30_000, windowsHide: true, maxBuffer: 1024 * 1024 },
|
|
256
|
+
);
|
|
257
|
+
if (probeBundle().current) {
|
|
258
|
+
ctx.ui.notify(
|
|
259
|
+
`pi-thinking-header: bundle patch ${migrating ? "migrated to" : "applied"} (${PATCH_VERSION}) at startup — ` +
|
|
260
|
+
"effective after the next restart; this session keeps its current renderer.",
|
|
261
|
+
"info",
|
|
262
|
+
);
|
|
263
|
+
} else {
|
|
264
|
+
const out = (stdout + "\n" + stderr).trim();
|
|
265
|
+
ctx.ui.notify(
|
|
266
|
+
"pi-thinking-header: startup auto-patch had no effect.\n" +
|
|
267
|
+
(out || "(no installer output)") +
|
|
268
|
+
"\nRun /thinking-header patch for details.",
|
|
269
|
+
"warning",
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
} catch (err) {
|
|
273
|
+
const detail = [err?.message, err?.stdout, err?.stderr].filter(Boolean).join("\n");
|
|
274
|
+
ctx.ui.notify(
|
|
275
|
+
"pi-thinking-header: startup auto-patch failed.\n" +
|
|
276
|
+
detail +
|
|
277
|
+
"\nRun /thinking-header patch to apply manually.",
|
|
278
|
+
"warning",
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
|
|
213
284
|
export default function (pi) {
|
|
214
285
|
// /thinking-header — single entry point for everything this package does:
|
|
215
286
|
// /thinking-header status overview + usage
|
|
216
287
|
// /thinking-header patch apply/repair the full-mode bundle patch
|
|
288
|
+
// /thinking-header autopatch on|off startup auto-check+apply (default on)
|
|
217
289
|
// /thinking-header decimals show the k-token decimal places
|
|
218
290
|
// /thinking-header decimals <0-6> set + persist them (0-6, default 2)
|
|
219
291
|
// Registered in BOTH modes. Fallback mode re-reads the config per label; the
|
|
@@ -223,21 +295,38 @@ export default function (pi) {
|
|
|
223
295
|
"usage:\n" +
|
|
224
296
|
" /thinking-header status overview\n" +
|
|
225
297
|
" /thinking-header patch apply/repair the full-mode patch\n" +
|
|
298
|
+
" /thinking-header autopatch on|off startup auto-check+apply (default on)\n" +
|
|
226
299
|
" /thinking-header decimals show k-token decimal places\n" +
|
|
227
300
|
" /thinking-header decimals <0-6> set them (persisted, applies live)";
|
|
228
301
|
|
|
229
302
|
pi.registerCommand("thinking-header", {
|
|
230
|
-
description:
|
|
231
|
-
"pi-thinking-header: status / `patch` apply-repair full-mode patch / `decimals <0-6>` token-count decimals",
|
|
303
|
+
description: "Thinking display settings: status, patch, autopatch, decimals",
|
|
232
304
|
getArgumentCompletions: (prefix) => {
|
|
233
305
|
const p = (prefix ?? "").trimStart();
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
306
|
+
const tokens = p.split(/\s+/);
|
|
307
|
+
// Fully typed token → close the menu so Enter SUBMITS the command:
|
|
308
|
+
// pi's editor consumes Enter to accept an open completion (argument
|
|
309
|
+
// prefixes don't start with "/", so accept-without-submit applies), and
|
|
310
|
+
// an exact-match item would re-open the menu on every re-query — Enter
|
|
311
|
+
// would be swallowed repeatedly and the command would never run.
|
|
312
|
+
if (tokens.length === 1) {
|
|
313
|
+
const items = [
|
|
314
|
+
{ value: "patch", label: "apply/repair the full-mode bundle patch" },
|
|
315
|
+
{ value: "autopatch", label: "startup auto-apply/migrate the patch (on/off)" },
|
|
316
|
+
{ value: "decimals", label: "k-token decimal places (0-6, default 2)" },
|
|
317
|
+
];
|
|
318
|
+
const filtered = items.filter((i) => i.value.startsWith(tokens[0]));
|
|
319
|
+
if (filtered.length === 0) return null;
|
|
320
|
+
if (filtered.length === 1 && filtered[0].value === tokens[0]) return null;
|
|
321
|
+
return filtered;
|
|
322
|
+
}
|
|
323
|
+
if (tokens.length === 2 && tokens[0] === "autopatch") {
|
|
324
|
+
const opts = ["on", "off"].filter((o) => o.startsWith(tokens[1]));
|
|
325
|
+
if (opts.length === 0) return null;
|
|
326
|
+
if (opts.length === 1 && opts[0] === tokens[1]) return null;
|
|
327
|
+
return opts.map((o) => ({ value: o, label: `startup auto-patch: ${o}` }));
|
|
328
|
+
}
|
|
329
|
+
return null; // decimals <0-6>: single-digit values never need completion
|
|
241
330
|
},
|
|
242
331
|
handler: async (args, ctx) => {
|
|
243
332
|
const argv = (args ?? "").trim().split(/\s+/).filter(Boolean);
|
|
@@ -260,7 +349,7 @@ export default function (pi) {
|
|
|
260
349
|
return;
|
|
261
350
|
}
|
|
262
351
|
try {
|
|
263
|
-
|
|
352
|
+
saveConfigKey("decimals", n);
|
|
264
353
|
} catch (err) {
|
|
265
354
|
ctx.ui.notify(`pi-thinking-header: failed to save ${CONFIG_PATH}: ${err?.message ?? err}`, "error");
|
|
266
355
|
return;
|
|
@@ -273,6 +362,42 @@ export default function (pi) {
|
|
|
273
362
|
return;
|
|
274
363
|
}
|
|
275
364
|
|
|
365
|
+
// ---- autopatch subcommand: startup auto-check+apply toggle --------
|
|
366
|
+
if (argv[0] === "autopatch") {
|
|
367
|
+
const raw = argv[1];
|
|
368
|
+
if (raw === undefined) {
|
|
369
|
+
ctx.ui.notify(
|
|
370
|
+
`pi-thinking-header: autoPatch = ${getAutoPatch() ? "on" : "off"} (default on)\n` +
|
|
371
|
+
"session-start check: applies the patch when missing, migrates old versions\n" +
|
|
372
|
+
`config: ${CONFIG_PATH}\n` +
|
|
373
|
+
"usage: /thinking-header autopatch on|off",
|
|
374
|
+
"info",
|
|
375
|
+
);
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
if (raw !== "on" && raw !== "off") {
|
|
379
|
+
ctx.ui.notify('pi-thinking-header: autopatch expects "on" or "off"', "error");
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
try {
|
|
383
|
+
saveConfigKey("autoPatch", raw === "on");
|
|
384
|
+
} catch (err) {
|
|
385
|
+
ctx.ui.notify(
|
|
386
|
+
`pi-thinking-header: failed to save ${CONFIG_PATH}: ${err?.message ?? err}`,
|
|
387
|
+
"error",
|
|
388
|
+
);
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
ctx.ui.notify(
|
|
392
|
+
`pi-thinking-header: autoPatch = ${raw} → saved to ${CONFIG_PATH}\n` +
|
|
393
|
+
(raw === "on"
|
|
394
|
+
? "the next session start applies/migrates the patch automatically"
|
|
395
|
+
: "startup auto-patch disabled — use /thinking-header patch to apply manually"),
|
|
396
|
+
"info",
|
|
397
|
+
);
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
|
|
276
401
|
// ---- patch subcommand: apply/repair the full-mode bundle patch ----
|
|
277
402
|
if (argv[0] === "patch") {
|
|
278
403
|
try {
|
|
@@ -284,9 +409,9 @@ export default function (pi) {
|
|
|
284
409
|
);
|
|
285
410
|
return;
|
|
286
411
|
}
|
|
287
|
-
if (state.
|
|
412
|
+
if (state.current) {
|
|
288
413
|
ctx.ui.notify(
|
|
289
|
-
`pi-thinking-header: full-mode patch already applied (
|
|
414
|
+
`pi-thinking-header: full-mode patch already applied (${PATCH_VERSION}) — decimals = ${getDecimals()}`,
|
|
290
415
|
"info",
|
|
291
416
|
);
|
|
292
417
|
return;
|
|
@@ -322,12 +447,15 @@ export default function (pi) {
|
|
|
322
447
|
const state = probeBundle();
|
|
323
448
|
const mode = !state.found
|
|
324
449
|
? "fallback (pi installation not detected)"
|
|
325
|
-
: state.
|
|
326
|
-
?
|
|
327
|
-
:
|
|
450
|
+
: state.current
|
|
451
|
+
? `full (bundle patch ${PATCH_VERSION} applied)`
|
|
452
|
+
: state.patched
|
|
453
|
+
? "full (older patch on disk — auto-migrates at next session start, or /thinking-header patch now)"
|
|
454
|
+
: "fallback (bundle unpatched — auto-patches at next session start, or /thinking-header patch now)";
|
|
328
455
|
ctx.ui.notify(
|
|
329
456
|
"pi-thinking-header\n" +
|
|
330
457
|
`mode: ${mode}\n` +
|
|
458
|
+
`autoPatch: ${getAutoPatch() ? "on" : "off"} (default on)\n` +
|
|
331
459
|
`decimals: ${getDecimals()} (default ${DEFAULT_DECIMALS})\n` +
|
|
332
460
|
`config: ${CONFIG_PATH}\n` +
|
|
333
461
|
usage,
|
|
@@ -337,10 +465,21 @@ export default function (pi) {
|
|
|
337
465
|
});
|
|
338
466
|
|
|
339
467
|
const bundle = probeBundle();
|
|
468
|
+
if (bundle.found && bundle.current) {
|
|
469
|
+
// FULL MODE, up to date: the bundle patch owns rendering end-to-end
|
|
470
|
+
// (per-message labels, width-aware single-line previews). Stay inert — a
|
|
471
|
+
// global label update here would corrupt the patch's per-block counts.
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Not current on disk (unpatched, or an older family version): register the
|
|
476
|
+
// startup auto-patch. It lands on disk for the NEXT restart; this session
|
|
477
|
+
// keeps whichever renderer it booted with.
|
|
478
|
+
registerAutoPatch(pi);
|
|
479
|
+
|
|
340
480
|
if (bundle.found && bundle.patched) {
|
|
341
|
-
//
|
|
342
|
-
//
|
|
343
|
-
// would corrupt the patch's per-block counts.
|
|
481
|
+
// An older family patch still owns rendering for THIS session → stay
|
|
482
|
+
// inert to avoid double headers; the auto-patch migrates it (above).
|
|
344
483
|
return;
|
|
345
484
|
}
|
|
346
485
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-thinking-header",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "dsh-style thinking display for pi: one-line header with token count (configurable decimals, default 2); collapsed thinking shows a single-line content preview",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|