pi-think-anchor 1.0.2 → 1.0.4
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 +4 -3
- package/index.ts +145 -42
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
## 来源与借鉴
|
|
6
6
|
|
|
7
|
-
蒸馏自 [dsh-anchored-monitor](https://dsh-plugin.org/zh/plugins/aik358/dsh-anchored-monitor) 0.3.1(DSH
|
|
7
|
+
蒸馏自 [dsh-anchored-monitor](https://dsh-plugin.org/zh/plugins/aik358/dsh-anchored-monitor) 0.3.1(DSH 生态):机制思想来源,非代码移植(回合级而非流内实时、无独立进程)。判据经 6151 条日志三次校准:**v2.3 = 单轮证据门槛 + α 平滑专注/发散比**(α=1;react <0.5 / mixed <1.2 / spec ≥1.2)。命中不足的轮次不评分(避免把「长而散」判成反复试探),回放 react 11.3% → 3.4%。
|
|
8
8
|
|
|
9
9
|
## 功能
|
|
10
10
|
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
| 项 | 默认 | 说明 |
|
|
20
20
|
| --- | --- | --- |
|
|
21
21
|
| 启用 | 开 | `THINK_ANCHOR=0` 或配置文件 `enabled: false` 关闭 |
|
|
22
|
-
| 波段阈值 | 1.2 / 0.5 |
|
|
22
|
+
| 波段阈值 | 1.2 / 0.5 | α 平滑比率:高专注 ≥1.2;发散 <0.5 |
|
|
23
|
+
| 证据门槛 | 3 命中/轮 | 本轮词频命中不足 3 个 → 不评分、不干预 |
|
|
23
24
|
| 聚合窗口 | 5 轮 | 滑动窗口 |
|
|
24
25
|
| L1 冷却 | 10 分钟 | 首次跌入发散 |
|
|
25
26
|
| L2 冷却 | 15 分钟 | 持续发散,会话上限 2 次 |
|
|
@@ -28,7 +29,7 @@
|
|
|
28
29
|
## 安装
|
|
29
30
|
|
|
30
31
|
```bash
|
|
31
|
-
pi install npm:pi-think-anchor
|
|
32
|
+
pi install npm:pi-think-anchor
|
|
32
33
|
pi install ./pi-think-anchor
|
|
33
34
|
```
|
|
34
35
|
|
package/index.ts
CHANGED
|
@@ -31,7 +31,7 @@ interface TAConfig {
|
|
|
31
31
|
windowSize: number;
|
|
32
32
|
bands: { spec: number; react: number };
|
|
33
33
|
/** v2 主判据:证据门槛 + 发散词占比(与长度无关) */
|
|
34
|
-
score: { minHits: number;
|
|
34
|
+
score: { minHits: number; spec: number; react: number; alpha: number };
|
|
35
35
|
weights: { beta: number; eps: number };
|
|
36
36
|
intervention: { l1CooldownMin: number; l2CooldownMin: number; maxL2: number };
|
|
37
37
|
}
|
|
@@ -41,7 +41,7 @@ const DEF_CFG: TAConfig = {
|
|
|
41
41
|
lexicon: { positive: DEF_POS, negative: DEF_NEG, neutral: DEF_NEU },
|
|
42
42
|
windowSize: 5,
|
|
43
43
|
bands: { spec: 1.2, react: 0.5 },
|
|
44
|
-
score: { minHits: 3,
|
|
44
|
+
score: { minHits: 3, spec: 1.2, react: 0.5, alpha: 1 },
|
|
45
45
|
weights: { beta: 0.4, eps: 0.5 },
|
|
46
46
|
intervention: { l1CooldownMin: 10, l2CooldownMin: 15, maxL2: 2 },
|
|
47
47
|
};
|
|
@@ -58,6 +58,8 @@ function loadCfg(): TAConfig {
|
|
|
58
58
|
...DEF_CFG,
|
|
59
59
|
...raw,
|
|
60
60
|
lexicon: { ...DEF_CFG.lexicon, ...(raw.lexicon ?? {}) },
|
|
61
|
+
// score 是 v2 判据;未配置 score 时兼容旧 bands(否则改 bands 静默无效)
|
|
62
|
+
score: { ...DEF_CFG.score, ...(raw.score ?? {}), ...(raw.score ? {} : (raw.bands ?? {})) },
|
|
61
63
|
bands: { ...DEF_CFG.bands, ...(raw.bands ?? {}) },
|
|
62
64
|
weights: { ...DEF_CFG.weights, ...(raw.weights ?? {}) },
|
|
63
65
|
intervention: { ...DEF_CFG.intervention, ...(raw.intervention ?? {}) },
|
|
@@ -103,15 +105,17 @@ export function bandOf(ratio: number, bands: { spec: number; react: number } = D
|
|
|
103
105
|
}
|
|
104
106
|
|
|
105
107
|
/**
|
|
106
|
-
* v2
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
108
|
+
* v2.1 判据:证据门槛 + α 平滑的专注/发散比。
|
|
109
|
+
* 回放 2069 条可评分日志:旧判据 react 占 18.3%(其中 57% 是命中 <3 的长而散);
|
|
110
|
+
* α=1(伪计数)+ minHits=3 后 react 占 9.7%,且选中样本 negRate 中位 0.86(真发散)。
|
|
111
|
+
* α 的作用:低命中时比率趋向 1(mixed),不再因「命中少」被误判成 react。
|
|
110
112
|
*/
|
|
111
|
-
export function classify(
|
|
113
|
+
export function classify(pos: number, neg: number, neu: number, c: { minHits: number; spec: number; react: number; alpha: number }): TABand {
|
|
114
|
+
const hits = pos + neg + neu;
|
|
112
115
|
if (hits < c.minHits) return "none";
|
|
113
|
-
|
|
114
|
-
if (
|
|
116
|
+
const r = (pos + 0.4 * neu + c.alpha) / (neg + 0.5 + c.alpha);
|
|
117
|
+
if (r < c.react) return "react";
|
|
118
|
+
if (r < c.spec) return "mixed";
|
|
115
119
|
return "spec";
|
|
116
120
|
}
|
|
117
121
|
|
|
@@ -164,6 +168,11 @@ export function decideNext(phase: TAPhase, band: TABand, l2Tried: number, maxL2:
|
|
|
164
168
|
}
|
|
165
169
|
|
|
166
170
|
// ── 干预文案(中性建议式——原版措辞纪律:不指责、不自称审查者) ──
|
|
171
|
+
export function downgradeIfIneffective(level: "none" | "L1" | "L2", lastEff: number | null): "none" | "L1" | "L2" {
|
|
172
|
+
if (level === "L2" && lastEff !== null && lastEff >= 0) return "L1";
|
|
173
|
+
return level;
|
|
174
|
+
}
|
|
175
|
+
|
|
167
176
|
const L1_TEXT = "「think-anchor」信号(词频启发式,非判据):思考偏离收敛。动作:给结论 + 下一步,只留依据。";
|
|
168
177
|
const L2_TEXT = "「think-anchor」信号(词频启发式,非判据):连续偏离。动作:停止探索,立即执行一个具体动作。";
|
|
169
178
|
|
|
@@ -176,11 +185,19 @@ interface TAState {
|
|
|
176
185
|
l1Count: number;
|
|
177
186
|
l2Count: number;
|
|
178
187
|
turns: number;
|
|
179
|
-
bandHist: { spec: number; mixed: number; react: number };
|
|
188
|
+
bandHist: { spec: number; mixed: number; react: number; none: number };
|
|
180
189
|
lastL1: number;
|
|
181
190
|
lastL2: number;
|
|
182
191
|
lastRatio: number;
|
|
183
192
|
lastBand: TABand;
|
|
193
|
+
/** v3:效果闭环与自适应(借鉴 ptc-meter 的数据自校正 + patch-keeper 的条件触发) */
|
|
194
|
+
recent: number[];
|
|
195
|
+
pending: { turn: number; pre: number; ts: number; seen: number } | null;
|
|
196
|
+
effSamples: number;
|
|
197
|
+
effImproved: number;
|
|
198
|
+
autoMuted: boolean;
|
|
199
|
+
lastLoggedBand: TABand;
|
|
200
|
+
skipped: number;
|
|
184
201
|
}
|
|
185
202
|
|
|
186
203
|
function newState(cfg: TAConfig): TAState {
|
|
@@ -192,14 +209,29 @@ function newState(cfg: TAConfig): TAState {
|
|
|
192
209
|
l1Count: 0,
|
|
193
210
|
l2Count: 0,
|
|
194
211
|
turns: 0,
|
|
195
|
-
bandHist: { spec: 0, mixed: 0, react: 0 },
|
|
212
|
+
bandHist: { spec: 0, mixed: 0, react: 0, none: 0 },
|
|
196
213
|
lastL1: 0,
|
|
197
214
|
lastL2: 0,
|
|
198
215
|
lastRatio: 0,
|
|
199
216
|
lastBand: "mixed",
|
|
217
|
+
recent: [],
|
|
218
|
+
pending: null,
|
|
219
|
+
effSamples: 0,
|
|
220
|
+
effImproved: 0,
|
|
221
|
+
autoMuted: false,
|
|
222
|
+
lastLoggedBand: "mixed",
|
|
223
|
+
skipped: 0,
|
|
200
224
|
};
|
|
201
225
|
}
|
|
202
226
|
|
|
227
|
+
/** 采样写盘(v3):非事件轮每 5 轮留一行——实测 6151 行 / 1.0 MB 逼近 1 MiB 上限,降为 1/5 仍够统计 */
|
|
228
|
+
const SAMPLE_EVERY = 5;
|
|
229
|
+
/** 自适应(v3):有效样本 ≥ AUTO_MIN 且下降率 < AUTO_RATE(不及抛硬币)即转静默,只记录不注入 */
|
|
230
|
+
const AUTO_MIN = 8;
|
|
231
|
+
const AUTO_RATE = 0.5;
|
|
232
|
+
/** 上次干预的实测效果(eff,负 = 改善);用于「无效则不升级」 */
|
|
233
|
+
let lastEffValue: number | null = null;
|
|
234
|
+
|
|
203
235
|
/** 日志上限 1 MiB(与 ptc-meter 同口径):超限保留尾部,避免无界增长。 */
|
|
204
236
|
const MAX_LOG = 1 << 20;
|
|
205
237
|
|
|
@@ -207,9 +239,10 @@ export function appendLog(row: Record<string, unknown>): void {
|
|
|
207
239
|
try {
|
|
208
240
|
appendFileSync(LOG, JSON.stringify(row) + "\n");
|
|
209
241
|
if (statSync(LOG).size > MAX_LOG) {
|
|
210
|
-
const
|
|
211
|
-
const
|
|
212
|
-
|
|
242
|
+
const buf = readFileSync(LOG);
|
|
243
|
+
const keep = buf.subarray(Math.max(0, buf.length - MAX_LOG));
|
|
244
|
+
const cut = keep.indexOf(10);
|
|
245
|
+
writeFileSync(LOG, cut >= 0 ? keep.subarray(cut + 1) : keep);
|
|
213
246
|
}
|
|
214
247
|
} catch {
|
|
215
248
|
/* 静默 */
|
|
@@ -220,7 +253,12 @@ export default function thinkAnchor(pi: ExtensionAPI): void {
|
|
|
220
253
|
let st = newState(loadCfg());
|
|
221
254
|
|
|
222
255
|
pi.on("session_start", () => {
|
|
256
|
+
// 跨会话保住效果闭环:干预常发生在会话末尾,3 轮结算窗口会跨过 session 边界
|
|
257
|
+
const carryRecent = st.recent.slice(-3);
|
|
258
|
+
const carryPending = st.pending;
|
|
223
259
|
st = newState(loadCfg());
|
|
260
|
+
st.recent = carryRecent;
|
|
261
|
+
st.pending = carryPending;
|
|
224
262
|
});
|
|
225
263
|
|
|
226
264
|
pi.on("turn_end", async (event, ctx) => {
|
|
@@ -234,7 +272,18 @@ export default function thinkAnchor(pi: ExtensionAPI): void {
|
|
|
234
272
|
const fp = fingerprint(thinking, st.cfg.lexicon);
|
|
235
273
|
if (fp.pos + fp.neg + fp.neu === 0) {
|
|
236
274
|
st.turns++;
|
|
237
|
-
appendLog({ ts: new Date().toISOString(), turn: st.turns, v:
|
|
275
|
+
if (st.turns % SAMPLE_EVERY === 0) appendLog({ ts: new Date().toISOString(), turn: st.turns, v: 3, len: thinking.length, pos: 0, neg: 0, neu: 0, ratio: null, hits: 0, negRate: null, band: "none", phase: st.phase, want: "none", acted: "none", eff: null });
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
// v2.3:证据门槛作用于**本轮**。窗口聚合 hits 中位 27(仅 1.4% 低于门槛)→ 门槛形同虚设;
|
|
279
|
+
// 而 42.7% 的轮次单轮命中 <3(多为长而散),正是旧口径下 react 偏高的来源(回放 11.3% → 3.4%)。
|
|
280
|
+
const turnHits = fp.pos + fp.neg + fp.neu;
|
|
281
|
+
if (turnHits < st.cfg.score.minHits) {
|
|
282
|
+
st.bandHist.none++; // 会话内计数:v3 采样下 none 轮通常不写日志,只有这里能观测 v2.3 生效
|
|
283
|
+
st.window.push(fp);
|
|
284
|
+
if (st.window.length > st.cfg.windowSize) st.window.shift();
|
|
285
|
+
st.turns++;
|
|
286
|
+
if (st.turns % SAMPLE_EVERY === 0) appendLog({ ts: new Date().toISOString(), turn: st.turns, v: 3, len: thinking.length, pos: fp.pos, neg: fp.neg, neu: fp.neu, ratio: null, hits: turnHits, negRate: null, band: "none", phase: st.phase, want: "none", acted: "none", eff: null });
|
|
238
287
|
return;
|
|
239
288
|
}
|
|
240
289
|
st.window.push(fp);
|
|
@@ -245,65 +294,95 @@ export default function thinkAnchor(pi: ExtensionAPI): void {
|
|
|
245
294
|
lastHits = hits;
|
|
246
295
|
lastNegRate = negRate;
|
|
247
296
|
const ratio = ratioOf(agg.pos, agg.neg, agg.neu, st.cfg.weights);
|
|
248
|
-
const band = classify(
|
|
297
|
+
const band = classify(agg.pos, agg.neg, agg.neu, st.cfg.score);
|
|
249
298
|
const prevPhase = st.phase;
|
|
250
|
-
const
|
|
299
|
+
const r = (agg.pos + 0.4 * agg.neu + st.cfg.score.alpha) / (agg.neg + 0.5 + st.cfg.score.alpha);
|
|
300
|
+
const d0 = decideNext(st.phase, band, st.l2Tried, st.cfg.intervention.maxL2);
|
|
301
|
+
// 升级前先看上次干预有没有用:无效则本次不升 L2(实测 L2 无收益)
|
|
302
|
+
const d = { ...d0, level: downgradeIfIneffective(d0.level, lastEffValue) };
|
|
251
303
|
st.turns++;
|
|
252
|
-
st.lastRatio =
|
|
304
|
+
st.lastRatio = r;
|
|
253
305
|
st.lastBand = band;
|
|
254
306
|
if (band !== "none") st.bandHist[band]++; // none = 证据不足,不计波段(否则 NaN)
|
|
255
307
|
let acted = "none";
|
|
256
308
|
const now = Date.now();
|
|
257
309
|
if (d.level === "none") {
|
|
258
310
|
st.phase = d.phase;
|
|
259
|
-
} else if (d.level === "L1" && now - st.lastL1 > st.cfg.intervention.l1CooldownMin * 60_000) {
|
|
311
|
+
} else if (d.level === "L1" && !st.autoMuted && now - st.lastL1 > st.cfg.intervention.l1CooldownMin * 60_000) {
|
|
260
312
|
st.lastL1 = now;
|
|
261
313
|
st.l1Count++;
|
|
262
314
|
(pi as unknown as { sendMessage?: (m: unknown, o?: unknown) => void }).sendMessage?.(
|
|
263
|
-
{ customType: "think-anchor", content: L1_TEXT +
|
|
315
|
+
{ customType: "think-anchor", content: L1_TEXT + `(比率 ${r.toFixed(2)} · 命中 ${hits};react<0.5)`, display: false },
|
|
264
316
|
{ deliverAs: "nextTurn" },
|
|
265
317
|
);
|
|
266
318
|
acted = "L1";
|
|
319
|
+
st.pending = { turn: st.turns, pre: st.recent.slice(-3).reduce((a, b) => a + b, 0) / Math.max(1, Math.min(3, st.recent.length)), ts: Date.now(), seen: 0 };
|
|
267
320
|
st.phase = d.phase;
|
|
268
|
-
} else if (d.level === "L2" && now - st.lastL2 > st.cfg.intervention.l2CooldownMin * 60_000) {
|
|
321
|
+
} else if (d.level === "L2" && !st.autoMuted && now - st.lastL2 > st.cfg.intervention.l2CooldownMin * 60_000) {
|
|
269
322
|
st.lastL2 = now;
|
|
270
323
|
st.l2Tried++;
|
|
271
324
|
st.l2Count++;
|
|
272
325
|
(pi as unknown as { sendMessage?: (m: unknown, o?: unknown) => void }).sendMessage?.(
|
|
273
|
-
{ customType: "think-anchor", content: L2_TEXT +
|
|
326
|
+
{ customType: "think-anchor", content: L2_TEXT + `(比率 ${r.toFixed(2)} · 命中 ${hits};L2 ${st.l2Tried}/${st.cfg.intervention.maxL2})`, display: false },
|
|
274
327
|
{ deliverAs: "nextTurn" },
|
|
275
328
|
);
|
|
276
329
|
acted = "L2";
|
|
330
|
+
st.pending = { turn: st.turns, pre: st.recent.slice(-3).reduce((a, b) => a + b, 0) / Math.max(1, Math.min(3, st.recent.length)), ts: Date.now(), seen: 0 };
|
|
277
331
|
st.phase = d.phase;
|
|
278
332
|
} else {
|
|
279
333
|
// 被冷却/上限挡下:记 blocked,避免与「无需动作」混淆(实测 583 次 want=L2 只有 55 次真发)
|
|
280
334
|
st.phase = prevPhase;
|
|
281
|
-
if (d.level !== "none") acted = "blocked-" + d.level;
|
|
335
|
+
if (d.level !== "none") acted = st.autoMuted ? "muted-" + d.level : "blocked-" + d.level;
|
|
282
336
|
}
|
|
283
337
|
if (band === "spec" && st.phase === "healthy") st.l2Tried = 0; // 恢复后重置 L2 计数
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
338
|
+
// v3 效果闭环:干预后第 3 轮结算(pre = 干预前最近 3 轮 ratio 均值)
|
|
339
|
+
st.recent.push(ratio);
|
|
340
|
+
if (st.recent.length > 6) st.recent.shift();
|
|
341
|
+
if (st.pending) st.pending.seen++; // 用独立计数而非 turns(新会话 turns 归零,差值会为负)
|
|
342
|
+
let eff: number | null = null;
|
|
343
|
+
// 跨会话只在 30 分钟内结算(避免隔天比率混算)
|
|
344
|
+
const pendingFresh = st.pending !== null && Date.now() - st.pending.ts < 30 * 60_000;
|
|
345
|
+
if (st.pending && pendingFresh && st.pending.seen >= 3) {
|
|
346
|
+
const tail = st.recent.slice(-3);
|
|
347
|
+
const post = tail.reduce((a, b) => a + b, 0) / Math.max(1, tail.length);
|
|
348
|
+
eff = Math.round((st.pending.pre - post) * 100) / 100; // 负 = 改善(比率上升)
|
|
349
|
+
lastEffValue = eff;
|
|
350
|
+
st.effSamples++;
|
|
351
|
+
if (eff < 0) st.effImproved++;
|
|
352
|
+
st.pending = null;
|
|
353
|
+
// 自适应:样本够且实测下降率不及抛硬币 → 转静默(只记录,不注入)
|
|
354
|
+
st.autoMuted = st.effSamples >= AUTO_MIN && st.effImproved / st.effSamples < AUTO_RATE;
|
|
355
|
+
}
|
|
356
|
+
// 采样写盘:事件轮(有干预/有结算/波段变化)必写,其余每 SAMPLE_EVERY 轮一行
|
|
357
|
+
const eventful = acted !== "none" || eff !== null || band !== st.lastLoggedBand;
|
|
358
|
+
if (!eventful && st.turns % SAMPLE_EVERY !== 0) { st.skipped++; }
|
|
359
|
+
else {
|
|
360
|
+
st.lastLoggedBand = band;
|
|
361
|
+
appendLog({
|
|
362
|
+
ts: new Date().toISOString(),
|
|
363
|
+
turn: st.turns,
|
|
364
|
+
v: 3,
|
|
365
|
+
len: thinking.length,
|
|
366
|
+
pos: fp.pos,
|
|
367
|
+
neg: fp.neg,
|
|
368
|
+
neu: fp.neu,
|
|
369
|
+
hits,
|
|
370
|
+
negRate: Math.round(negRate * 100) / 100,
|
|
371
|
+
ratio: Math.round(r * 100) / 100,
|
|
372
|
+
band,
|
|
373
|
+
phase: st.phase,
|
|
374
|
+
want: d.level,
|
|
375
|
+
acted,
|
|
376
|
+
eff,
|
|
377
|
+
});
|
|
378
|
+
}
|
|
300
379
|
} catch {
|
|
301
380
|
/* 绝不抛错 */
|
|
302
381
|
}
|
|
303
382
|
});
|
|
304
383
|
|
|
305
384
|
pi.registerCommand("think-anchor", {
|
|
306
|
-
description: "思维锚定监测:状态 / on / off / reset",
|
|
385
|
+
description: "思维锚定监测:状态 / stats(日志统计)/ on / off / reset",
|
|
307
386
|
handler: async (args, ctx) => {
|
|
308
387
|
const sub = (args || "").trim().toLowerCase();
|
|
309
388
|
if (sub === "on") {
|
|
@@ -321,14 +400,38 @@ export default function thinkAnchor(pi: ExtensionAPI): void {
|
|
|
321
400
|
if (ctx.hasUI) ctx.ui.notify("think-anchor: 会话状态已重置。", "info");
|
|
322
401
|
return;
|
|
323
402
|
}
|
|
403
|
+
if (sub === "stats") {
|
|
404
|
+
try {
|
|
405
|
+
const txt = readFileSync(LOG, "utf8");
|
|
406
|
+
const rows = txt.split("\n").filter((l) => l.trim()).map((l) => { try { return JSON.parse(l) as Record<string, unknown>; } catch { return null; } }).filter(Boolean) as Array<Record<string, unknown>>;
|
|
407
|
+
const group = (k: string) => {
|
|
408
|
+
const m = new Map<string, number>();
|
|
409
|
+
for (const r of rows) { const key = String(r[k] ?? "?"); m.set(key, (m.get(key) ?? 0) + 1); }
|
|
410
|
+
return [...m.entries()].map(([a, b]) => a + "=" + b).join(" ") || "-";
|
|
411
|
+
};
|
|
412
|
+
const effRows = rows.filter((r) => typeof r.eff === "number");
|
|
413
|
+
const improved = effRows.filter((r) => (r.eff as number) < 0).length;
|
|
414
|
+
const size = (statSync(LOG).size / 1048576).toFixed(2);
|
|
415
|
+
const lines = [
|
|
416
|
+
"think-anchor 统计:" + size + " MB / 上限 1.00 · " + rows.length + " 行",
|
|
417
|
+
" 版本 " + group("v") + " · 波段 " + group("band"),
|
|
418
|
+
" 动作 " + group("acted") + " · want " + group("want"),
|
|
419
|
+
" 效果结算 " + improved + "/" + effRows.length + (effRows.length ? "(下降 " + Math.round((100 * improved) / effRows.length) + "%)" : "(样本未满:干预后需再走 3 轮)"),
|
|
420
|
+
];
|
|
421
|
+
if (ctx.hasUI) ctx.ui.notify(lines.join("\n"), "info");
|
|
422
|
+
} catch { if (ctx.hasUI) ctx.ui.notify("think-anchor: 统计失败(日志读取异常)", "warning"); }
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
324
425
|
const line =
|
|
325
426
|
"think-anchor:" +
|
|
326
427
|
(st.cfg.enabled ? "ON" : "OFF") +
|
|
327
428
|
" · phase=" + st.phase +
|
|
328
429
|
" · 消息 " + st.turns +
|
|
329
|
-
" · 波段 spec/mixed/react=" + st.bandHist.spec + "/" + st.bandHist.mixed + "/" + st.bandHist.react +
|
|
430
|
+
" · 波段 spec/mixed/react/none=" + st.bandHist.spec + "/" + st.bandHist.mixed + "/" + st.bandHist.react + "/" + st.bandHist.none +
|
|
330
431
|
" · 干预 L1×" + st.l1Count + " L2×" + st.l2Count +
|
|
331
|
-
" ·
|
|
432
|
+
" · 有效率 " + st.effImproved + "/" + st.effSamples + (st.autoMuted ? "(不及抛硬币→已静默)" : st.effSamples === 0 ? "(样本未满:干预后 3 轮结算)" : st.effSamples < AUTO_MIN ? "(样本 " + st.effSamples + "/" + AUTO_MIN + ")" : "") +
|
|
433
|
+
" · 上轮 命中" + lastHits + " 发散占比" + (Math.round(lastNegRate * 100) / 100) + "(" + st.lastBand + ")" +
|
|
434
|
+
" · 采样跳过 " + st.skipped;
|
|
332
435
|
if (ctx.hasUI) ctx.ui.notify(line, "info");
|
|
333
436
|
},
|
|
334
437
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-think-anchor",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Monitors thinking focus drift and nudges the model back on track.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -22,5 +22,6 @@
|
|
|
22
22
|
],
|
|
23
23
|
"peerDependencies": {
|
|
24
24
|
"@earendil-works/pi-coding-agent": "*"
|
|
25
|
-
}
|
|
25
|
+
},
|
|
26
|
+
"author": "15066212pp"
|
|
26
27
|
}
|