pi-mega-compact 0.9.2 → 0.10.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 +3 -2
- package/dist/extensions/dashboard-server/api-contracts/endpoints.js +95 -47
- package/dist/extensions/dashboard-server/api-contracts/index.js +1 -1
- package/dist/extensions/dashboard-server/api-contracts/turns.js +10 -0
- package/dist/extensions/dashboard-server/api-contracts.test.js +3 -2
- package/dist/extensions/dashboard-server/routes-topics.js +87 -0
- package/dist/extensions/dashboard-server/routes-turns.js +251 -0
- package/dist/extensions/dashboard-server/routes.js +3 -1
- package/dist/extensions/dashboard-server/server.js +6 -2
- package/dist/extensions/mega-compact.js +10 -6
- package/dist/extensions/mega-config.js +4 -0
- package/dist/extensions/mega-events/agent-handlers.js +62 -49
- package/dist/extensions/mega-events/context-handler.js +48 -2
- package/dist/extensions/mega-events/session-handlers.js +24 -0
- package/dist/extensions/mega-metrics-cmds.js +90 -0
- package/dist/extensions/mega-pipeline/recall.js +47 -17
- package/dist/extensions/mega-topics-cmds.js +111 -0
- package/dist/extensions/mega-turn-store.js +110 -0
- package/dist/extensions/mega-turn-store.test.js +68 -0
- package/dist/src/config/turns.js +48 -0
- package/dist/src/fork.js +33 -0
- package/dist/src/fork.test.js +81 -0
- package/dist/src/intent.js +51 -0
- package/dist/src/intent.test.js +78 -0
- package/dist/src/metrics/index.js +4 -0
- package/dist/src/metrics/turns.js +118 -0
- package/dist/src/metrics/turns.test.js +152 -0
- package/dist/src/store/sqlite/raw-transcript.js +5 -3
- package/dist/src/store/sqlite.dbmirror.test.js +9 -0
- package/dist/src/store/turns/connection.js +123 -0
- package/dist/src/store/turns/contract-compliance.test.js +428 -0
- package/dist/src/store/turns/index.js +31 -0
- package/dist/src/store/turns/memory-store.js +357 -0
- package/dist/src/store/turns/memory-store.test.js +9 -0
- package/dist/src/store/turns/migrations.js +178 -0
- package/dist/src/store/turns/migrations.test.js +138 -0
- package/dist/src/store/turns/schema.js +115 -0
- package/dist/src/store/turns/sqlite-store.js +455 -0
- package/dist/src/store/turns/sqlite-store.test.js +144 -0
- package/dist/src/store/turns/types.js +16 -0
- package/dist/src/topics/cluster.js +141 -0
- package/dist/src/topics/cluster.test.js +150 -0
- package/dist/src/topics/index.js +6 -0
- package/dist/src/topics/kselection.js +142 -0
- package/dist/src/topics/labels.js +56 -0
- package/dist/src/topics/store.js +119 -0
- package/dist/src/topics/store.test.js +117 -0
- package/dist/src/topics/types.js +8 -0
- package/dist/src/wiki.js +133 -0
- package/dist/src/wiki.test.js +171 -0
- package/extensions/dashboard-client/dist/assets/{AchievementTiles-QPo46Pwg.js → AchievementTiles-DUulvXUY.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{AchievementTiles-QPo46Pwg.js.map → AchievementTiles-DUulvXUY.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{AchievementsTab-BDJxRXRY.js → AchievementsTab-Dx0YtCNf.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{AchievementsTab-BDJxRXRY.js.map → AchievementsTab-Dx0YtCNf.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{CacheTab-CUMqXQjd.js → CacheTab-DtkaUo1v.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{CacheTab-CUMqXQjd.js.map → CacheTab-DtkaUo1v.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{ConfigTab-j1dRzurG.js → ConfigTab-CLa4KfiZ.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ConfigTab-j1dRzurG.js.map → ConfigTab-CLa4KfiZ.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{EventsTab-CMglXK4p.js → EventsTab-Q6tEIBBi.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{EventsTab-CMglXK4p.js.map → EventsTab-Q6tEIBBi.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{GameTab-fpaDoyxi.js → GameTab-Hq7GNWSY.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{GameTab-fpaDoyxi.js.map → GameTab-Hq7GNWSY.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MetricsTab-CWG2crG_.js → MetricsTab-BtmBV6lU.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MetricsTab-CWG2crG_.js.map → MetricsTab-BtmBV6lU.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{OverviewTab-BaV2D2nf.js → OverviewTab-CL10PwVU.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{OverviewTab-BaV2D2nf.js.map → OverviewTab-CL10PwVU.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{ReposTab-C-0kuZ_0.js → ReposTab-DAwANwbI.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ReposTab-C-0kuZ_0.js.map → ReposTab-DAwANwbI.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SessionsTab-CSYhddCA.js → SessionsTab-Ec08TZRQ.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SessionsTab-CSYhddCA.js.map → SessionsTab-Ec08TZRQ.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-Dy4KRjSM.js → TimeSavedCard-COb8dpTy.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-Dy4KRjSM.js.map → TimeSavedCard-COb8dpTy.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/TopicsTab-kvX3F3dY.js +2 -0
- package/extensions/dashboard-client/dist/assets/TopicsTab-kvX3F3dY.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/TurnsTab-CCUE73Aa.js +2 -0
- package/extensions/dashboard-client/dist/assets/TurnsTab-CCUE73Aa.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{index-D_WtU2TV.js → index-UMrussFX.js} +10 -10
- package/extensions/dashboard-client/dist/assets/index-UMrussFX.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{index-og3ghxyY.css → index-gn66lYD2.css} +1 -1
- package/extensions/dashboard-client/dist/assets/{useSSE-C9yVJlGP.js → useSSE-BDHZ7CeS.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{useSSE-C9yVJlGP.js.map → useSSE-BDHZ7CeS.js.map} +1 -1
- package/extensions/dashboard-client/dist/index.html +2 -2
- package/extensions/dashboard-client/src/App.tsx +10 -2
- package/extensions/dashboard-client/src/api/client.ts +84 -1
- package/extensions/dashboard-client/src/main.tsx +1 -0
- package/extensions/dashboard-client/src/styles/turns.css +142 -0
- package/extensions/dashboard-client/src/tabs/TopicsTab.tsx +185 -0
- package/extensions/dashboard-client/src/tabs/TurnsTab.tsx +326 -0
- package/extensions/dashboard-server/api-contracts/endpoints.ts +389 -291
- package/extensions/dashboard-server/api-contracts/game-types.ts +141 -88
- package/extensions/dashboard-server/api-contracts/index.ts +153 -133
- package/extensions/dashboard-server/api-contracts/turns.ts +114 -0
- package/extensions/dashboard-server/api-contracts.test.ts +920 -919
- package/extensions/dashboard-server/routes-topics.ts +119 -0
- package/extensions/dashboard-server/routes-turns.ts +282 -0
- package/extensions/dashboard-server/routes.ts +8 -1
- package/extensions/dashboard-server/server.ts +6 -2
- package/extensions/mega-compact.ts +75 -59
- package/extensions/mega-config.ts +232 -220
- package/extensions/mega-events/agent-handlers.ts +194 -151
- package/extensions/mega-events/context-handler.ts +50 -1
- package/extensions/mega-events/session-handlers.ts +23 -0
- package/extensions/mega-metrics-cmds.ts +114 -0
- package/extensions/mega-pipeline/recall.ts +215 -152
- package/extensions/mega-topics-cmds.ts +151 -0
- package/extensions/mega-turn-store.test.ts +108 -0
- package/extensions/mega-turn-store.ts +169 -0
- package/package.json +3 -1
- package/src/config/turns.ts +69 -0
- package/src/fork.test.ts +94 -0
- package/src/fork.ts +78 -0
- package/src/intent.test.ts +87 -0
- package/src/intent.ts +115 -0
- package/src/metrics/index.ts +9 -0
- package/src/metrics/turns.test.ts +165 -0
- package/src/metrics/turns.ts +201 -0
- package/src/store/sqlite/raw-transcript.ts +9 -3
- package/src/store/sqlite.dbmirror.test.ts +10 -0
- package/src/store/turns/connection.ts +137 -0
- package/src/store/turns/contract-compliance.test.ts +562 -0
- package/src/store/turns/index.ts +65 -0
- package/src/store/turns/memory-store.test.ts +15 -0
- package/src/store/turns/memory-store.ts +469 -0
- package/src/store/turns/migrations.test.ts +161 -0
- package/src/store/turns/migrations.ts +203 -0
- package/src/store/turns/schema.ts +158 -0
- package/src/store/turns/sqlite-store.test.ts +161 -0
- package/src/store/turns/sqlite-store.ts +656 -0
- package/src/store/turns/types.ts +181 -0
- package/src/topics/cluster.test.ts +176 -0
- package/src/topics/cluster.ts +191 -0
- package/src/topics/index.ts +18 -0
- package/src/topics/kselection.ts +182 -0
- package/src/topics/labels.ts +67 -0
- package/src/topics/store.test.ts +132 -0
- package/src/topics/store.ts +208 -0
- package/src/topics/types.ts +64 -0
- package/src/wiki.test.ts +217 -0
- package/src/wiki.ts +198 -0
- package/dist/extensions/openclaw-mega-compact.js +0 -291
- package/dist/src/minilm.js +0 -92
- package/dist/src/wordpiece.js +0 -129
- package/extensions/dashboard-client/dist/assets/index-D_WtU2TV.js.map +0 -1
|
@@ -5,21 +5,22 @@
|
|
|
5
5
|
* and turn_start/end (turn index, memory auto-review, length-stop detection).
|
|
6
6
|
*/
|
|
7
7
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
} from "../mega-pipeline.js";
|
|
13
|
-
import {
|
|
14
|
-
memoryReviewCadence,
|
|
15
|
-
type MegaConfig,
|
|
16
|
-
} from "../mega-config.js";
|
|
17
|
-
import { recordScore, recordTurn } from "../../src/store/sqlite.js";
|
|
8
|
+
import type { MegaRuntime } from "../mega-runtime.js";
|
|
9
|
+
import { piCompactWouldNoop, runMemoryReview } from "../mega-pipeline.js";
|
|
10
|
+
import { memoryReviewCadence, type MegaConfig } from "../mega-config.js";
|
|
11
|
+
import { recordScore } from "../../src/store/sqlite.js";
|
|
18
12
|
import { evaluateAndUnlockAchievements } from "../../src/store/sqlite/game-achievements.js";
|
|
19
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
ensureConversationIdFor,
|
|
15
|
+
recordTurnWrite,
|
|
16
|
+
} from "../mega-turn-store.js";
|
|
20
17
|
import { isMegaCache } from "../../src/game/scoring.js";
|
|
21
18
|
import { resolveRepoRoot } from "../mega-config.js";
|
|
22
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
classifyError,
|
|
21
|
+
errorRetryBackoffMs,
|
|
22
|
+
extractErrorSignature,
|
|
23
|
+
} from "./error-classifier.js";
|
|
23
24
|
import { safeSendUserMessage } from "./send-safe.js";
|
|
24
25
|
import { vectorStats } from "../../src/vectorStore.js";
|
|
25
26
|
|
|
@@ -64,7 +65,10 @@ export function registerAgentHandlers(
|
|
|
64
65
|
// compaction AND there is queued work AND we haven't nudged recently, nudge
|
|
65
66
|
// once so the agent continues (the live trim should make this rare). Guarded
|
|
66
67
|
// to never busy-loop: one nudge per 30s, only when truly idle + queued.
|
|
67
|
-
if (
|
|
68
|
+
if (
|
|
69
|
+
(config.auto || config.autoContinueLengthStop) &&
|
|
70
|
+
runtime.activeAgents === 0
|
|
71
|
+
) {
|
|
68
72
|
try {
|
|
69
73
|
const idle = ctx.isIdle?.() ?? true;
|
|
70
74
|
const queued = ctx.hasPendingMessages?.() ?? false;
|
|
@@ -80,7 +84,7 @@ export function registerAgentHandlers(
|
|
|
80
84
|
const liveTokens =
|
|
81
85
|
typeof liveUsage?.tokens === "number"
|
|
82
86
|
? liveUsage.tokens
|
|
83
|
-
: runtime.lastCtxTokens ?? 0;
|
|
87
|
+
: (runtime.lastCtxTokens ?? 0);
|
|
84
88
|
// Keep the cache fresh for snapshot()/diag regardless of which source we use.
|
|
85
89
|
if (typeof liveUsage?.tokens === "number") {
|
|
86
90
|
runtime.lastCtxTokens = liveUsage.tokens;
|
|
@@ -134,7 +138,12 @@ export function registerAgentHandlers(
|
|
|
134
138
|
// most. Instead we DECOUPLE the nudge from `queued`: after a durable
|
|
135
139
|
// trim we ALWAYS nudge so the agent reliably restarts. Debounced 30s.
|
|
136
140
|
let didDurableTrim = false;
|
|
137
|
-
if (
|
|
141
|
+
if (
|
|
142
|
+
config.auto &&
|
|
143
|
+
idle &&
|
|
144
|
+
overThreshold &&
|
|
145
|
+
now >= runtime.debounceUntil
|
|
146
|
+
) {
|
|
138
147
|
// COMPACT-DEDUP FIX: skip the manual durable-trim trigger when pi's
|
|
139
148
|
// NATIVE auto-compaction just fired (or is in-flight). pi emits
|
|
140
149
|
// agent_end BEFORE its own _checkCompaction (per its docstring:
|
|
@@ -177,9 +186,12 @@ export function registerAgentHandlers(
|
|
|
177
186
|
setTimeout(() => {
|
|
178
187
|
try {
|
|
179
188
|
if (runtime.rt.sessionId !== liveSid) return; // session reset
|
|
180
|
-
const since2 =
|
|
181
|
-
|
|
182
|
-
|
|
189
|
+
const since2 = now - (runtime.rt.lastNativeCompactAt ?? 0);
|
|
190
|
+
if (
|
|
191
|
+
runtime.rt.lastNativeCompactAt !== stamp &&
|
|
192
|
+
since2 < cooldownMs
|
|
193
|
+
)
|
|
194
|
+
return;
|
|
183
195
|
if (piCompactWouldNoop(ctx)) return;
|
|
184
196
|
ctx.compact({
|
|
185
197
|
customInstructions: undefined,
|
|
@@ -197,7 +209,8 @@ export function registerAgentHandlers(
|
|
|
197
209
|
// Restart the agent after a mid-run durable trim (which stopped it), or
|
|
198
210
|
// when it settled idle with queued work. Decoupled from `queued` for the
|
|
199
211
|
// durable-trim case — see FIX note above. Debounced 30s; never blocks.
|
|
200
|
-
const lengthStop =
|
|
212
|
+
const lengthStop =
|
|
213
|
+
config.autoContinueLengthStop && runtime.rt.lengthStopPending;
|
|
201
214
|
if (
|
|
202
215
|
idle &&
|
|
203
216
|
now >= runtime.resumeNudgeUntil &&
|
|
@@ -206,18 +219,21 @@ export function registerAgentHandlers(
|
|
|
206
219
|
runtime.resumeNudgeUntil = now + 30_000;
|
|
207
220
|
if (runtime.rt.lengthStopPending) {
|
|
208
221
|
runtime.rt.lengthStopPending = false; // one-shot: never re-fire for same stop
|
|
209
|
-
runtime.dashboard.event("length_stop_continue", {
|
|
222
|
+
runtime.dashboard.event("length_stop_continue", {
|
|
223
|
+
turnIndex: runtime.currentTurn,
|
|
224
|
+
});
|
|
210
225
|
runtime.logger.info("length_stop_continue", {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
226
|
+
sessionId: runtime.rt.sessionId,
|
|
227
|
+
didDurableTrim,
|
|
228
|
+
queued,
|
|
214
229
|
});
|
|
215
230
|
}
|
|
216
231
|
// S28: when a length-stop (max-output-token truncation) fired WITHOUT a durable trim, do NOT claim a compaction happened
|
|
217
232
|
// (nothing was compacted on the low-pressure length path). Branch the message so the nudge matches reality.
|
|
218
|
-
const nudgeMsg =
|
|
219
|
-
|
|
220
|
-
|
|
233
|
+
const nudgeMsg =
|
|
234
|
+
lengthStop && !didDurableTrim
|
|
235
|
+
? "[mega-compact] the last response hit the output-token cap; continue from where it stopped."
|
|
236
|
+
: "[mega-compact] continue from the compacted context above.";
|
|
221
237
|
await safeSendUserMessage(pi, nudgeMsg);
|
|
222
238
|
}
|
|
223
239
|
} catch {
|
|
@@ -252,18 +268,27 @@ export function registerAgentHandlers(
|
|
|
252
268
|
// the turn layer is queryable + forkable. Best-effort + non-fatal: a write
|
|
253
269
|
// failure never breaks the agent loop.
|
|
254
270
|
try {
|
|
255
|
-
const convId =
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
271
|
+
const convId = ensureConversationIdFor(
|
|
272
|
+
config,
|
|
273
|
+
runtime.rt.sessionId,
|
|
274
|
+
runtime.currentStateDir,
|
|
275
|
+
);
|
|
276
|
+
recordTurnWrite(
|
|
277
|
+
config,
|
|
278
|
+
{
|
|
279
|
+
conversationId: convId,
|
|
280
|
+
sessionId: runtime.rt.sessionId,
|
|
281
|
+
turnIndex: event.turnIndex,
|
|
282
|
+
role: (event as { role?: string }).role ?? "assistant",
|
|
283
|
+
endedAt: Date.now(),
|
|
284
|
+
startedAt: undefined,
|
|
285
|
+
ctxTokens: runtime.lastCtxTokens ?? undefined,
|
|
286
|
+
ctxPercent: runtime.lastCtxPercent ?? undefined,
|
|
287
|
+
pressureBand: runtime.pressureBand ?? undefined,
|
|
288
|
+
modelId: runtime.currentModel?.modelId ?? undefined,
|
|
289
|
+
},
|
|
290
|
+
runtime.currentStateDir,
|
|
291
|
+
);
|
|
267
292
|
} catch {
|
|
268
293
|
/* non-fatal: per-turn tracking never breaks the agent loop */
|
|
269
294
|
}
|
|
@@ -305,7 +330,9 @@ export function registerAgentHandlers(
|
|
|
305
330
|
}
|
|
306
331
|
// S35: evaluate achievements after scoring; arm a one-time flare for
|
|
307
332
|
// the newly-unlocked ones (consumed by snapshot() → widget toast).
|
|
308
|
-
const newTitles = evaluateAndUnlockAchievements(
|
|
333
|
+
const newTitles = evaluateAndUnlockAchievements(
|
|
334
|
+
runtime.currentStateDir,
|
|
335
|
+
);
|
|
309
336
|
if (newTitles.length) runtime.armAchievementFlare(newTitles);
|
|
310
337
|
}
|
|
311
338
|
} catch {
|
|
@@ -356,8 +383,9 @@ export function registerAgentHandlers(
|
|
|
356
383
|
// gating backoff, session-global cap, poisoned-context detection.
|
|
357
384
|
try {
|
|
358
385
|
// (1) S28 owns length — skip the classifier entirely for it.
|
|
359
|
-
const sr = (event.message as { stopReason?: string } | undefined)
|
|
360
|
-
|
|
386
|
+
const sr = (event.message as { stopReason?: string } | undefined)
|
|
387
|
+
?.stopReason;
|
|
388
|
+
if (sr === "length") {
|
|
361
389
|
// S28 handles; nothing for S38 to do here.
|
|
362
390
|
} else {
|
|
363
391
|
const category = classifyError(event.message);
|
|
@@ -371,7 +399,7 @@ export function registerAgentHandlers(
|
|
|
371
399
|
// (a 'permanent' auth error repeating is still permanent).
|
|
372
400
|
let effectiveCategory = category;
|
|
373
401
|
const errSig = extractErrorSignature(event.message);
|
|
374
|
-
if (category ===
|
|
402
|
+
if (category === "transient" || category === "poisoned-context") {
|
|
375
403
|
if (errSig) {
|
|
376
404
|
if (runtime.rt.lastErrorText === errSig) {
|
|
377
405
|
runtime.rt.errorTextRepeatCount++;
|
|
@@ -380,10 +408,11 @@ export function registerAgentHandlers(
|
|
|
380
408
|
runtime.rt.errorTextRepeatCount = 1;
|
|
381
409
|
}
|
|
382
410
|
if (
|
|
383
|
-
runtime.rt.errorTextRepeatCount >=
|
|
384
|
-
|
|
411
|
+
runtime.rt.errorTextRepeatCount >=
|
|
412
|
+
config.poisonedContextRepeatThreshold &&
|
|
413
|
+
category === "transient"
|
|
385
414
|
) {
|
|
386
|
-
effectiveCategory =
|
|
415
|
+
effectiveCategory = "poisoned-context";
|
|
387
416
|
}
|
|
388
417
|
}
|
|
389
418
|
} else {
|
|
@@ -398,7 +427,7 @@ export function registerAgentHandlers(
|
|
|
398
427
|
runtime.rt.consecutiveErrors = 0; // S38.6: circuit-breaker reset on success
|
|
399
428
|
// R4: a successful assistant turn consumes any queued nudge.
|
|
400
429
|
runtime.rt.retryNudgePending = false;
|
|
401
|
-
} else if (effectiveCategory ===
|
|
430
|
+
} else if (effectiveCategory === "compaction-noop") {
|
|
402
431
|
// (4) pi race / manual compact catch — NOT retryable. The compaction
|
|
403
432
|
// already succeeded via pi's native path; retrying would race again
|
|
404
433
|
// (FAIL-2026071701). Log a diagnostic, reset the counter, and surface
|
|
@@ -406,15 +435,15 @@ export function registerAgentHandlers(
|
|
|
406
435
|
runtime.rt.errorRetryCount = 0;
|
|
407
436
|
runtime.rt.consecutiveErrors = 0; // S38.6: circuit-breaker reset
|
|
408
437
|
runtime.rt.retryNudgePending = false; // R4: terminal for this burst
|
|
409
|
-
runtime.dashboard.event(
|
|
438
|
+
runtime.dashboard.event("compaction_noop_diagnostic", {
|
|
410
439
|
turnIndex: event.turnIndex,
|
|
411
440
|
sessionId: runtime.rt.sessionId,
|
|
412
441
|
});
|
|
413
|
-
runtime.logger.info(
|
|
442
|
+
runtime.logger.info("compaction-noop-diagnostic", {
|
|
414
443
|
sessionId: runtime.rt.sessionId,
|
|
415
444
|
turnIndex: event.turnIndex,
|
|
416
445
|
});
|
|
417
|
-
} else if (effectiveCategory ===
|
|
446
|
+
} else if (effectiveCategory === "cancelled") {
|
|
418
447
|
// (4c) User ESC / Ctrl-C — stopReason === 'aborted'. NOT retryable:
|
|
419
448
|
// nudging would restart a task the user explicitly stopped.
|
|
420
449
|
// Reset both counters (a cancel is not an error for circuit-breaker
|
|
@@ -422,15 +451,15 @@ export function registerAgentHandlers(
|
|
|
422
451
|
runtime.rt.errorRetryCount = 0;
|
|
423
452
|
runtime.rt.consecutiveErrors = 0;
|
|
424
453
|
runtime.rt.retryNudgePending = false; // R4: terminal for this burst
|
|
425
|
-
runtime.dashboard.event(
|
|
454
|
+
runtime.dashboard.event("error_retry_cancelled", {
|
|
426
455
|
turnIndex: event.turnIndex,
|
|
427
456
|
sessionId: runtime.rt.sessionId,
|
|
428
457
|
});
|
|
429
|
-
runtime.logger.info(
|
|
458
|
+
runtime.logger.info("error-retry-cancelled", {
|
|
430
459
|
sessionId: runtime.rt.sessionId,
|
|
431
460
|
turnIndex: event.turnIndex,
|
|
432
461
|
});
|
|
433
|
-
} else if (effectiveCategory ===
|
|
462
|
+
} else if (effectiveCategory === "context-overflow") {
|
|
434
463
|
// (4b) context-window overflow 400 ("too long... even after compaction").
|
|
435
464
|
// NOT a blind retry: re-submitting the same oversized prompt would just
|
|
436
465
|
// re-400 and busy-loop. Reset the counters (this turn is terminal, not
|
|
@@ -444,11 +473,11 @@ export function registerAgentHandlers(
|
|
|
444
473
|
runtime.rt.errorRetryCount = 0;
|
|
445
474
|
runtime.rt.consecutiveErrors = 0;
|
|
446
475
|
runtime.rt.retryNudgePending = false; // R4: terminal for this burst
|
|
447
|
-
runtime.dashboard.event(
|
|
476
|
+
runtime.dashboard.event("context_overflow", {
|
|
448
477
|
turnIndex: event.turnIndex,
|
|
449
478
|
sessionId: runtime.rt.sessionId,
|
|
450
479
|
});
|
|
451
|
-
runtime.logger.warn(
|
|
480
|
+
runtime.logger.warn("context-overflow", {
|
|
452
481
|
sessionId: runtime.rt.sessionId,
|
|
453
482
|
turnIndex: event.turnIndex,
|
|
454
483
|
});
|
|
@@ -463,8 +492,13 @@ export function registerAgentHandlers(
|
|
|
463
492
|
setTimeout(() => {
|
|
464
493
|
try {
|
|
465
494
|
if (runtime.rt.sessionId !== liveSid2) return; // session reset
|
|
466
|
-
const since3 =
|
|
467
|
-
|
|
495
|
+
const since3 =
|
|
496
|
+
Date.now() - (runtime.rt.lastNativeCompactAt ?? 0);
|
|
497
|
+
if (
|
|
498
|
+
runtime.rt.lastNativeCompactAt !== stamp2 &&
|
|
499
|
+
since3 < cooldownMs2
|
|
500
|
+
)
|
|
501
|
+
return;
|
|
468
502
|
if (piCompactWouldNoop(ctx)) return;
|
|
469
503
|
ctx.compact({ customInstructions: undefined }); // guardrails-allow PREVENT-PI-004: local ctx.compact() — no network; deferred + re-validated. Forced re-compact after a context-overflow 400.
|
|
470
504
|
} catch {
|
|
@@ -473,7 +507,7 @@ export function registerAgentHandlers(
|
|
|
473
507
|
}, 500);
|
|
474
508
|
}
|
|
475
509
|
}
|
|
476
|
-
} else if (effectiveCategory ===
|
|
510
|
+
} else if (effectiveCategory === "poisoned-context") {
|
|
477
511
|
// R3: poisoned context — a DETERMINISTIC request-rejection that
|
|
478
512
|
// retrying cannot fix (0-token 'error', generic "request failed",
|
|
479
513
|
// 400 non-overflow, or repeated identical error text). NO blind
|
|
@@ -487,15 +521,15 @@ export function registerAgentHandlers(
|
|
|
487
521
|
runtime.rt.errorRetryCount = 0; // poisoned is terminal for this burst
|
|
488
522
|
runtime.rt.consecutiveErrors++; // still counts toward the circuit breaker
|
|
489
523
|
runtime.rt.poisonedCount++; // R7: dashboard counter
|
|
490
|
-
const sig = errSig ||
|
|
524
|
+
const sig = errSig || "unknown";
|
|
491
525
|
// (a) dashboard + log
|
|
492
|
-
runtime.dashboard.event(
|
|
526
|
+
runtime.dashboard.event("poisoned_context", {
|
|
493
527
|
signature: sig,
|
|
494
528
|
repeatCount: runtime.rt.errorTextRepeatCount,
|
|
495
529
|
turnIndex: event.turnIndex,
|
|
496
530
|
sessionId: runtime.rt.sessionId,
|
|
497
531
|
});
|
|
498
|
-
runtime.logger.warn(
|
|
532
|
+
runtime.logger.warn("poisoned-context", {
|
|
499
533
|
sessionId: runtime.rt.sessionId,
|
|
500
534
|
turnIndex: event.turnIndex,
|
|
501
535
|
signature: sig,
|
|
@@ -507,7 +541,7 @@ export function registerAgentHandlers(
|
|
|
507
541
|
// PREVENT-PI-003: user-role sendUserMessage only.
|
|
508
542
|
await safeSendUserMessage(
|
|
509
543
|
pi,
|
|
510
|
-
|
|
544
|
+
"[mega-compact] this session's context may be poisoned (the provider is rejecting every request). Run /clear or /new to start a fresh context.",
|
|
511
545
|
);
|
|
512
546
|
}
|
|
513
547
|
// (c) one guarded compact per error signature (attempt to remove
|
|
@@ -526,8 +560,13 @@ export function registerAgentHandlers(
|
|
|
526
560
|
setTimeout(() => {
|
|
527
561
|
try {
|
|
528
562
|
if (runtime.rt.sessionId !== liveSidP) return; // session reset
|
|
529
|
-
const sinceP =
|
|
530
|
-
|
|
563
|
+
const sinceP =
|
|
564
|
+
Date.now() - (runtime.rt.lastNativeCompactAt ?? 0);
|
|
565
|
+
if (
|
|
566
|
+
runtime.rt.lastNativeCompactAt !== stampP &&
|
|
567
|
+
sinceP < cooldownMsP
|
|
568
|
+
)
|
|
569
|
+
return;
|
|
531
570
|
if (piCompactWouldNoop(ctx)) return;
|
|
532
571
|
ctx.compact({ customInstructions: undefined }); // guardrails-allow PREVENT-PI-004: local ctx.compact() — no network; deferred + re-validated. One guarded compact after a poisoned-context detection.
|
|
533
572
|
} catch {
|
|
@@ -541,22 +580,22 @@ export function registerAgentHandlers(
|
|
|
541
580
|
// S38.7: hard-stop switch — bypass ALL retry logic when set.
|
|
542
581
|
if (config.errorRetryHardStop) {
|
|
543
582
|
runtime.rt.errorRetryCount = 0;
|
|
544
|
-
runtime.dashboard.event(
|
|
583
|
+
runtime.dashboard.event("error_retry_disabled", {
|
|
545
584
|
category: effectiveCategory,
|
|
546
585
|
turnIndex: event.turnIndex,
|
|
547
|
-
reason:
|
|
586
|
+
reason: "hard-stop",
|
|
548
587
|
});
|
|
549
588
|
return; // early exit — no retry
|
|
550
589
|
}
|
|
551
590
|
// S38.6: circuit-breaker — stop retrying after too many consecutive errors.
|
|
552
591
|
runtime.rt.consecutiveErrors++;
|
|
553
592
|
if (runtime.rt.consecutiveErrors > config.maxConsecutiveErrors) {
|
|
554
|
-
runtime.dashboard.event(
|
|
593
|
+
runtime.dashboard.event("error_retry_circuit_open", {
|
|
555
594
|
consecutive: runtime.rt.consecutiveErrors,
|
|
556
595
|
max: config.maxConsecutiveErrors,
|
|
557
596
|
turnIndex: event.turnIndex,
|
|
558
597
|
});
|
|
559
|
-
runtime.logger.warn(
|
|
598
|
+
runtime.logger.warn("error-retry-circuit-open", {
|
|
560
599
|
sessionId: runtime.rt.sessionId,
|
|
561
600
|
consecutive: runtime.rt.consecutiveErrors,
|
|
562
601
|
max: config.maxConsecutiveErrors,
|
|
@@ -564,114 +603,118 @@ export function registerAgentHandlers(
|
|
|
564
603
|
return; // early exit — circuit breaker tripped
|
|
565
604
|
}
|
|
566
605
|
const max =
|
|
567
|
-
effectiveCategory ===
|
|
606
|
+
effectiveCategory === "transient"
|
|
568
607
|
? config.autoRetryTransientMax
|
|
569
608
|
: config.autoRetryPermanentMax;
|
|
570
609
|
// max === 0 disables the category entirely (revert to S28-only).
|
|
571
610
|
if (max <= 0) {
|
|
572
611
|
runtime.rt.errorRetryCount = 0;
|
|
612
|
+
} else {
|
|
613
|
+
runtime.rt.errorRetryCount++;
|
|
614
|
+
if (runtime.rt.errorRetryCount > max) {
|
|
615
|
+
// Exhausted — surface the error, reset for the next burst.
|
|
616
|
+
runtime.dashboard.event("error_retry_exhausted", {
|
|
617
|
+
category: effectiveCategory,
|
|
618
|
+
count: runtime.rt.errorRetryCount,
|
|
619
|
+
max,
|
|
620
|
+
turnIndex: event.turnIndex,
|
|
621
|
+
});
|
|
622
|
+
runtime.logger.info("error-retry-exhausted", {
|
|
623
|
+
sessionId: runtime.rt.sessionId,
|
|
624
|
+
category: effectiveCategory,
|
|
625
|
+
count: runtime.rt.errorRetryCount,
|
|
626
|
+
max,
|
|
627
|
+
});
|
|
628
|
+
runtime.rt.errorRetryCount = 0;
|
|
573
629
|
} else {
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
630
|
+
// R2: session-global cap — total S38 nudges per session across
|
|
631
|
+
// ALL bursts. Independent of the per-burst max and the circuit
|
|
632
|
+
// breaker. Hitting it is terminal for the session: log +
|
|
633
|
+
// dashboard event, stop nudging. `0` disables (reverts to
|
|
634
|
+
// per-burst + circuit-breaker only).
|
|
635
|
+
if (
|
|
636
|
+
config.errorRetrySessionMax > 0 &&
|
|
637
|
+
runtime.rt.errorRetrySessionCount >= config.errorRetrySessionMax
|
|
638
|
+
) {
|
|
639
|
+
runtime.dashboard.event("error_retry_session_exhausted", {
|
|
640
|
+
count: runtime.rt.errorRetrySessionCount,
|
|
641
|
+
max: config.errorRetrySessionMax,
|
|
578
642
|
category: effectiveCategory,
|
|
579
|
-
count: runtime.rt.errorRetryCount,
|
|
580
|
-
max,
|
|
581
643
|
turnIndex: event.turnIndex,
|
|
582
644
|
});
|
|
583
|
-
runtime.logger.
|
|
645
|
+
runtime.logger.warn("error-retry-session-exhausted", {
|
|
584
646
|
sessionId: runtime.rt.sessionId,
|
|
647
|
+
count: runtime.rt.errorRetrySessionCount,
|
|
648
|
+
max: config.errorRetrySessionMax,
|
|
585
649
|
category: effectiveCategory,
|
|
586
|
-
count: runtime.rt.errorRetryCount,
|
|
587
|
-
max,
|
|
588
650
|
});
|
|
589
651
|
runtime.rt.errorRetryCount = 0;
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
category: effectiveCategory,
|
|
604
|
-
turnIndex: event.turnIndex,
|
|
605
|
-
});
|
|
606
|
-
runtime.logger.warn('error-retry-session-exhausted', {
|
|
607
|
-
sessionId: runtime.rt.sessionId,
|
|
608
|
-
count: runtime.rt.errorRetrySessionCount,
|
|
609
|
-
max: config.errorRetrySessionMax,
|
|
610
|
-
category: effectiveCategory,
|
|
611
|
-
});
|
|
612
|
-
runtime.rt.errorRetryCount = 0;
|
|
613
|
-
return; // terminal for the session — no nudge
|
|
614
|
-
}
|
|
615
|
-
// R1: in-flight nudge dedup — a nudge queued via
|
|
616
|
-
// deliverAs:'followUp' must not be re-sent until it has been
|
|
617
|
-
// consumed by an actual new agent turn (turn_start resets
|
|
618
|
-
// retryNudgePending). Without this, a fast-erroring provider +
|
|
619
|
-
// a per-turn nudge → N nudges queue up and pi dispatches N
|
|
620
|
-
// retry turns, each re-submitting the same failing prompt
|
|
621
|
-
// (the 2026-07-28 incident). errorRetryCount still advances
|
|
622
|
-
// (this IS an error turn), so the per-burst max + circuit
|
|
623
|
-
// breaker still bound the burst.
|
|
624
|
-
if (runtime.rt.retryNudgePending) {
|
|
625
|
-
runtime.dashboard.event('error_retry_dedup_skip', {
|
|
626
|
-
category: effectiveCategory,
|
|
627
|
-
count: runtime.rt.errorRetryCount,
|
|
628
|
-
max,
|
|
629
|
-
turnIndex: event.turnIndex,
|
|
630
|
-
});
|
|
631
|
-
return; // pending nudge not yet consumed — skip
|
|
632
|
-
}
|
|
633
|
-
// R1: gating backoff — errorRetryUntil is now GATING
|
|
634
|
-
// (previously documented as non-gating). A nudge cannot fire
|
|
635
|
-
// before the previous backoff elapses. This paces retries
|
|
636
|
-
// (5s/10s/20s/30s by default) so a fast-erroring provider
|
|
637
|
-
// doesn't slam N nudges in <1s.
|
|
638
|
-
const now = Date.now();
|
|
639
|
-
if (now < runtime.rt.errorRetryUntil) {
|
|
640
|
-
runtime.dashboard.event('error_retry_backoff_skip', {
|
|
641
|
-
category: effectiveCategory,
|
|
642
|
-
count: runtime.rt.errorRetryCount,
|
|
643
|
-
max,
|
|
644
|
-
turnIndex: event.turnIndex,
|
|
645
|
-
});
|
|
646
|
-
return; // backoff not elapsed — skip
|
|
647
|
-
}
|
|
648
|
-
// Fire the retry nudge. Set pending (R1 dedup) + backoff
|
|
649
|
-
// (R1 pacing) + session count (R2 cap) BEFORE the await so a
|
|
650
|
-
// re-entrant turn_end during the send can't double-fire.
|
|
651
|
-
runtime.rt.retryNudgePending = true;
|
|
652
|
-
runtime.rt.lastErrorRetryAt = now;
|
|
653
|
-
runtime.rt.errorRetryUntil =
|
|
654
|
-
now + errorRetryBackoffMs(runtime.rt.errorRetryCount, config.errorRetryBackoffMs);
|
|
655
|
-
runtime.rt.errorRetrySessionCount++;
|
|
656
|
-
runtime.dashboard.event('error_retry', {
|
|
652
|
+
return; // terminal for the session — no nudge
|
|
653
|
+
}
|
|
654
|
+
// R1: in-flight nudge dedup — a nudge queued via
|
|
655
|
+
// deliverAs:'followUp' must not be re-sent until it has been
|
|
656
|
+
// consumed by an actual new agent turn (turn_start resets
|
|
657
|
+
// retryNudgePending). Without this, a fast-erroring provider +
|
|
658
|
+
// a per-turn nudge → N nudges queue up and pi dispatches N
|
|
659
|
+
// retry turns, each re-submitting the same failing prompt
|
|
660
|
+
// (the 2026-07-28 incident). errorRetryCount still advances
|
|
661
|
+
// (this IS an error turn), so the per-burst max + circuit
|
|
662
|
+
// breaker still bound the burst.
|
|
663
|
+
if (runtime.rt.retryNudgePending) {
|
|
664
|
+
runtime.dashboard.event("error_retry_dedup_skip", {
|
|
657
665
|
category: effectiveCategory,
|
|
658
666
|
count: runtime.rt.errorRetryCount,
|
|
659
667
|
max,
|
|
660
668
|
turnIndex: event.turnIndex,
|
|
661
669
|
});
|
|
662
|
-
|
|
663
|
-
|
|
670
|
+
return; // pending nudge not yet consumed — skip
|
|
671
|
+
}
|
|
672
|
+
// R1: gating backoff — errorRetryUntil is now GATING
|
|
673
|
+
// (previously documented as non-gating). A nudge cannot fire
|
|
674
|
+
// before the previous backoff elapses. This paces retries
|
|
675
|
+
// (5s/10s/20s/30s by default) so a fast-erroring provider
|
|
676
|
+
// doesn't slam N nudges in <1s.
|
|
677
|
+
const now = Date.now();
|
|
678
|
+
if (now < runtime.rt.errorRetryUntil) {
|
|
679
|
+
runtime.dashboard.event("error_retry_backoff_skip", {
|
|
664
680
|
category: effectiveCategory,
|
|
665
681
|
count: runtime.rt.errorRetryCount,
|
|
666
682
|
max,
|
|
683
|
+
turnIndex: event.turnIndex,
|
|
667
684
|
});
|
|
668
|
-
//
|
|
669
|
-
await safeSendUserMessage(
|
|
670
|
-
pi,
|
|
671
|
-
'[mega-compact] the last turn ended with an error; please retry.',
|
|
672
|
-
);
|
|
685
|
+
return; // backoff not elapsed — skip
|
|
673
686
|
}
|
|
687
|
+
// Fire the retry nudge. Set pending (R1 dedup) + backoff
|
|
688
|
+
// (R1 pacing) + session count (R2 cap) BEFORE the await so a
|
|
689
|
+
// re-entrant turn_end during the send can't double-fire.
|
|
690
|
+
runtime.rt.retryNudgePending = true;
|
|
691
|
+
runtime.rt.lastErrorRetryAt = now;
|
|
692
|
+
runtime.rt.errorRetryUntil =
|
|
693
|
+
now +
|
|
694
|
+
errorRetryBackoffMs(
|
|
695
|
+
runtime.rt.errorRetryCount,
|
|
696
|
+
config.errorRetryBackoffMs,
|
|
697
|
+
);
|
|
698
|
+
runtime.rt.errorRetrySessionCount++;
|
|
699
|
+
runtime.dashboard.event("error_retry", {
|
|
700
|
+
category: effectiveCategory,
|
|
701
|
+
count: runtime.rt.errorRetryCount,
|
|
702
|
+
max,
|
|
703
|
+
turnIndex: event.turnIndex,
|
|
704
|
+
});
|
|
705
|
+
runtime.logger.info("error-retry", {
|
|
706
|
+
sessionId: runtime.rt.sessionId,
|
|
707
|
+
category: effectiveCategory,
|
|
708
|
+
count: runtime.rt.errorRetryCount,
|
|
709
|
+
max,
|
|
710
|
+
});
|
|
711
|
+
// PREVENT-PI-003: user-role sendUserMessage only (queued + catch-guarded).
|
|
712
|
+
await safeSendUserMessage(
|
|
713
|
+
pi,
|
|
714
|
+
"[mega-compact] the last turn ended with an error; please retry.",
|
|
715
|
+
);
|
|
674
716
|
}
|
|
717
|
+
}
|
|
675
718
|
}
|
|
676
719
|
}
|
|
677
720
|
} catch {
|
|
@@ -23,6 +23,14 @@ import { autoCompactCheck } from "../../src/compact.js";
|
|
|
23
23
|
import { estimateSessionTokens } from "../../src/tokens.js";
|
|
24
24
|
import type { MegaRuntime } from "../mega-runtime.js";
|
|
25
25
|
import { runCompact, piCompactWouldNoop } from "../mega-pipeline.js";
|
|
26
|
+
import { stampTurnsEpochFor } from "../mega-turn-store.js";
|
|
27
|
+
import { TurnsConfig } from "../../src/config/turns.js";
|
|
28
|
+
import { openTurnStore } from "../../src/store/turns/connection.js";
|
|
29
|
+
import {
|
|
30
|
+
buildTopicModel,
|
|
31
|
+
createTopicStore,
|
|
32
|
+
bumpWikiCompactCounter,
|
|
33
|
+
} from "../../src/topics/index.js";
|
|
26
34
|
import { computeLiveTrimCut, liveTrimSummaryMessage } from "../mega-trim.js";
|
|
27
35
|
import {
|
|
28
36
|
pressureFromPct,
|
|
@@ -71,6 +79,7 @@ function toRawTranscriptRow(
|
|
|
71
79
|
msg: AgentMessage,
|
|
72
80
|
sessionId: string,
|
|
73
81
|
epochId: string,
|
|
82
|
+
currentTurn?: number,
|
|
74
83
|
): RawTranscriptRow | null {
|
|
75
84
|
// Narrow to Message union (has content + timestamp).
|
|
76
85
|
const m = msg as {
|
|
@@ -101,6 +110,9 @@ function toRawTranscriptRow(
|
|
|
101
110
|
toolName: m.toolName ?? null,
|
|
102
111
|
messageTimestamp: m.timestamp ?? null,
|
|
103
112
|
checkpointEpoch: epochId,
|
|
113
|
+
// S50: label the row with the turn that produced it (per-turn dedup /
|
|
114
|
+
// compression-by-turn metrics). Null when the writer omits it (back-compat).
|
|
115
|
+
turnIndex: currentTurn ?? null,
|
|
104
116
|
};
|
|
105
117
|
}
|
|
106
118
|
|
|
@@ -162,7 +174,7 @@ export function registerContextHandler(
|
|
|
162
174
|
const db = openStore(runtime.currentStateDir);
|
|
163
175
|
const epochId = epochIdFor(runtime.rt.sessionId);
|
|
164
176
|
for (const msg of messages) {
|
|
165
|
-
const raw = toRawTranscriptRow(msg, runtime.rt.sessionId, epochId);
|
|
177
|
+
const raw = toRawTranscriptRow(msg, runtime.rt.sessionId, epochId, runtime.currentTurn);
|
|
166
178
|
if (raw) appendRawTranscript(db, raw);
|
|
167
179
|
}
|
|
168
180
|
} catch (e) {
|
|
@@ -279,6 +291,43 @@ export function registerContextHandler(
|
|
|
279
291
|
createdAt: Date.now(),
|
|
280
292
|
};
|
|
281
293
|
writeCheckpointEpoch(db, epoch);
|
|
294
|
+
// S50B: link this session's turns to the epoch that just compacted
|
|
295
|
+
// them (compression-by-conversation-epoch metrics). Isolated-store
|
|
296
|
+
// only; best-effort + non-fatal.
|
|
297
|
+
try {
|
|
298
|
+
stampTurnsEpochFor(config, runtime.rt.sessionId, epoch.epochId, runtime.currentStateDir);
|
|
299
|
+
} catch {
|
|
300
|
+
/* non-fatal: epoch stamping never breaks compaction */
|
|
301
|
+
}
|
|
302
|
+
// S51B: auto-categorizing wiki rebuild — every Nth compaction, derived
|
|
303
|
+
// from real context_chunks embeddings. Isolated-store only, gated on
|
|
304
|
+
// AUTO_WIKI_ENABLED; best-effort + non-fatal (never breaks compaction).
|
|
305
|
+
try {
|
|
306
|
+
if (config.autoWikiEnabled && config.turnsDbEnabled) {
|
|
307
|
+
const every = Math.max(1, TurnsConfig.WIKI_REBUILD_EVERY_N_COMPACTS);
|
|
308
|
+
const tdb = openTurnStore(runtime.currentStateDir);
|
|
309
|
+
const n = bumpWikiCompactCounter(tdb);
|
|
310
|
+
if (n % every === 0) {
|
|
311
|
+
const model = buildTopicModel(db, {
|
|
312
|
+
kRange: [TurnsConfig.WIKI_K_MIN, TurnsConfig.WIKI_K_MAX],
|
|
313
|
+
labelTopTerms: TurnsConfig.WIKI_LABEL_TOP_TERMS,
|
|
314
|
+
restarts: 5,
|
|
315
|
+
seed: 0x9e3779b9,
|
|
316
|
+
});
|
|
317
|
+
createTopicStore(runtime.currentStateDir).replaceTopicModel(model);
|
|
318
|
+
runtime.logger.info("wiki_rebuild", {
|
|
319
|
+
clusterCount: model.k,
|
|
320
|
+
totalChunks: model.totalChunks,
|
|
321
|
+
method: "kmeans+tfidf",
|
|
322
|
+
criterion: model.criterion,
|
|
323
|
+
silhouetteScore: model.silhouetteScore,
|
|
324
|
+
uncalibrated: false,
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
} catch (wikiErr) {
|
|
329
|
+
runtime.logger.warn("wiki_rebuild_failed", { error: String(wikiErr) });
|
|
330
|
+
}
|
|
282
331
|
// S27 Task 6: Fire-and-forget dedup pipeline.
|
|
283
332
|
// Deduplicates raw_transcript rows for the compacted range.
|
|
284
333
|
try {
|