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
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
} from "../mega-runtime.js";
|
|
25
25
|
import { resolveRepoRoot, preserveRecentForPressure, type MegaConfig } from "../mega-config.js";
|
|
26
26
|
import { runRaptor } from "../../src/dedup/raptor/index.js";
|
|
27
|
+
import { isRaptorTreeFresh } from "../../src/dedup/raptor/buildHistory.js";
|
|
27
28
|
import { loadDedupConfig } from "../../src/config/dedup.js";
|
|
28
29
|
import { upsertEmbedding as indexUpsertEmbedding } from "../../src/store/vectorIndex.js";
|
|
29
30
|
import { runMemoryReview } from "./memory-review.js";
|
|
@@ -221,22 +222,33 @@ function doCompact(
|
|
|
221
222
|
embedding: cp.embedding,
|
|
222
223
|
}));
|
|
223
224
|
if (leaves.length >= 2) {
|
|
224
|
-
//
|
|
225
|
-
//
|
|
226
|
-
//
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
225
|
+
// S42D: skip the rebuild when the last build is fresh (within
|
|
226
|
+
// RAPTOR_FRESHNESS_HOURS) and the checkpoint count hasn't drifted by
|
|
227
|
+
// more than 20%. avoids re-clustering on every compaction when the
|
|
228
|
+
// tree is still representative. 0 disables (always rebuild).
|
|
229
|
+
if (
|
|
230
|
+
dd.RAPTOR_FRESHNESS_HOURS > 0 &&
|
|
231
|
+
isRaptorTreeFresh(sid, runtime.currentStateDir, dd.RAPTOR_FRESHNESS_HOURS, all.length)
|
|
232
|
+
) {
|
|
233
|
+
runtime.logger?.info("raptor_skip_fresh", { sessionId: sid });
|
|
234
|
+
} else {
|
|
235
|
+
// S25: stamp the tree with the newest checkpoint epoch so the
|
|
236
|
+
// freshness guard in raptorSearchHits can reject stale trees after a
|
|
237
|
+
// later compaction adds newer checkpoints.
|
|
238
|
+
const builtAt = all.length > 0 ? Math.max(...all.map((c) => c.timestamp)) : Date.now();
|
|
239
|
+
runRaptor(
|
|
240
|
+
leaves,
|
|
241
|
+
{
|
|
242
|
+
stateDir: runtime.currentStateDir,
|
|
243
|
+
sessionId: sid,
|
|
244
|
+
budgetMs: dd.RAPTOR_BUDGET_MS,
|
|
245
|
+
clustersPerLevel: dd.RAPTOR_CLUSTERS_PER_LEVEL,
|
|
246
|
+
consistencyThreshold: dd.RAPTOR_CONSISTENCY,
|
|
247
|
+
logger: runtime.logger,
|
|
248
|
+
builtAt: Number.isFinite(builtAt) ? builtAt : Date.now(),
|
|
249
|
+
},
|
|
250
|
+
);
|
|
251
|
+
}
|
|
240
252
|
}
|
|
241
253
|
} catch {
|
|
242
254
|
/* non-fatal: tree refresh never blocks a compaction */
|
|
@@ -10,7 +10,8 @@ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
|
10
10
|
import { sessionEntryToContextMessages } from "@earendil-works/pi-coding-agent";
|
|
11
11
|
import { recallAndInline, recallAndInlineAsync, formatRecallBlock, type RecallInjectResult } from "../../src/recall.js";
|
|
12
12
|
import { normalizeSessionId } from "../../src/store.js";
|
|
13
|
-
import { incRecallInjected, incCacheHitTokens } from "../../src/store/sqlite.js";
|
|
13
|
+
import { incRecallInjected, incCacheHitTokens, getIndexDir } from "../../src/store/sqlite.js";
|
|
14
|
+
import { ensureConversationId, recordTurn, recordTurnRecall, type RecallSource } from "../../src/store/sqlite/turns.js";
|
|
14
15
|
import {
|
|
15
16
|
type MegaRuntime,
|
|
16
17
|
C,
|
|
@@ -64,6 +65,30 @@ export function doRecall(
|
|
|
64
65
|
runtime.rt.cacheHitTokens += sumTokens;
|
|
65
66
|
incRecallInjected(result.toInject.length, runtime.currentStateDir);
|
|
66
67
|
incCacheHitTokens(sumTokens, runtime.currentStateDir);
|
|
68
|
+
// S43: record recall provenance — which checkpoints/summaries served this
|
|
69
|
+
// turn, their score + source path. Linked to the turn row written at
|
|
70
|
+
// turn_end via the conversation+turnIndex. Best-effort + non-fatal.
|
|
71
|
+
try {
|
|
72
|
+
const convId = ensureConversationId(sid, runtime.currentStateDir);
|
|
73
|
+
const turnId = recordTurn({
|
|
74
|
+
conversationId: convId,
|
|
75
|
+
sessionId: sid,
|
|
76
|
+
turnIndex: runtime.currentTurn,
|
|
77
|
+
startedAt: Date.now(),
|
|
78
|
+
}, runtime.currentStateDir);
|
|
79
|
+
recordTurnRecall(
|
|
80
|
+
turnId,
|
|
81
|
+
result.toInject.map((h) => ({
|
|
82
|
+
checkpointId: h.checkpoint.checkpointId,
|
|
83
|
+
score: h.score,
|
|
84
|
+
source: (h.raptorLevel !== undefined ? "raptor" : h.repoId ? "cross-repo" : "flat") as RecallSource,
|
|
85
|
+
raptorLevel: h.raptorLevel,
|
|
86
|
+
})),
|
|
87
|
+
runtime.currentStateDir,
|
|
88
|
+
);
|
|
89
|
+
} catch {
|
|
90
|
+
/* non-fatal: recall provenance never breaks the recall path */
|
|
91
|
+
}
|
|
67
92
|
}
|
|
68
93
|
return result;
|
|
69
94
|
}
|
|
@@ -108,7 +133,14 @@ export async function doRecallAsync(
|
|
|
108
133
|
sessionId: sid, query, limit: config.autoInlineK, source, skipInjected: true,
|
|
109
134
|
recallMaxTokens: config.recallMaxTokens, windowDedupe: config.windowDedupe,
|
|
110
135
|
liveWindow, dedupSim: config.crossRepoCosine, crossRepo: true,
|
|
111
|
-
|
|
136
|
+
// F2: resolve the machine-wide index dir via the shared resolver so the
|
|
137
|
+
// cross-repo injected-set dedup works even when MEGACOMPACT_INDEX_DIR is
|
|
138
|
+
// unset. The env var still wins when set (getIndexDir checks it first);
|
|
139
|
+
// the default (~/.mega-compact-index) is the same DB mega-commands and the
|
|
140
|
+
// dashboard read, so injection counts stay consistent. Without this, a
|
|
141
|
+
// bare `process.env` read returns undefined → cross-repo hits re-inject in
|
|
142
|
+
// every new session (the global injected-set is never consulted).
|
|
143
|
+
globalIndexDir: getIndexDir(),
|
|
112
144
|
},
|
|
113
145
|
runtime.store,
|
|
114
146
|
);
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# mega-runtime — Decomposition Tracker
|
|
2
|
+
|
|
3
|
+
This file tracks the progressive decomposition of the original `mega-runtime.ts`
|
|
4
|
+
monolith (2 600+ lines) into focused single-responsibility modules.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Current State (raptor-promotion branch)
|
|
9
|
+
|
|
10
|
+
### Completed — Phase 1: Extract pure helpers
|
|
11
|
+
|
|
12
|
+
The original `state.ts` (later `mega-runtime.ts`) has been split into:
|
|
13
|
+
|
|
14
|
+
| File | Lines | Responsibility |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| `state.ts` | 8 | **Re-export placeholder** — re-exports `MegaRuntime` from `runtime.ts` so all existing imports keep working |
|
|
17
|
+
| `runtime.ts` | ~522 | **MegaRuntime class** — the orchestrator (constructor, dispose, bindRepo, 1-line delegate methods) |
|
|
18
|
+
| `runtime-snapshot.ts` | ~289 | `snapshotImpl(ctx, ctxPi)` — the full `snapshot()` body (vector stats, threshold/armed/ready, drift, widget, dashboard.json write) |
|
|
19
|
+
| `runtime-helpers.ts` | ~119 | Extracted private helpers: `materialSigImpl`, `embedderNameImpl`, `driftStatusImpl`, `getTurnLevelImpl` + `RuntimeHelpersContext` |
|
|
20
|
+
| `snapshot.ts` | ~229 | `computeMegaSnapshot()` — pure function, no class state |
|
|
21
|
+
| `dashboard-snapshot.ts` | ~173 | `computeDashboardSnapshot()` — dashboard-specific snapshot variant |
|
|
22
|
+
| `widget.ts` | ~172 | **Thin barrel** — owns `buildWidgetLines()` + re-exports from `widget-ansi.ts`/`widget-types.ts` |
|
|
23
|
+
| `widget-ansi.ts` | ~218 | ANSI palette (`C`), `PULSE`, panel layout helpers, ambient border-effect helpers, token/time formatters |
|
|
24
|
+
| `widget-types.ts` | ~81 | `TickerEntry` + `WidgetData` interfaces (pure types, zero imports) |
|
|
25
|
+
| `effects.ts` | ~129 | Effect/flare helpers: `armMegaCacheFlareImpl`, `armAchievementFlareImpl`, `setEffectImpl`, `pushTickerImpl` |
|
|
26
|
+
| `game-state.ts` | ~125 | Game state cache: `ensureGameStateWatcherImpl`, `bumpGameStateImpl`, `refreshWidgetGameStateImpl` |
|
|
27
|
+
| `capture-model.ts` | ~101 | Model capture: `captureModelImpl` |
|
|
28
|
+
| `bind-repo.ts` | ~81 | Repo binding: `resolveRepoId`, `resolveMemoRoot` |
|
|
29
|
+
| `perf.ts` | ~60 | Perf sample helpers: `recordPerfSample`, `ensurePerfIntervalImpl`, `disposePerf` |
|
|
30
|
+
| `helpers.ts` | ~73 | Shared constants/types (`MEGA_HOME`, `DEFAULT_CONFIG`, `MegaConfig`, etc.) |
|
|
31
|
+
| `query.ts` | ~29 | Query helpers (`recentUserQuery`) |
|
|
32
|
+
|
|
33
|
+
All tests pass (the suite is built and run via `npm test` → `node scripts/run-tests.mjs`).
|
|
34
|
+
|
|
35
|
+
### Completed — Phase 2a: Split `widget.ts` (~422 → 172 + 218 + 81 lines)
|
|
36
|
+
|
|
37
|
+
The original monolithic `widget.ts` has been decomposed into three files with a
|
|
38
|
+
preserved barrel so **no consumer import changed**:
|
|
39
|
+
|
|
40
|
+
| New file | Content | Source of the code |
|
|
41
|
+
|---|---|---|
|
|
42
|
+
| `widget-types.ts` | `TickerEntry`, `WidgetData` interfaces | moved verbatim from the old `widget.ts` |
|
|
43
|
+
| `widget-ansi.ts` | `C` palette, `PULSE`, `DEFAULT_PANEL_BG`, `panelBgFor`, `themeAnsi`, `sgrReset`, `wrapLine`, `panelLine`, `panelBar`, `EFFECT_BASE`, `effectBorderSgr`, `effectBar`, `fmtTokens`, `ramp`, `sinceCompactStr` | moved verbatim; the previously-private helpers are now `export`ed |
|
|
44
|
+
| `widget.ts` (slimmed) | `buildWidgetLines()` + `export { … } from "./widget-ansi.js"` + `export type { … } from "./widget-types.js"` | the render function stays here; barrel re-exports keep `./widget.js` resolving `C`, `TickerEntry`, `WidgetData`, `buildWidgetLines` |
|
|
45
|
+
|
|
46
|
+
**Compatibility:** the only names the old `widget.ts` exported were `C`,
|
|
47
|
+
`TickerEntry`, `WidgetData`, `buildWidgetLines`; every other helper was a
|
|
48
|
+
non-exported `const`/`function`. The new `widget.ts` re-exports **all** of them
|
|
49
|
+
(additive — more public surface, no removed names), and there is **zero name
|
|
50
|
+
collision** with the sibling barrels (`helpers.ts`/`state.ts`/`query.ts`), so the
|
|
51
|
+
`extensions/mega-runtime.ts` `export * from "./mega-runtime/widget.js"` keeps
|
|
52
|
+
resolving identically. Verified: `tsc --noEmit` passes; `widget.test.ts`
|
|
53
|
+
(S31 matrix + ambient-effect + footer-stability + achievement-flare) green.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Completed — Phase 2b: Extract `runtime.ts` private helpers (~783 → ~754 lines + 120)
|
|
58
|
+
|
|
59
|
+
The four pure/instance helpers were extracted from the `MegaRuntime` class into
|
|
60
|
+
`runtime-helpers.ts` following the established context-interface + free-function
|
|
61
|
+
+ thin-delegate pattern (same as `effects.ts` / `game-state.ts` / `capture-model.ts`
|
|
62
|
+
/ `bind-repo.ts` / `perf.ts`).
|
|
63
|
+
|
|
64
|
+
| New file | Content | Source of the code |
|
|
65
|
+
|---|---|---|
|
|
66
|
+
| `runtime-helpers.ts` | `RuntimeHelpersContext` interface + `materialSigImpl(ctx)`, `embedderNameImpl()`, `driftStatusImpl(ctx)`, `getTurnLevelImpl(ctx)` | moved verbatim from the old private methods |
|
|
67
|
+
| `runtime.ts` (slightly slimmer) | the four methods are now 1-line delegates (`return *Impl(this)`); `driftCache` is now public so `MegaRuntime` satisfies `RuntimeHelpersContext` structurally | — |
|
|
68
|
+
|
|
69
|
+
**What moved:**
|
|
70
|
+
- `materialSig()` → `materialSigImpl(ctx)` — pure over all-public fields.
|
|
71
|
+
- `embedderName()` → `embedderNameImpl()` — trivially pure (reads `process.env`).
|
|
72
|
+
- `driftStatus()` → `driftStatusImpl(ctx)` — required `driftCache` to become
|
|
73
|
+
public (one-token change; internal state, not an API contract). The
|
|
74
|
+
`detectCrossRepoDrift` import moved into `runtime-helpers.ts` (was only used
|
|
75
|
+
there) and was dropped from `runtime.ts`.
|
|
76
|
+
- `getTurnLevel()` → `getTurnLevelImpl(ctx)` — the `turnLevel` import moved into
|
|
77
|
+
`runtime-helpers.ts` and was dropped from `runtime.ts`.
|
|
78
|
+
|
|
79
|
+
All call sites (`this.materialSig()`, `this.embedderName()`, `this.driftStatus()`,
|
|
80
|
+
`this.getTurnLevel()`) are unchanged — the thin in-class delegates preserve the
|
|
81
|
+
existing API. Verified: `tsc --noEmit` passes; full suite green.
|
|
82
|
+
|
|
83
|
+
## Completed — Phase 2c: Extract `snapshot()` body to `runtime-snapshot.ts` (~783 → ~522 lines + 289)
|
|
84
|
+
|
|
85
|
+
The `snapshot()` method — the single largest method on `MegaRuntime` — was
|
|
86
|
+
extracted into `runtime-snapshot.ts` as `snapshotImpl(self, ctx?)`, following
|
|
87
|
+
the same context-interface + free-function + thin-delegate pattern.
|
|
88
|
+
|
|
89
|
+
| New file | Content | Source of the code |
|
|
90
|
+
|---|---|---|
|
|
91
|
+
| `runtime-snapshot.ts` | `RuntimeSnapshotContext` interface + `snapshotImpl(self, ctx?)` — the full snapshot body: material-sig gate, vector stats, effective threshold/armed/ready, drift status, `computeMegaSnapshot` → `widgetData` + `renderWidget`, `writeFileSync(dashboard.json)`, perf sample, ticker push, flare arming | moved verbatim from the old `snapshot()` method |
|
|
92
|
+
| `runtime.ts` (slimmed) | `snapshot(ctx?)` is now a 1-line delegate (`return snapshotImpl(this, ctx)`); `lastSnapshotSig` is now public so `MegaRuntime` satisfies `RuntimeSnapshotContext` structurally | — |
|
|
93
|
+
|
|
94
|
+
**What moved:**
|
|
95
|
+
- The `computeMegaSnapshot`, `buildDashboardSnapshot`, `detectCrossRepoDrift`,
|
|
96
|
+
`vectorStats`/`vectorRepoStats`/`vectorDataInvariant`, `recordPerfSample`,
|
|
97
|
+
`recordSessionHeartbeat`, `appendTokenSample`, and `latestModelSnapshot`
|
|
98
|
+
imports moved into `runtime-snapshot.ts` (none are used by `runtime.ts`
|
|
99
|
+
anymore) — `runtime.ts` only imports `VectorStore` (type) now.
|
|
100
|
+
- `lastSnapshotSig` became public (one-token change; internal state, not an
|
|
101
|
+
API contract) so `MegaRuntime` satisfies `RuntimeSnapshotContext`.
|
|
102
|
+
|
|
103
|
+
All call sites (the `before_agent_start`/`context`/`compact` handlers that
|
|
104
|
+
call `this.snapshot(ctx)`) are unchanged — the thin in-class delegate
|
|
105
|
+
preserves the existing API. Verified: `tsc --noEmit` passes; full suite
|
|
106
|
+
green (649 tests across 61 files).
|
|
107
|
+
|
|
108
|
+
## Completed — Phase 2d: Maximal split of `runtime.ts` (~522 → ~437 lines, zero method bodies)
|
|
109
|
+
|
|
110
|
+
The maximal split moves **every** remaining method body in `runtime.ts` into
|
|
111
|
+
its own single-responsibility module, leaving the class as field declarations,
|
|
112
|
+
the constructor, and 1-line delegates only. This completes the decomposition
|
|
113
|
+
charter: no logic lives in the orchestrator file anymore.
|
|
114
|
+
|
|
115
|
+
| New file | Content | Source of the code |
|
|
116
|
+
|---|---|---|
|
|
117
|
+
| `pressure-getters.ts` | `PressureContext` + `pressureImpl` / `effectiveThresholdImpl` / `pressureBandImpl` | moved verbatim from the `pressure` / `effectiveThreshold` / `pressureBand` getters |
|
|
118
|
+
| `reset-runtime.ts` | `ResetRuntimeContext` + `resetRuntimeImpl(self, sessionId)` | moved verbatim from `resetRuntime()` |
|
|
119
|
+
| `append-event.ts` | `AppendEventContext` + `appendEventImpl(self, event, fields)` | moved verbatim from `appendEvent()` |
|
|
120
|
+
| `get-state-dir.ts` | `GetStateDirContext` + `getStateDirImpl(self)` | moved verbatim from `getStateDir()` |
|
|
121
|
+
| `render-widget.ts` | `RenderWidgetContext` + `renderWidgetImpl(self, ctx)` | moved verbatim from `renderWidget()` |
|
|
122
|
+
| `status.ts` | `SetStatusContext` + `setStatusImpl(self, ctx, text)` | moved verbatim from `setStatus()` |
|
|
123
|
+
| `engine-view.ts` | `engineViewImpl(messages)` | moved verbatim from `engineView()` |
|
|
124
|
+
| `game-state.ts` (+append) | `DisposeRuntimeContext` + `disposeRuntimeImpl(self)` | moved verbatim from `dispose()` (game-state.ts already owns the watcher context, so this lands there alongside `ensureGameStateWatcherImpl`) |
|
|
125
|
+
|
|
126
|
+
**What moved / changed:**
|
|
127
|
+
- `runtime.ts` import block rewritten: dropped `appendFileSync`/`mkdirSync`,
|
|
128
|
+
`STATUS_KEY`/`WIDGET_KEY`, `buildWidgetLines`, and
|
|
129
|
+
`pressureRatio`/`pressureFromPct`/`pressureBand`/`effectiveThresholdTokens`
|
|
130
|
+
(now consumed only in their respective modules); added imports of the 7 new
|
|
131
|
+
`*Impl` functions + `disposeRuntimeImpl`. `toEngineMessages` is kept
|
|
132
|
+
(`engineView`'s return type still references it); `normalizeSessionId` is kept
|
|
133
|
+
(the `rt` field initializer still uses it).
|
|
134
|
+
- The big pressure doc comment moved to `pressure-getters.ts`; `runtime.ts`
|
|
135
|
+
keeps a one-line pointer comment per delegate.
|
|
136
|
+
- `dispose()` delegates to `disposeRuntimeImpl` (in `game-state.ts`), which
|
|
137
|
+
composes `GameStateContext` + `PerfContext` and calls the existing
|
|
138
|
+
`disposePerf` — so `disposePerf` is no longer imported by `runtime.ts`.
|
|
139
|
+
|
|
140
|
+
**Why maximal:** the soft `~500 line` doc-length target (CLAUDE.md §6) applies to
|
|
141
|
+
source files too. `runtime.ts` at 522 lines was the only mega-runtime source
|
|
142
|
+
file over the target; the split brings it to **437 lines** (delegates + fields +
|
|
143
|
+
constructor only), with every new file ≤96 lines. The pattern (context-interface
|
|
144
|
+
+ free-function + thin-delegate) is the same one established in Phase 1
|
|
145
|
+
(`effects.ts`/`game-state.ts`/`capture-model.ts`/`bind-repo.ts`/`perf.ts`) and
|
|
146
|
+
extended in Phase 2b/2c (`runtime-helpers.ts`/`runtime-snapshot.ts`).
|
|
147
|
+
|
|
148
|
+
Verified: `tsc --noEmit` passes; `npm run lint` (tsc + guardrails-scan +
|
|
149
|
+
semantic-scan) green; `npm test` green (678 tests, 61 files, 0 failures);
|
|
150
|
+
`python3 scripts/regression_check.py --all` green; 8-point structural audit
|
|
151
|
+
green (line counts, no logic primitives left, 22 `Impl(this)` delegates, no
|
|
152
|
+
`this.` in new files, each `*Impl` defined exactly once, git scope limited to
|
|
153
|
+
the 2 modified + 7 new files, game-state.ts diff is purely additive, tsc clean).
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
### Deferred — effects wrappers
|
|
157
|
+
|
|
158
|
+
The `armMegaCacheFlare` / `armAchievementFlare` / `setEffect` / `pushTicker`
|
|
159
|
+
methods on `MegaRuntime` are already thin one-line delegates whose bodies live
|
|
160
|
+
in `effects.ts` (`*Impl` functions). They could optionally be collapsed into a
|
|
161
|
+
`runtime-effects.ts` barrel-of-delegates, but this yields little benefit (each
|
|
162
|
+
is already one line) and risks churn. **Deferred** unless `runtime.ts` grows.
|
|
163
|
+
|
|
164
|
+
## Sprint Backlog (prioritised)
|
|
165
|
+
|
|
166
|
+
1. ~~**Split `widget.ts`** — done (Phase 2a).~~
|
|
167
|
+
2. ~~**Split `runtime.ts`** — extract `materialSig`/`embedderName`/`driftStatus`/`getTurnLevel` into `runtime-helpers.ts` — done (Phase 2b).~~
|
|
168
|
+
3. ~~**Extract `snapshot()` body** into `runtime-snapshot.ts` — done (Phase 2c).~~
|
|
169
|
+
4. ~~**Verify `state.ts` re-export** — confirm `state.ts` → `runtime.ts` → final modules chain works (`state.test.ts` uses `createRequire` to load compiled JS) — green after Phase 2b.~~
|
|
170
|
+
5. ~~**Run full test suite** after each split (`npm test`) — green after 2a, 2b, and 2c (649 tests, 61 files, 0 failures).~~
|
|
171
|
+
6. **Final cleanup** — remove the `state.ts` placeholder once all consumers are updated to import `MegaRuntime` directly from `runtime.ts` (low priority; `state.ts` re-export is zero-cost).
|
|
172
|
+
7. **Commit Phase 2** — stage the Phase 2a/2b/2c changes (`runtime-helpers.ts`, `widget-ansi.ts`, `widget-types.ts`, `widget.ts`, `runtime-snapshot.ts`, `runtime.ts`, `DECOMPOSITION.md`) and commit.
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Notes
|
|
177
|
+
|
|
178
|
+
- `state.ts` is intentionally kept as a re-export to avoid a breaking change in `extensions/mega-runtime.ts:18` which imports `MegaRuntime` from `./mega-runtime/state.js`.
|
|
179
|
+
- `state.test.ts` and `widget.test.ts` exist alongside the source — they use `createRequire` to load compiled JS, not direct TS imports, so they exercise the built `dist/` output (build before testing).
|
|
180
|
+
- The `extensions/mega-runtime.ts` barrel does `export * from "./mega-runtime/widget.js"`; the widget split keeps that resolving because `widget.ts` re-exports the same public surface.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# mega-runtime
|
|
2
|
+
|
|
3
|
+
Runtime state and widget rendering for the mega-compact dashboard.
|
|
4
|
+
|
|
5
|
+
## Module structure
|
|
6
|
+
|
|
7
|
+
`MegaRuntime` (in `runtime.ts`) is a delegates-only orchestrator: field
|
|
8
|
+
declarations, the constructor, and 1-line delegates to `*Impl` free functions in
|
|
9
|
+
single-responsibility modules. Keep it that way — when adding logic, put the body
|
|
10
|
+
in a new `*.ts` module and add a thin delegate, rather than growing `runtime.ts`
|
|
11
|
+
(see `DECOMPOSITION.md` + the "no large files" rule in `CLAUDE.md` §6).
|
|
12
|
+
|
|
13
|
+
| Module | Responsibility |
|
|
14
|
+
|---|---|
|
|
15
|
+
| `state.ts` | Barrel re-export of `MegaRuntime` (backwards-compat import path) |
|
|
16
|
+
| `runtime.ts` | `MegaRuntime` class — delegates-only orchestrator (fields + constructor + 1-line delegates) |
|
|
17
|
+
| `runtime-snapshot.ts` | `snapshotImpl()` — the full `snapshot()` body (dashboard write + widget-data compute + gate) |
|
|
18
|
+
| `runtime-helpers.ts` | `materialSigImpl` / `embedderNameImpl` / `driftStatusImpl` / `getTurnLevelImpl` + `RuntimeHelpersContext` |
|
|
19
|
+
| `pressure-getters.ts` | `pressureImpl` / `effectiveThresholdImpl` / `pressureBandImpl` (the pressure accessors) |
|
|
20
|
+
| `reset-runtime.ts` | `resetRuntimeImpl()` — per-session state reset |
|
|
21
|
+
| `append-event.ts` | `appendEventImpl()` — structured events.log sink |
|
|
22
|
+
| `get-state-dir.ts` | `getStateDirImpl()` — bound repo state dir (S21) |
|
|
23
|
+
| `render-widget.ts` | `renderWidgetImpl()` — width-aware above-editor widget factory |
|
|
24
|
+
| `status.ts` | `setStatusImpl()` — status-key text mirrored to pi's status line |
|
|
25
|
+
| `engine-view.ts` | `engineViewImpl()` — pi→engine message adapter passthrough |
|
|
26
|
+
| `snapshot.ts` | `computeMegaSnapshot()` pure function |
|
|
27
|
+
| `dashboard-snapshot.ts` | `computeDashboardSnapshot()` |
|
|
28
|
+
| `widget.ts` | Barrel — `buildWidgetLines()` + re-exports from `widget-ansi.ts` / `widget-types.ts` |
|
|
29
|
+
| `widget-ansi.ts` | ANSI palette (`C`), `PULSE`, panel layout + border-effect helpers, token/time formatters |
|
|
30
|
+
| `widget-types.ts` | `TickerEntry` + `WidgetData` interfaces (pure types) |
|
|
31
|
+
| `effects.ts` | Effect/flare helpers (`setEffectImpl`, `armMegaCacheFlareImpl`, etc.) |
|
|
32
|
+
| `game-state.ts` | Game-state cache + watcher (`ensureGameStateWatcherImpl`, `bumpGameStateImpl`, `disposeRuntimeImpl`) |
|
|
33
|
+
| `capture-model.ts` | Model capture (`captureModelImpl`) |
|
|
34
|
+
| `bind-repo.ts` | Repo binding utilities (`bindRepoImpl`) |
|
|
35
|
+
| `perf.ts` | Perf sample recording + interval (`ensurePerfIntervalImpl`, `disposePerf`) |
|
|
36
|
+
| `helpers.ts` | Shared constants/types (`SessionRuntime`, `STATUS_KEY`, `WIDGET_KEY`, …) |
|
|
37
|
+
| `query.ts` | Query helpers (`recentUserQuery`) |
|
|
38
|
+
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* append-event.ts — extracted `MegaRuntime.appendEvent()`: the structured
|
|
3
|
+
* events.log diagnostics sink. Same context-interface + free-function +
|
|
4
|
+
* thin-delegate pattern as runtime-helpers.ts / effects.ts / game-state.ts.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { appendFileSync, mkdirSync } from "node:fs";
|
|
9
|
+
|
|
10
|
+
// ---------------------------------------------------------------------- types
|
|
11
|
+
|
|
12
|
+
/** The slice of `MegaRuntime` appendEvent reads (the bound repo's state dir). */
|
|
13
|
+
export interface AppendEventContext {
|
|
14
|
+
readonly currentStateDir: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// ---------------------------------------------------------------- appendEvent
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Append a structured line to the repo's events.log — the always-on
|
|
21
|
+
* diagnostics sink the dashboard live-streams. Unlike the runtime logger
|
|
22
|
+
* (gated by config.debug), this fires in production, so capture failures
|
|
23
|
+
* surface during a real capture even with debugging off. Best-effort +
|
|
24
|
+
* non-fatal.
|
|
25
|
+
*/
|
|
26
|
+
export function appendEventImpl(
|
|
27
|
+
self: AppendEventContext,
|
|
28
|
+
event: string,
|
|
29
|
+
fields: Record<string, unknown>,
|
|
30
|
+
): void {
|
|
31
|
+
try {
|
|
32
|
+
mkdirSync(self.currentStateDir, { recursive: true });
|
|
33
|
+
appendFileSync(
|
|
34
|
+
join(self.currentStateDir, "events.log"),
|
|
35
|
+
JSON.stringify({ ts: Date.now(), event, ...fields }) + "\n",
|
|
36
|
+
);
|
|
37
|
+
} catch {
|
|
38
|
+
/* non-fatal */
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bind-repo.ts — extracted per-repo binding for MegaRuntime.
|
|
3
|
+
*
|
|
4
|
+
* Point store/dashboard/logger at the current repo's state dir. Rebuilds the
|
|
5
|
+
* instances only when the repo root changes, so cross-repo dedup stats, db,
|
|
6
|
+
* and events are fully isolated. Falls back to the global default outside git.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { VectorStore, vectorRepoStats, vectorDataInvariant } from "../../src/vectorStore.js";
|
|
11
|
+
import { repoStateDir, resolveRepoRoot } from "../mega-config.js";
|
|
12
|
+
import { upsertRepoRegistry } from "../../src/store/sqlite.js";
|
|
13
|
+
import { Logger } from "../../src/log.js";
|
|
14
|
+
import { Dashboard } from "../mega-dashboard.js";
|
|
15
|
+
import type { MegaConfig } from "../mega-config.js";
|
|
16
|
+
|
|
17
|
+
// ---------------------------------------------------------------------- types
|
|
18
|
+
|
|
19
|
+
export interface BindRepoContext {
|
|
20
|
+
readonly config: MegaConfig;
|
|
21
|
+
activeRepoRoot: string | null;
|
|
22
|
+
currentStateDir: string;
|
|
23
|
+
store: VectorStore;
|
|
24
|
+
dashboard: Dashboard;
|
|
25
|
+
logger: Logger;
|
|
26
|
+
cachedGameState: import("../../src/store/sqlite.js").GameState | undefined;
|
|
27
|
+
gameStateBump: number;
|
|
28
|
+
ensureGameStateWatcher(): void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ------------------------------------------------------------------ bindRepo
|
|
32
|
+
|
|
33
|
+
export function bindRepoImpl(ctx: BindRepoContext, cwd: string | undefined): string {
|
|
34
|
+
const dir = cwd
|
|
35
|
+
? repoStateDir(cwd, ctx.config.stateDir)
|
|
36
|
+
: ctx.config.stateDir;
|
|
37
|
+
const key = cwd ? (resolveRepoRoot(cwd) ?? dir) : dir;
|
|
38
|
+
if (key === ctx.activeRepoRoot) return dir;
|
|
39
|
+
ctx.activeRepoRoot = key;
|
|
40
|
+
ctx.currentStateDir = dir;
|
|
41
|
+
// S31 audit P2: bindRepo switched currentStateDir but left cachedGameState
|
|
42
|
+
// memoized -> the widget kept showing the previous repo's theme/mode/toggle
|
|
43
|
+
// until /mega-game or a restart. The game_state row is per-repo (per
|
|
44
|
+
// stateDir), so evict the memo on every repo switch; the next widget render
|
|
45
|
+
// re-queries lazily via getCachedGameState().
|
|
46
|
+
ctx.cachedGameState = undefined;
|
|
47
|
+
ctx.gameStateBump++;
|
|
48
|
+
// S32: re-target the fs.watch cache-eviction watcher at the NEW stateDir's
|
|
49
|
+
// sqlite.db so cross-process writes (dashboard server) still evict the memo.
|
|
50
|
+
ctx.ensureGameStateWatcher();
|
|
51
|
+
ctx.store = new VectorStore({
|
|
52
|
+
dedupSim: ctx.config.dedupSim,
|
|
53
|
+
stateDir: dir,
|
|
54
|
+
});
|
|
55
|
+
ctx.logger = new Logger({
|
|
56
|
+
enabled: ctx.config.debug,
|
|
57
|
+
path: join(dir, "mega-compact.log"),
|
|
58
|
+
});
|
|
59
|
+
ctx.dashboard = new Dashboard(dir);
|
|
60
|
+
// Aggregate this repo into the machine-wide index so the multi-repo
|
|
61
|
+
// dashboard (Summary / All-repos tabs) can show it alongside every other
|
|
62
|
+
// repo. Best-effort + non-fatal: a read-only index dir or contention must
|
|
63
|
+
// never break the per-repo compaction path. Runs only on repo-switch
|
|
64
|
+
// (this branch), so it's infrequent — not per-context-event.
|
|
65
|
+
try {
|
|
66
|
+
const repo = vectorRepoStats(ctx.store);
|
|
67
|
+
const di = vectorDataInvariant(ctx.store);
|
|
68
|
+
const root = key !== dir ? key : (resolveRepoRoot(cwd ?? dir) ?? dir);
|
|
69
|
+
upsertRepoRegistry({
|
|
70
|
+
repoRoot: root,
|
|
71
|
+
displayName: root.split(/[\\/]/).filter(Boolean).pop() ?? root,
|
|
72
|
+
stateDir: dir,
|
|
73
|
+
checkpointCount: repo.checkpointCount,
|
|
74
|
+
tokensSaved: repo.tokensSaved,
|
|
75
|
+
compressedOriginalBytes: di.compressedOriginalBytes,
|
|
76
|
+
});
|
|
77
|
+
} catch {
|
|
78
|
+
/* non-fatal: index aggregation must not block compaction */
|
|
79
|
+
}
|
|
80
|
+
return dir;
|
|
81
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* capture-model.ts — extracted model-capture logic for MegaRuntime.
|
|
3
|
+
*
|
|
4
|
+
* Captures the active model/provider from ctx.model and persists it so cost
|
|
5
|
+
* estimation + the dashboard can read real pricing. Cheap + idempotent-ish:
|
|
6
|
+
* only writes a new row when the model id changes (models change rarely).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
10
|
+
import { resolveRepoRoot } from "../mega-config.js";
|
|
11
|
+
import { recordModelSnapshot, recordRepoModel } from "../../src/store/sqlite.js";
|
|
12
|
+
import type { ModelSnapshot } from "../../src/store/sqlite.js";
|
|
13
|
+
|
|
14
|
+
// ---------------------------------------------------------------------- types
|
|
15
|
+
|
|
16
|
+
export interface CaptureModelContext {
|
|
17
|
+
readonly currentStateDir: string;
|
|
18
|
+
currentModel: (ModelSnapshot & { capturedAt: number }) | null | undefined;
|
|
19
|
+
diagCaptureModelCalls: number;
|
|
20
|
+
diagCaptureModelFails: number;
|
|
21
|
+
appendEvent(event: string, fields: Record<string, unknown>): void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// -------------------------------------------------------------- captureModel
|
|
25
|
+
|
|
26
|
+
export function captureModelImpl(ctx: CaptureModelContext, ectx: ExtensionContext): void {
|
|
27
|
+
const m = ectx.model;
|
|
28
|
+
if (!m) {
|
|
29
|
+
ctx.appendEvent("captureModel:no-model", { cwd: ectx.cwd });
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (
|
|
33
|
+
ctx.currentModel &&
|
|
34
|
+
ctx.currentModel.modelId === m.id &&
|
|
35
|
+
ctx.currentModel.provider === m.provider
|
|
36
|
+
)
|
|
37
|
+
return;
|
|
38
|
+
let providerName: string | null = null;
|
|
39
|
+
try {
|
|
40
|
+
providerName =
|
|
41
|
+
ectx.modelRegistry?.getProviderDisplayName(m.provider) ?? null;
|
|
42
|
+
} catch {
|
|
43
|
+
/* optional */
|
|
44
|
+
}
|
|
45
|
+
const snap: Omit<ModelSnapshot, "capturedAt"> = {
|
|
46
|
+
provider: m.provider,
|
|
47
|
+
providerName,
|
|
48
|
+
modelId: m.id,
|
|
49
|
+
modelName: m.name ?? null,
|
|
50
|
+
inputRate: m.cost?.input ?? 0,
|
|
51
|
+
outputRate: m.cost?.output ?? 0,
|
|
52
|
+
contextWindow: m.contextWindow ?? 0,
|
|
53
|
+
maxTokens: m.maxTokens ?? 0,
|
|
54
|
+
reasoning: !!m.reasoning,
|
|
55
|
+
};
|
|
56
|
+
ctx.currentModel = { ...snap, capturedAt: Date.now() };
|
|
57
|
+
ctx.diagCaptureModelCalls++;
|
|
58
|
+
const repo = resolveRepoRoot(ectx.cwd) ?? ctx.currentStateDir;
|
|
59
|
+
// S26: previously a single silent `catch {}` hid every capture failure, so
|
|
60
|
+
// model_snapshots stayed empty and the cost card read $0.00 with zero signal.
|
|
61
|
+
// Split per-write + append to events.log (always-on, dashboard live-streams
|
|
62
|
+
// it) + bump a DIAG counter so a live capture surfaces the root cause.
|
|
63
|
+
try {
|
|
64
|
+
recordModelSnapshot(repo, snap, ctx.currentStateDir);
|
|
65
|
+
ctx.appendEvent("captureModel:recorded", {
|
|
66
|
+
repo,
|
|
67
|
+
modelId: snap.modelId,
|
|
68
|
+
provider: snap.provider,
|
|
69
|
+
inputRate: snap.inputRate,
|
|
70
|
+
outputRate: snap.outputRate,
|
|
71
|
+
});
|
|
72
|
+
} catch (e) {
|
|
73
|
+
ctx.diagCaptureModelFails++;
|
|
74
|
+
ctx.appendEvent("captureModel:record-failed", {
|
|
75
|
+
repo,
|
|
76
|
+
modelId: snap.modelId,
|
|
77
|
+
error: e instanceof Error ? e.message : String(e),
|
|
78
|
+
stack: e instanceof Error ? e.stack : undefined,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
// Denormalize the active model into the machine-wide index so the
|
|
83
|
+
// All-repos dashboard table can show provider/model per repo without
|
|
84
|
+
// opening every repo's DB. Best-effort + non-fatal.
|
|
85
|
+
recordRepoModel(repo, {
|
|
86
|
+
provider: snap.provider,
|
|
87
|
+
providerName: snap.providerName,
|
|
88
|
+
modelName: snap.modelName,
|
|
89
|
+
inputRate: snap.inputRate,
|
|
90
|
+
outputRate: snap.outputRate,
|
|
91
|
+
stateDir: ctx.currentStateDir,
|
|
92
|
+
displayName: repo.split(/[\\/]/).filter(Boolean).pop() ?? repo,
|
|
93
|
+
});
|
|
94
|
+
} catch (e) {
|
|
95
|
+
ctx.appendEvent("captureModel:index-record-failed", {
|
|
96
|
+
repo,
|
|
97
|
+
modelId: snap.modelId,
|
|
98
|
+
error: e instanceof Error ? e.message : String(e),
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|