pi-mega-compact 0.8.23 → 0.8.25
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 +26 -0
- package/dist/extensions/dashboard-server/api-contracts/endpoints.js +8 -0
- package/dist/extensions/dashboard-server/api-contracts/game-types.js +7 -0
- package/dist/extensions/mega-compact-s38.test.js +263 -14
- package/dist/extensions/mega-compact.js +15 -0
- package/dist/extensions/mega-config.js +3 -0
- package/dist/extensions/mega-events/agent-handlers.js +211 -26
- package/dist/extensions/mega-events/context-handler.js +45 -7
- package/dist/extensions/mega-events/error-classifier.js +125 -18
- package/dist/extensions/mega-pipeline/compact.js +24 -13
- package/dist/extensions/mega-pipeline/recall.js +31 -2
- package/dist/extensions/mega-runtime/append-event.js +24 -0
- package/dist/extensions/mega-runtime/bind-repo.js +65 -0
- package/dist/extensions/mega-runtime/capture-model.js +87 -0
- package/dist/extensions/mega-runtime/dashboard-snapshot.js +122 -0
- package/dist/extensions/mega-runtime/effects.js +86 -0
- package/dist/extensions/mega-runtime/engine-view.js +11 -0
- package/dist/extensions/mega-runtime/game-state.js +116 -0
- package/dist/extensions/mega-runtime/get-state-dir.js +10 -0
- package/dist/extensions/mega-runtime/perf.js +49 -0
- package/dist/extensions/mega-runtime/pressure-getters.js +64 -0
- package/dist/extensions/mega-runtime/render-widget.js +17 -0
- package/dist/extensions/mega-runtime/reset-runtime.js +50 -0
- package/dist/extensions/mega-runtime/runtime-helpers.js +73 -0
- package/dist/extensions/mega-runtime/runtime-snapshot.js +208 -0
- package/dist/extensions/mega-runtime/runtime.js +405 -0
- package/dist/extensions/mega-runtime/snapshot.js +142 -0
- package/dist/extensions/mega-runtime/state.js +5 -1151
- package/dist/extensions/mega-runtime/status.js +11 -0
- package/dist/extensions/mega-runtime/widget-ansi.js +207 -0
- package/dist/extensions/mega-runtime/widget-types.js +8 -0
- package/dist/extensions/mega-runtime/widget.js +15 -204
- package/dist/extensions/openclaw-mega-compact.js +291 -0
- package/dist/src/boundary.js +79 -43
- package/dist/src/boundary.test.js +119 -2
- package/dist/src/canary.js +10 -0
- package/dist/src/config/dedup.js +14 -0
- package/dist/src/config.js +3 -1
- package/dist/src/dedup/raptor/buildHistory.js +164 -0
- package/dist/src/dedup/raptor/buildHistory.test.js +292 -0
- package/dist/src/dedup/raptor/index.js +38 -0
- package/dist/src/dedup/raptor/multilevel-serve.test.js +229 -0
- package/dist/src/dedup/raptor/multilevel.js +17 -5
- package/dist/src/dedup/raptor/multilevel.test.js +36 -1
- package/dist/src/dedup/raptor/raptor.test.js +43 -0
- package/dist/src/dedup/raptor/retrieval.js +14 -2
- package/dist/src/dedup/raptor/retrieval.test.js +95 -0
- package/dist/src/dedup/raptor/serve-gate.test.js +298 -0
- package/dist/src/dedup/raptor/summarizer.js +1 -0
- package/dist/src/dedup/raptor/tree.js +16 -2
- package/dist/src/engine.js +18 -2
- package/dist/src/httpEmbedder.js +96 -6
- package/dist/src/httpEmbedder.test.js +277 -0
- package/dist/src/mechanical-fix.test.js +65 -0
- package/dist/src/minilm.js +92 -0
- package/dist/src/raptor-inject-summaries.test.js +155 -0
- package/dist/src/recall.js +135 -21
- package/dist/src/recall.test.js +179 -4
- package/dist/src/store/sqlite/dedup-mirror.js +32 -15
- package/dist/src/store/sqlite/maintenance.js +2 -2
- package/dist/src/store/sqlite/mechanical-fix.test.js +146 -0
- package/dist/src/store/sqlite/memories.js +5 -5
- package/dist/src/store/sqlite/meta.js +1 -1
- package/dist/src/store/sqlite/raptor.js +56 -17
- package/dist/src/store/sqlite/raptor.test.js +106 -0
- package/dist/src/store/sqlite/schema.js +90 -1
- package/dist/src/store/sqlite/session-state.js +9 -3
- package/dist/src/store/sqlite/stats.js +9 -5
- package/dist/src/store/sqlite/turns.js +181 -0
- package/dist/src/store/sqlite/turns.test.js +183 -0
- package/dist/src/store/sqlite/utils.js +15 -4
- package/dist/src/store/sqlite.js +1 -0
- package/dist/src/store.js +2 -2
- package/dist/src/vector-search-cache.test.js +157 -0
- package/dist/src/vector-search.js +107 -15
- package/dist/src/vectorStore.js +36 -8
- package/dist/src/wordpiece.js +129 -0
- package/extensions/dashboard-client/dist/assets/index-D_WtU2TV.js.map +1 -1
- package/extensions/dashboard-server/api-contracts/endpoints.ts +30 -155
- package/extensions/dashboard-server/api-contracts/game-types.ts +172 -0
- package/extensions/mega-compact-s38.test.ts +259 -14
- package/extensions/mega-compact.ts +15 -0
- package/extensions/mega-config.ts +18 -0
- package/extensions/mega-dashboard.ts +10 -1
- package/extensions/mega-events/agent-handlers.ts +211 -26
- package/extensions/mega-events/context-handler.ts +43 -7
- package/extensions/mega-events/error-classifier.ts +125 -17
- package/extensions/mega-pipeline/compact.ts +28 -16
- package/extensions/mega-pipeline/recall.ts +34 -2
- package/extensions/mega-runtime/DECOMPOSITION.md +180 -0
- package/extensions/mega-runtime/README.md +38 -0
- package/extensions/mega-runtime/append-event.ts +40 -0
- package/extensions/mega-runtime/bind-repo.ts +81 -0
- package/extensions/mega-runtime/capture-model.ts +101 -0
- package/extensions/mega-runtime/dashboard-snapshot.ts +181 -0
- package/extensions/mega-runtime/effects.ts +129 -0
- package/extensions/mega-runtime/engine-view.ts +17 -0
- package/extensions/mega-runtime/game-state.ts +149 -0
- package/extensions/mega-runtime/get-state-dir.ts +19 -0
- package/extensions/mega-runtime/helpers.ts +25 -1
- package/extensions/mega-runtime/perf.ts +60 -0
- package/extensions/mega-runtime/pressure-getters.ts +96 -0
- package/extensions/mega-runtime/render-widget.ts +41 -0
- package/extensions/mega-runtime/runtime-helpers.ts +119 -0
- package/extensions/mega-runtime/runtime-snapshot.ts +293 -0
- package/extensions/mega-runtime/runtime.ts +483 -0
- package/extensions/mega-runtime/snapshot.ts +230 -0
- package/extensions/mega-runtime/state.ts +5 -1268
- package/extensions/mega-runtime/status.ts +26 -0
- package/extensions/mega-runtime/widget-ansi.ts +217 -0
- package/extensions/mega-runtime/widget-types.ts +80 -0
- package/extensions/mega-runtime/widget.ts +34 -285
- package/package.json +1 -1
- package/src/boundary.test.ts +128 -2
- package/src/boundary.ts +75 -39
- package/src/canary.ts +10 -0
- package/src/config/dedup.ts +25 -0
- package/src/config.ts +3 -1
- package/src/dedup/raptor/buildHistory.test.ts +353 -0
- package/src/dedup/raptor/buildHistory.ts +259 -0
- package/src/dedup/raptor/index.ts +38 -0
- package/src/dedup/raptor/multilevel-serve.test.ts +273 -0
- package/src/dedup/raptor/multilevel.test.ts +47 -0
- package/src/dedup/raptor/multilevel.ts +18 -8
- package/src/dedup/raptor/raptor.test.ts +59 -0
- package/src/dedup/raptor/retrieval.test.ts +118 -0
- package/src/dedup/raptor/retrieval.ts +14 -2
- package/src/dedup/raptor/serve-gate.test.ts +348 -0
- package/src/dedup/raptor/summarizer.ts +1 -0
- package/src/dedup/raptor/tree.ts +17 -2
- package/src/engine.ts +32 -3
- package/src/httpEmbedder.test.ts +286 -0
- package/src/httpEmbedder.ts +98 -8
- package/src/mechanical-fix.test.ts +70 -0
- package/src/raptor-inject-summaries.test.ts +212 -0
- package/src/recall.test.ts +220 -4
- package/src/recall.ts +151 -22
- package/src/store/sqlite/dedup-mirror.ts +35 -18
- package/src/store/sqlite/maintenance.ts +2 -2
- package/src/store/sqlite/mechanical-fix.test.ts +162 -0
- package/src/store/sqlite/memories.ts +5 -5
- package/src/store/sqlite/meta.ts +1 -1
- package/src/store/sqlite/raptor.test.ts +139 -0
- package/src/store/sqlite/raptor.ts +135 -81
- package/src/store/sqlite/schema.ts +90 -1
- package/src/store/sqlite/session-state.ts +9 -3
- package/src/store/sqlite/stats.ts +10 -8
- package/src/store/sqlite/turns.test.ts +218 -0
- package/src/store/sqlite/turns.ts +292 -0
- package/src/store/sqlite/utils.ts +14 -4
- package/src/store/sqlite.ts +1 -0
- package/src/store.ts +9 -2
- package/src/vector-search-cache.test.ts +190 -0
- package/src/vector-search.ts +273 -156
- package/src/vectorStore.ts +443 -382
- package/dist/extensions/dashboard-client/src/hooks/useApi.js +0 -51
- package/dist/extensions/dashboard-client/src/hooks/useSSE.js +0 -63
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dashboard-snapshot.ts — extracted DashboardSnapshot builder for MegaRuntime.
|
|
3
|
+
*
|
|
4
|
+
* Builds the ~130-line DashboardSnapshot data object that was previously
|
|
5
|
+
* inlined inside MegaRuntime.snapshot(). Pure data-gathering — no I/O, no
|
|
6
|
+
* side effects.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { DashboardSnapshot } from "../mega-dashboard.js";
|
|
10
|
+
|
|
11
|
+
// ---------------------------------------------------------------------- types
|
|
12
|
+
|
|
13
|
+
export interface SnapshotBuildContext {
|
|
14
|
+
// Config
|
|
15
|
+
readonly config: {
|
|
16
|
+
readonly tier: string;
|
|
17
|
+
readonly fastGatePct: number;
|
|
18
|
+
readonly tierPct: number | null;
|
|
19
|
+
readonly anchorUserMessages: number;
|
|
20
|
+
readonly preserveRecent: number;
|
|
21
|
+
readonly auto: boolean;
|
|
22
|
+
readonly autoInline: boolean;
|
|
23
|
+
};
|
|
24
|
+
// Runtime
|
|
25
|
+
readonly rt: {
|
|
26
|
+
readonly sessionId: string;
|
|
27
|
+
readonly persistedThisSession: boolean;
|
|
28
|
+
readonly lastCheckpointId: string | undefined;
|
|
29
|
+
readonly lastCompactedFrom: number;
|
|
30
|
+
readonly lastCompactedTokens: number;
|
|
31
|
+
readonly tokensSaved: number;
|
|
32
|
+
readonly compactCount: number;
|
|
33
|
+
readonly recallInjections: number;
|
|
34
|
+
readonly cacheHitTokens: number;
|
|
35
|
+
readonly dedupSkips: number;
|
|
36
|
+
readonly dedupAttempts: number;
|
|
37
|
+
};
|
|
38
|
+
// Live metrics
|
|
39
|
+
readonly pressureBand: string;
|
|
40
|
+
readonly pressure: number;
|
|
41
|
+
readonly effectiveThreshold: number;
|
|
42
|
+
readonly statusKey: string | undefined;
|
|
43
|
+
readonly lastCtxTokens: number | null;
|
|
44
|
+
readonly lastCtxPercent: number | null;
|
|
45
|
+
readonly lastCtxWindow: number;
|
|
46
|
+
readonly diagCtxFastGate: number;
|
|
47
|
+
readonly diagLiveTrimFires: number;
|
|
48
|
+
readonly diagLiveTrimReplays: number;
|
|
49
|
+
readonly errorRetryCount: number;
|
|
50
|
+
readonly consecutiveErrors: number;
|
|
51
|
+
readonly ERROR_RETRY_MAX_CONSECUTIVE: number;
|
|
52
|
+
readonly errorRetryHardStop: boolean;
|
|
53
|
+
// R7 (retry redesign): session-global cap + poisoned-context counters.
|
|
54
|
+
readonly sessionRetryCount: number;
|
|
55
|
+
readonly sessionRetryMax: number;
|
|
56
|
+
readonly poisonedCount: number;
|
|
57
|
+
readonly activeAgents: number;
|
|
58
|
+
readonly currentTurn: number;
|
|
59
|
+
readonly currentModel: { providerName: string | null; modelId: string; provider: string; inputRate: number; outputRate: number } | null | undefined;
|
|
60
|
+
// Store stats (precomputed by caller)
|
|
61
|
+
readonly st: { checkpointCount: number; totalTokenEstimate: number; originalTokens: number; tokensSaved: number; injectedCount: number; dedupHitRate: number; storageDedupRate: number; dedupAttempts: number; dedupCollapsed: number };
|
|
62
|
+
readonly repo: { checkpointCount: number; totalTokenEstimate: number; originalTokens: number; tokensSaved: number; sessionCount: number; dedupAttempts: number; dedupCollapsed: number; storageDedupRate: number };
|
|
63
|
+
readonly di: { regionsRetained: number; compressedOriginalBytes: number; duplicatesCollapsed: number; bytesPermanentlyDeleted: number };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ---------------------------------------------------------- buildDashboardSnapshot
|
|
67
|
+
|
|
68
|
+
/** Build the DashboardSnapshot object from precomputed store/live metrics.
|
|
69
|
+
* Pure — no I/O, no side effects. */
|
|
70
|
+
export function buildDashboardSnapshot(ctx: SnapshotBuildContext): DashboardSnapshot {
|
|
71
|
+
const armed = (ctx.lastCtxTokens ?? 0) >= ctx.effectiveThreshold * ctx.config.fastGatePct;
|
|
72
|
+
const ready = armed && (ctx.lastCtxTokens ?? 0) >= ctx.effectiveThreshold;
|
|
73
|
+
return {
|
|
74
|
+
version: 1,
|
|
75
|
+
updatedAt: new Date().toISOString(),
|
|
76
|
+
tier: ctx.pressureBand,
|
|
77
|
+
presetTier: ctx.config.tier,
|
|
78
|
+
pressure: ctx.pressure,
|
|
79
|
+
config: {
|
|
80
|
+
fastGatePct: ctx.config.fastGatePct,
|
|
81
|
+
thresholdTokens: ctx.effectiveThreshold,
|
|
82
|
+
tierPct: ctx.config.tierPct,
|
|
83
|
+
effectiveThresholdPct: ctx.config.tierPct != null ? ctx.config.tierPct * 100 : null,
|
|
84
|
+
anchorUserMessages: ctx.config.anchorUserMessages,
|
|
85
|
+
preserveRecent: ctx.config.preserveRecent,
|
|
86
|
+
auto: ctx.config.auto,
|
|
87
|
+
autoInline: ctx.config.autoInline,
|
|
88
|
+
},
|
|
89
|
+
session: {
|
|
90
|
+
id: ctx.rt.sessionId,
|
|
91
|
+
state: ctx.statusKey ?? "idle",
|
|
92
|
+
persistedThisSession: ctx.rt.persistedThisSession,
|
|
93
|
+
lastCheckpointId: ctx.rt.lastCheckpointId ?? null,
|
|
94
|
+
lastCompactedFrom: ctx.rt.lastCompactedFrom,
|
|
95
|
+
lastCompactedTokens: ctx.rt.lastCompactedTokens,
|
|
96
|
+
dedupSkips: ctx.rt.dedupSkips,
|
|
97
|
+
dedupAttempts: ctx.rt.dedupAttempts,
|
|
98
|
+
},
|
|
99
|
+
context: {
|
|
100
|
+
tokens: ctx.lastCtxTokens,
|
|
101
|
+
percent: ctx.lastCtxPercent,
|
|
102
|
+
contextWindow: ctx.lastCtxWindow,
|
|
103
|
+
},
|
|
104
|
+
trigger: {
|
|
105
|
+
armed,
|
|
106
|
+
ready,
|
|
107
|
+
currentTokens: ctx.lastCtxTokens,
|
|
108
|
+
thresholdTokens: ctx.effectiveThreshold,
|
|
109
|
+
fastGatePct: ctx.config.fastGatePct,
|
|
110
|
+
tierPct: ctx.config.tierPct,
|
|
111
|
+
effectiveThresholdPct: ctx.config.tierPct != null ? ctx.config.tierPct * 100 : null,
|
|
112
|
+
},
|
|
113
|
+
store: ctx.st,
|
|
114
|
+
crew: {
|
|
115
|
+
activeAgents: ctx.activeAgents,
|
|
116
|
+
currentTurn: ctx.currentTurn,
|
|
117
|
+
},
|
|
118
|
+
repo: ctx.repo,
|
|
119
|
+
compression: {
|
|
120
|
+
session: {
|
|
121
|
+
tokensIn: ctx.rt.tokensSaved + (ctx.st.totalTokenEstimate - ctx.st.originalTokens),
|
|
122
|
+
tokensOut: ctx.st.totalTokenEstimate,
|
|
123
|
+
tokensFreed: ctx.rt.tokensSaved,
|
|
124
|
+
compressionPct: ctx.rt.tokensSaved / Math.max(1, ctx.rt.tokensSaved + (ctx.st.totalTokenEstimate - ctx.st.originalTokens)),
|
|
125
|
+
dedupPct: ctx.rt.dedupAttempts > 0 ? ctx.rt.dedupSkips / ctx.rt.dedupAttempts : 0,
|
|
126
|
+
},
|
|
127
|
+
repo: {
|
|
128
|
+
tokensIn: ctx.repo.tokensSaved + (ctx.repo.totalTokenEstimate - ctx.repo.originalTokens),
|
|
129
|
+
tokensOut: ctx.repo.totalTokenEstimate,
|
|
130
|
+
tokensFreed: ctx.repo.tokensSaved,
|
|
131
|
+
compressionPct: ctx.repo.tokensSaved / Math.max(1, ctx.repo.tokensSaved + (ctx.repo.totalTokenEstimate - ctx.repo.originalTokens)),
|
|
132
|
+
dedupPct: ctx.repo.dedupAttempts > 0 ? ctx.repo.dedupCollapsed / ctx.repo.dedupAttempts : 0,
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
integrity: ctx.di,
|
|
136
|
+
cacheHits: {
|
|
137
|
+
session: ctx.rt.dedupSkips + ctx.rt.recallInjections,
|
|
138
|
+
total: ctx.st.dedupCollapsed + ctx.st.injectedCount,
|
|
139
|
+
sessionTokensSaved: ctx.rt.cacheHitTokens,
|
|
140
|
+
totalTokensSaved: ctx.st.dedupCollapsed > 0 ? ctx.st.dedupCollapsed * 100 : 0,
|
|
141
|
+
},
|
|
142
|
+
compacts: {
|
|
143
|
+
session: ctx.rt.compactCount,
|
|
144
|
+
total: ctx.st.checkpointCount,
|
|
145
|
+
},
|
|
146
|
+
timeSaved: {
|
|
147
|
+
compact: {
|
|
148
|
+
sessionSec: ctx.rt.tokensSaved / 1000,
|
|
149
|
+
totalSec: ctx.repo.tokensSaved / 1000,
|
|
150
|
+
},
|
|
151
|
+
cacheHit: {
|
|
152
|
+
sessionSec: ctx.rt.cacheHitTokens / 1000,
|
|
153
|
+
totalSec: (ctx.st.dedupCollapsed * 100) / 1000,
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
model: ctx.currentModel
|
|
157
|
+
? {
|
|
158
|
+
name: ctx.currentModel.modelId,
|
|
159
|
+
provider: ctx.currentModel.provider,
|
|
160
|
+
providerName: ctx.currentModel.providerName ?? ctx.currentModel.provider,
|
|
161
|
+
inputRate: ctx.currentModel.inputRate,
|
|
162
|
+
outputRate: ctx.currentModel.outputRate,
|
|
163
|
+
}
|
|
164
|
+
: undefined,
|
|
165
|
+
diag: {
|
|
166
|
+
ctxFastGate: ctx.diagCtxFastGate,
|
|
167
|
+
liveTrimFires: ctx.diagLiveTrimFires,
|
|
168
|
+
liveTrimReplays: ctx.diagLiveTrimReplays,
|
|
169
|
+
},
|
|
170
|
+
retries: {
|
|
171
|
+
errorRetryCount: ctx.errorRetryCount,
|
|
172
|
+
consecutiveErrors: ctx.consecutiveErrors,
|
|
173
|
+
maxConsecutiveErrors: ctx.ERROR_RETRY_MAX_CONSECUTIVE,
|
|
174
|
+
errorRetryHardStop: ctx.errorRetryHardStop,
|
|
175
|
+
// R7 (retry redesign): additive session-cap + poisoned-context counters.
|
|
176
|
+
sessionRetryCount: ctx.sessionRetryCount,
|
|
177
|
+
sessionMax: ctx.sessionRetryMax,
|
|
178
|
+
poisonedCount: ctx.poisonedCount,
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* effects.ts — extracted effect/flare/ticker logic for MegaRuntime.
|
|
3
|
+
*
|
|
4
|
+
* Pure implementations of ambient effects, flare arming, tier-trace callback,
|
|
5
|
+
* and the recall/activity ticker. Each function takes a typed context object so
|
|
6
|
+
* the class methods in state.ts become thin one-line delegates.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
10
|
+
import { C } from "./widget.js";
|
|
11
|
+
import type { TickerEntry } from "./widget.js";
|
|
12
|
+
|
|
13
|
+
// ---------------------------------------------------------------------- types
|
|
14
|
+
|
|
15
|
+
export interface EffectsContext {
|
|
16
|
+
// Effect state
|
|
17
|
+
activeEffect: { type: "pulse" | "flash"; role: "accent" | "mega" | "red"; startedAt: number; durationMs: number } | null;
|
|
18
|
+
// Flare state
|
|
19
|
+
megaCacheFlare: boolean;
|
|
20
|
+
megaCacheFlarePct: number;
|
|
21
|
+
achievementFlare: boolean;
|
|
22
|
+
achievementFlareTitles: string[];
|
|
23
|
+
// Tier trace
|
|
24
|
+
tierTrace: string | undefined;
|
|
25
|
+
// Ticker
|
|
26
|
+
ticker: TickerEntry[];
|
|
27
|
+
readonly TICKER_MAX: number;
|
|
28
|
+
lastActivityAt: number;
|
|
29
|
+
// Callback — the tier callback calls snapshot() to trigger a widget refresh
|
|
30
|
+
snapshot(ctx: ExtensionContext): void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// --------------------------------------------------------------- setEffect
|
|
34
|
+
|
|
35
|
+
/** v0.8.3: arm an ambient border effect (animated pulse/flash on the panel
|
|
36
|
+
* borders). Replaces any in-flight effect (last call wins). The widget reads
|
|
37
|
+
* activeEffect each frame and computes the per-frame phase from startedAt vs
|
|
38
|
+
* Date.now(); it renders '' once the window elapses. */
|
|
39
|
+
export function setEffectImpl(
|
|
40
|
+
ctx: EffectsContext,
|
|
41
|
+
type: "pulse" | "flash",
|
|
42
|
+
role: "accent" | "mega" | "red",
|
|
43
|
+
durationMs: number,
|
|
44
|
+
): void {
|
|
45
|
+
ctx.activeEffect = { type, role, startedAt: Date.now(), durationMs };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// -------------------------------------------------------- armMegaCacheFlare
|
|
49
|
+
|
|
50
|
+
/** S33: arm the transient MEGA CACHE flare so the next snapshot() copies it
|
|
51
|
+
* into widgetData and the widget renders the oopsie gag for one cycle.
|
|
52
|
+
* v0.8.3: also arm a 'flash' ambient effect on the panel borders (mega
|
|
53
|
+
* color) for 1.2s. */
|
|
54
|
+
export function armMegaCacheFlareImpl(ctx: EffectsContext, peakPct: number): void {
|
|
55
|
+
ctx.megaCacheFlare = true;
|
|
56
|
+
ctx.megaCacheFlarePct = peakPct;
|
|
57
|
+
setEffectImpl(ctx, "flash", "mega", 1200);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ------------------------------------------------------ armAchievementFlare
|
|
61
|
+
|
|
62
|
+
/** S35: arm the transient achievement-unlock flare with the newly-unlocked
|
|
63
|
+
* titles so the next snapshot() copies them into widgetData and the widget
|
|
64
|
+
* renders the one-time unlock toast for one render cycle.
|
|
65
|
+
* v0.8.3: also arm a 'pulse' ambient effect on the panel borders (accent
|
|
66
|
+
* color) for 2s to celebrate the unlock. */
|
|
67
|
+
export function armAchievementFlareImpl(ctx: EffectsContext, titles: string[]): void {
|
|
68
|
+
ctx.achievementFlare = true;
|
|
69
|
+
ctx.achievementFlareTitles = titles;
|
|
70
|
+
setEffectImpl(ctx, "pulse", "accent", 2000);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// -------------------------------------------------------- makeTierCallback
|
|
74
|
+
|
|
75
|
+
/** Build the sync onTier callback that paints the live per-tier trace. */
|
|
76
|
+
export function makeTierCallbackImpl(
|
|
77
|
+
ctx: EffectsContext,
|
|
78
|
+
ectx: ExtensionContext,
|
|
79
|
+
): (ev: {
|
|
80
|
+
tier: "L0" | "L1" | "L2" | "new";
|
|
81
|
+
status: "scanning" | "deduped" | "passed" | "stored";
|
|
82
|
+
detail?: string;
|
|
83
|
+
}) => void {
|
|
84
|
+
const order: Array<"L0" | "L1" | "L2" | "new"> = ["L0", "L1", "L2", "new"];
|
|
85
|
+
const seen = new Map<string, string>();
|
|
86
|
+
const glyph = (status: string) =>
|
|
87
|
+
status === "deduped"
|
|
88
|
+
? `${C.green}✓${C.reset}`
|
|
89
|
+
: status === "passed"
|
|
90
|
+
? `${C.dim}○${C.reset}`
|
|
91
|
+
: status === "scanning"
|
|
92
|
+
? `${C.amber}…${C.reset}`
|
|
93
|
+
: `${C.cyan}●${C.reset}`;
|
|
94
|
+
return (ev) => {
|
|
95
|
+
const label =
|
|
96
|
+
ev.tier === "new"
|
|
97
|
+
? `${C.cyan}stored${C.reset}`
|
|
98
|
+
: `${ev.tier} ${glyph(ev.status)}` +
|
|
99
|
+
(ev.detail ? ` ${C.gray}(${ev.detail})${C.reset}` : "");
|
|
100
|
+
// Show the most recent outcome per tier (collapses re-fires).
|
|
101
|
+
seen.set(ev.tier, label);
|
|
102
|
+
const show: string[] = [];
|
|
103
|
+
for (const t of order) if (seen.has(t)) show.push(seen.get(t)!);
|
|
104
|
+
ctx.tierTrace = `${C.teal}⚙${C.reset} ${show.join(` ${C.gray}→${C.reset} `)}`;
|
|
105
|
+
ctx.lastActivityAt = Date.now();
|
|
106
|
+
try {
|
|
107
|
+
ctx.snapshot(ectx);
|
|
108
|
+
} catch {
|
|
109
|
+
/* non-fatal */
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// -------------------------------------------------------------- pushTicker
|
|
115
|
+
|
|
116
|
+
/** Phase 3 — recall/activity ticker ring buffer.
|
|
117
|
+
* Dedupe consecutive identical entries — skip the append when the last
|
|
118
|
+
* entry's text matches, so a re-fired compact/recall/dedup event doesn't
|
|
119
|
+
* flood the ring (keeps it at TICKER_MAX for real variety). `at` is NOT
|
|
120
|
+
* refreshed on a skip (the original event time stands). */
|
|
121
|
+
export function pushTickerImpl(ctx: EffectsContext, text: string): void {
|
|
122
|
+
if (ctx.ticker[ctx.ticker.length - 1]?.text === text) {
|
|
123
|
+
ctx.lastActivityAt = Date.now();
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
ctx.ticker.push({ text, at: Date.now() });
|
|
127
|
+
while (ctx.ticker.length > ctx.TICKER_MAX) ctx.ticker.shift();
|
|
128
|
+
ctx.lastActivityAt = Date.now();
|
|
129
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* engine-view.ts — extracted `MegaRuntime.engineView()`: the pi→engine message
|
|
3
|
+
* adapter passthrough. A one-liner in its own module per the maximal-split
|
|
4
|
+
* convention.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { AgentMessage } from "@earendil-works/pi-agent-core";
|
|
8
|
+
import { toEngineMessages } from "../../src/adapt.js";
|
|
9
|
+
|
|
10
|
+
// ------------------------------------------------------------------ engineView
|
|
11
|
+
|
|
12
|
+
/** Convert the messages pi hands us in the `context` event into the engine view. */
|
|
13
|
+
export function engineViewImpl(
|
|
14
|
+
messages: AgentMessage[],
|
|
15
|
+
): ReturnType<typeof toEngineMessages> {
|
|
16
|
+
return toEngineMessages(messages);
|
|
17
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* game-state.ts — extracted game-state management for MegaRuntime.
|
|
3
|
+
*
|
|
4
|
+
* This module keeps the game-state logic cohesive while leaving the class fields
|
|
5
|
+
* and public method signatures in state.ts. The original methods become thin
|
|
6
|
+
* delegates so runtime behavior stays byte-for-byte identical.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
10
|
+
import { watch } from "node:fs";
|
|
11
|
+
import { getGameState, type GameState } from "../../src/store/sqlite.js";
|
|
12
|
+
import { getTheme } from "../../src/config/themes.js";
|
|
13
|
+
import { disposePerf, type PerfContext } from "./perf.js";
|
|
14
|
+
|
|
15
|
+
export interface GameWatcherLike {
|
|
16
|
+
close(): void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface GameStateContext {
|
|
20
|
+
readonly currentStateDir: string;
|
|
21
|
+
cachedGameState: GameState | undefined;
|
|
22
|
+
gameStateBump: number;
|
|
23
|
+
gameStateWatcher?: GameWatcherLike;
|
|
24
|
+
gameStateWatchDir?: string;
|
|
25
|
+
lastWidgetCtx?: ExtensionContext;
|
|
26
|
+
widgetData: import("./widget.js").WidgetData | null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface GameStateViewApi {
|
|
30
|
+
renderWidget(ctx: ExtensionContext): void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function getCachedGameStateImpl(self: GameStateContext): GameState {
|
|
34
|
+
if (!self.cachedGameState) {
|
|
35
|
+
try {
|
|
36
|
+
self.cachedGameState = getGameState(self.currentStateDir);
|
|
37
|
+
} catch {
|
|
38
|
+
self.cachedGameState = {
|
|
39
|
+
game_mode_on: false,
|
|
40
|
+
theme: "transparent",
|
|
41
|
+
tui_display_mode: "full",
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return self.cachedGameState;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function refreshWidgetGameStateImpl(
|
|
49
|
+
self: GameStateContext,
|
|
50
|
+
view: GameStateViewApi,
|
|
51
|
+
ctx: ExtensionContext,
|
|
52
|
+
): void {
|
|
53
|
+
if (!self.widgetData || !ctx) return;
|
|
54
|
+
const gs = getCachedGameStateImpl(self);
|
|
55
|
+
self.widgetData.gameMode = gs.game_mode_on;
|
|
56
|
+
self.widgetData.theme = getTheme(gs.theme) ? gs.theme : "transparent";
|
|
57
|
+
self.widgetData.tuiMode = gs.tui_display_mode;
|
|
58
|
+
view.renderWidget(ctx);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function bumpGameStateImpl(self: GameStateContext): void {
|
|
62
|
+
self.cachedGameState = undefined;
|
|
63
|
+
self.gameStateBump++;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ------------------------------------------------------------- disposeRuntime
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The slice of `MegaRuntime` dispose() touches: the S32 fs.watch game-state
|
|
70
|
+
* watcher (GameStateContext) plus the v0.8.8 perf cpu/mem sampling interval
|
|
71
|
+
* (PerfContext). `MegaRuntime` satisfies this structurally.
|
|
72
|
+
*/
|
|
73
|
+
export interface DisposeRuntimeContext extends GameStateContext, PerfContext {}
|
|
74
|
+
|
|
75
|
+
/** S32: release the fs.watch game-state watcher AND stop the v0.8.8 perf
|
|
76
|
+
* sampling interval. Called when the runtime is torn down (no existing
|
|
77
|
+
* dispose path — the process exit reclaims the fd, but explicit close is
|
|
78
|
+
* correct for any in-process reload / test reuse). Extracted from
|
|
79
|
+
* MegaRuntime.dispose(); the class keeps a thin delegate. */
|
|
80
|
+
export function disposeRuntimeImpl(self: DisposeRuntimeContext): void {
|
|
81
|
+
if (self.gameStateWatcher) {
|
|
82
|
+
try { self.gameStateWatcher.close(); } catch { /* non-fatal */ }
|
|
83
|
+
self.gameStateWatcher = undefined;
|
|
84
|
+
self.gameStateWatchDir = undefined;
|
|
85
|
+
}
|
|
86
|
+
disposePerf(self);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function ensureGameStateWatcherImpl(self: GameStateContext, view: GameStateViewApi): void {
|
|
90
|
+
if (self.gameStateWatcher && self.gameStateWatchDir === self.currentStateDir) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (self.gameStateWatcher) {
|
|
94
|
+
try {
|
|
95
|
+
self.gameStateWatcher.close();
|
|
96
|
+
} catch {
|
|
97
|
+
/* non-fatal */
|
|
98
|
+
}
|
|
99
|
+
self.gameStateWatcher = undefined;
|
|
100
|
+
self.gameStateWatchDir = undefined;
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
// Watch the state DIR (not just sqlite.db) and filter by filename.
|
|
104
|
+
// Why: the store is WAL-mode (openStore sets PRAGMA journal_mode=WAL).
|
|
105
|
+
// Cross-process writes (dashboard server child) append to sqlite.db-wal
|
|
106
|
+
// and do NOT modify sqlite.db until a checkpoint — and a long-lived
|
|
107
|
+
// parent connection (VectorStore + dashboard readers) keeps the WAL
|
|
108
|
+
// uncheckpointed, so a watcher on sqlite.db alone never fires and
|
|
109
|
+
// cachedGameState stays stale (theme stuck after a dashboard edit).
|
|
110
|
+
// Watching the dir + matching sqlite.db* catches the main db, the -wal
|
|
111
|
+
// sidecar, and -shm, so the memo evicts on any cross-process write. The
|
|
112
|
+
// filter also excludes events.log / *.log noise in the same dir.
|
|
113
|
+
self.gameStateWatcher = watch(
|
|
114
|
+
self.currentStateDir,
|
|
115
|
+
(_eventType, filename) => {
|
|
116
|
+
if (typeof filename === "string" && filename.startsWith("sqlite.db")) {
|
|
117
|
+
self.cachedGameState = undefined;
|
|
118
|
+
self.gameStateBump++;
|
|
119
|
+
// P2: force a widget re-render so a dashboard-made theme/toggle/
|
|
120
|
+
// tui-mode change reflects in the live TUI immediately, even when
|
|
121
|
+
// pi is idle (no context event to drive snapshot()). Use the
|
|
122
|
+
// LIGHTWEIGHT refreshWidgetGameState() — NOT the full snapshot():
|
|
123
|
+
// snapshot() recomputes 6 sync SQLite opens + writes dashboard.json
|
|
124
|
+
// + writes to the store, and those store writes RETRIGGER this
|
|
125
|
+
// same fs.watch callback (it fires on every sqlite.db* write) →
|
|
126
|
+
// re-entrant thrash → 190s test timeout under mega-compact.test.js
|
|
127
|
+
// / mega-teamrun.test.js. The lightweight path re-reads ONLY the
|
|
128
|
+
// game_state row and patches the three game-mode fields on the
|
|
129
|
+
// existing widgetData, then re-registers the factory via
|
|
130
|
+
// renderWidget() — it writes nothing to the store or
|
|
131
|
+
// dashboard.json, so it cannot retrigger itself. Guard: skip until
|
|
132
|
+
// the first snapshot stashed a ctx (no widget registered yet →
|
|
133
|
+
// nothing to refresh). Non-fatal: next context event re-snapshots.
|
|
134
|
+
const ctx = self.lastWidgetCtx;
|
|
135
|
+
if (ctx) {
|
|
136
|
+
try {
|
|
137
|
+
refreshWidgetGameStateImpl(self, view, ctx);
|
|
138
|
+
} catch {
|
|
139
|
+
/* non-fatal */
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
);
|
|
145
|
+
self.gameStateWatchDir = self.currentStateDir;
|
|
146
|
+
} catch {
|
|
147
|
+
/* non-fatal: missing dir / platform issue — next snapshot re-queries */
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* get-state-dir.ts — extracted `MegaRuntime.getStateDir()` (S21). A one-liner
|
|
3
|
+
* in its own module per the maximal-split convention: no method bodies left in
|
|
4
|
+
* runtime.ts.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// ---------------------------------------------------------------------- types
|
|
8
|
+
|
|
9
|
+
/** The slice of `MegaRuntime` getStateDir reads. */
|
|
10
|
+
export interface GetStateDirContext {
|
|
11
|
+
readonly currentStateDir: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// --------------------------------------------------------------- getStateDir
|
|
15
|
+
|
|
16
|
+
/** S21: state dir of the currently bound repo (where memories live). */
|
|
17
|
+
export function getStateDirImpl(self: GetStateDirContext): string {
|
|
18
|
+
return self.currentStateDir;
|
|
19
|
+
}
|
|
@@ -44,10 +44,34 @@ export interface SessionRuntime {
|
|
|
44
44
|
cacheHitTokens: number; // tokens saved via cache hits (dedup + recall) this session
|
|
45
45
|
lengthStopPending: boolean; // S28: set on turn_end when stopReason==='length'
|
|
46
46
|
errorRetryCount: number; // S38: consecutive error turns, reset on success/turn_start
|
|
47
|
-
errorRetryUntil: number; // S38: wall-clock ms
|
|
47
|
+
errorRetryUntil: number; // S38: wall-clock ms before which the next nudge is suppressed (R1: now gating)
|
|
48
48
|
// S38.6: circuit-breaker state — consecutive error turns across the session.
|
|
49
49
|
// When this exceeds maxConsecutiveErrors, the extension stops retrying.
|
|
50
50
|
consecutiveErrors: number; // reset to 0 on successful turn_end
|
|
51
|
+
// R1 (retry redesign): in-flight nudge dedup. A nudge queued via
|
|
52
|
+
// deliverAs:'followUp' must not be re-sent until it has been consumed by an
|
|
53
|
+
// actual new agent turn (turn_start) or superseded by a successful turn.
|
|
54
|
+
// Without this, a fast-erroring provider + a per-turn nudge → N nudges queue
|
|
55
|
+
// up and pi dispatches N retry turns, each re-submitting the same failing
|
|
56
|
+
// prompt (the 2026-07-28 incident: ~60-message spam storm).
|
|
57
|
+
lastErrorRetryAt: number; // wall-clock ms of the last fired nudge (diagnostics)
|
|
58
|
+
retryNudgePending: boolean; // true while a queued nudge awaits consumption
|
|
59
|
+
// R2: session-global cap. Total S38 nudges per session across ALL bursts;
|
|
60
|
+
// independent of the per-burst max and the circuit breaker. Hitting it is
|
|
61
|
+
// terminal for the session — the extension stops nudging entirely.
|
|
62
|
+
errorRetrySessionCount: number; // nudges fired this session (across all bursts)
|
|
63
|
+
// R3: poisoned-context detection state. The classifier is stateless; the
|
|
64
|
+
// stateful "repeated identical error text" signal is tracked here and upgrades
|
|
65
|
+
// a 'transient' classification to 'poisoned-context' after the threshold.
|
|
66
|
+
lastErrorText: string | undefined; // normalized error signature from the last error turn
|
|
67
|
+
errorTextRepeatCount: number; // consecutive count of identical error signatures
|
|
68
|
+
// R3b: one-per-session /clear advise message throttle.
|
|
69
|
+
poisonedAdviseSent: boolean; // true after the advise message fires once
|
|
70
|
+
// R3c: one guarded compact per error signature (avoids re-compacting the
|
|
71
|
+
// same poisoned region repeatedly).
|
|
72
|
+
poisonedCompactSignatures: Set<string>; // signatures already attempted a compact for
|
|
73
|
+
// R7: poisoned-context event counter for the dashboard.
|
|
74
|
+
poisonedCount: number;
|
|
51
75
|
}
|
|
52
76
|
|
|
53
77
|
// ── ownVersion ─────────────────────────────────────────────────────────────
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* perf.ts — extracted perf-interval sampling for MegaRuntime.
|
|
3
|
+
*
|
|
4
|
+
* The 5s cpu/mem sampling interval + its teardown. Keeps the setInterval
|
|
5
|
+
* boilerplate out of the main state.ts class body.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { recordPerfSample } from "../../src/store/sqlite.js";
|
|
9
|
+
|
|
10
|
+
// ---------------------------------------------------------------------- types
|
|
11
|
+
|
|
12
|
+
export interface PerfContext {
|
|
13
|
+
readonly currentStateDir: string;
|
|
14
|
+
perfCpuInterval: NodeJS.Timeout | null;
|
|
15
|
+
perfCpuBaseline: { user: number; sys: number } | undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// ---------------------------------------------------------- ensurePerfInterval
|
|
19
|
+
|
|
20
|
+
/** v0.8.8: (re)start the 5s cpu/mem sampling interval (idempotent). One per
|
|
21
|
+
* MegaRuntime; cleared in disposePerf(). Samples process.cpuUsage() (user/sys
|
|
22
|
+
* delta vs the last tick → ms) + process.memoryUsage() (rss/heap → MB) and
|
|
23
|
+
* records them as perf_samples. unref'd so it never keeps the process alive
|
|
24
|
+
* on its own. Non-fatal: any failure is swallowed (instrumentation never
|
|
25
|
+
* blocks the agent). PREVENT-PI-004: local process stats + SQLite only. */
|
|
26
|
+
export function ensurePerfIntervalImpl(ctx: PerfContext): void {
|
|
27
|
+
if (ctx.perfCpuInterval) return;
|
|
28
|
+
ctx.perfCpuBaseline = undefined; // first tick sets the baseline (no delta)
|
|
29
|
+
ctx.perfCpuInterval = setInterval(() => {
|
|
30
|
+
try {
|
|
31
|
+
const dir = ctx.currentStateDir;
|
|
32
|
+
const cpu = process.cpuUsage();
|
|
33
|
+
const mem = process.memoryUsage();
|
|
34
|
+
if (ctx.perfCpuBaseline) {
|
|
35
|
+
const du = (cpu.user - ctx.perfCpuBaseline.user) / 1000; // μs → ms
|
|
36
|
+
const ds = (cpu.system - ctx.perfCpuBaseline.sys) / 1000;
|
|
37
|
+
recordPerfSample(dir, "cpu_user_ms", Math.max(0, du));
|
|
38
|
+
recordPerfSample(dir, "cpu_sys_ms", Math.max(0, ds));
|
|
39
|
+
}
|
|
40
|
+
ctx.perfCpuBaseline = { user: cpu.user, sys: cpu.system };
|
|
41
|
+
recordPerfSample(dir, "rss_mb", mem.rss / 1_000_000);
|
|
42
|
+
recordPerfSample(dir, "heap_mb", mem.heapUsed / 1_000_000);
|
|
43
|
+
} catch {
|
|
44
|
+
/* non-fatal */
|
|
45
|
+
}
|
|
46
|
+
}, 5000);
|
|
47
|
+
ctx.perfCpuInterval.unref?.();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ------------------------------------------------------------------ disposePerf
|
|
51
|
+
|
|
52
|
+
/** Stop the cpu/mem sampling interval on teardown. Re-armed lazily by
|
|
53
|
+
* ensurePerfInterval() on the next turn_start. */
|
|
54
|
+
export function disposePerf(ctx: PerfContext): void {
|
|
55
|
+
if (ctx.perfCpuInterval) {
|
|
56
|
+
clearInterval(ctx.perfCpuInterval);
|
|
57
|
+
ctx.perfCpuInterval = null;
|
|
58
|
+
ctx.perfCpuBaseline = undefined;
|
|
59
|
+
}
|
|
60
|
+
}
|