pi-mega-compact 0.8.23 → 0.8.24

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.
Files changed (55) hide show
  1. package/dist/extensions/dashboard-server/api-contracts/endpoints.js +8 -0
  2. package/dist/extensions/dashboard-server/api-contracts/game-types.js +7 -0
  3. package/dist/extensions/mega-runtime/append-event.js +24 -0
  4. package/dist/extensions/mega-runtime/bind-repo.js +65 -0
  5. package/dist/extensions/mega-runtime/capture-model.js +87 -0
  6. package/dist/extensions/mega-runtime/dashboard-snapshot.js +118 -0
  7. package/dist/extensions/mega-runtime/effects.js +86 -0
  8. package/dist/extensions/mega-runtime/engine-view.js +11 -0
  9. package/dist/extensions/mega-runtime/game-state.js +116 -0
  10. package/dist/extensions/mega-runtime/get-state-dir.js +10 -0
  11. package/dist/extensions/mega-runtime/perf.js +49 -0
  12. package/dist/extensions/mega-runtime/pressure-getters.js +64 -0
  13. package/dist/extensions/mega-runtime/render-widget.js +17 -0
  14. package/dist/extensions/mega-runtime/reset-runtime.js +50 -0
  15. package/dist/extensions/mega-runtime/runtime-helpers.js +73 -0
  16. package/dist/extensions/mega-runtime/runtime-snapshot.js +204 -0
  17. package/dist/extensions/mega-runtime/runtime.js +352 -0
  18. package/dist/extensions/mega-runtime/snapshot.js +142 -0
  19. package/dist/extensions/mega-runtime/state.js +5 -1151
  20. package/dist/extensions/mega-runtime/status.js +11 -0
  21. package/dist/extensions/mega-runtime/widget-ansi.js +207 -0
  22. package/dist/extensions/mega-runtime/widget-types.js +8 -0
  23. package/dist/extensions/mega-runtime/widget.js +15 -204
  24. package/dist/extensions/openclaw-mega-compact.js +291 -0
  25. package/dist/src/minilm.js +92 -0
  26. package/dist/src/wordpiece.js +129 -0
  27. package/extensions/dashboard-client/dist/assets/index-D_WtU2TV.js.map +1 -1
  28. package/extensions/dashboard-server/api-contracts/endpoints.ts +30 -155
  29. package/extensions/dashboard-server/api-contracts/game-types.ts +172 -0
  30. package/extensions/mega-runtime/DECOMPOSITION.md +180 -0
  31. package/extensions/mega-runtime/README.md +38 -0
  32. package/extensions/mega-runtime/append-event.ts +40 -0
  33. package/extensions/mega-runtime/bind-repo.ts +81 -0
  34. package/extensions/mega-runtime/capture-model.ts +101 -0
  35. package/extensions/mega-runtime/dashboard-snapshot.ts +173 -0
  36. package/extensions/mega-runtime/effects.ts +129 -0
  37. package/extensions/mega-runtime/engine-view.ts +17 -0
  38. package/extensions/mega-runtime/game-state.ts +149 -0
  39. package/extensions/mega-runtime/get-state-dir.ts +19 -0
  40. package/extensions/mega-runtime/perf.ts +60 -0
  41. package/extensions/mega-runtime/pressure-getters.ts +96 -0
  42. package/extensions/mega-runtime/render-widget.ts +41 -0
  43. package/extensions/mega-runtime/reset-runtime.ts +80 -0
  44. package/extensions/mega-runtime/runtime-helpers.ts +119 -0
  45. package/extensions/mega-runtime/runtime-snapshot.ts +289 -0
  46. package/extensions/mega-runtime/runtime.ts +437 -0
  47. package/extensions/mega-runtime/snapshot.ts +230 -0
  48. package/extensions/mega-runtime/state.ts +5 -1268
  49. package/extensions/mega-runtime/status.ts +26 -0
  50. package/extensions/mega-runtime/widget-ansi.ts +217 -0
  51. package/extensions/mega-runtime/widget-types.ts +80 -0
  52. package/extensions/mega-runtime/widget.ts +34 -285
  53. package/package.json +1 -1
  54. package/dist/extensions/dashboard-client/src/hooks/useApi.js +0 -51
  55. package/dist/extensions/dashboard-client/src/hooks/useSSE.js +0 -63
@@ -12,9 +12,34 @@
12
12
  import type { EndpointDef } from './core.js';
13
13
  import type { SnapshotResponse } from './snapshot.js';
14
14
  import type { IndexesIndexRow, IndexesSummaryResponse } from './multi-repo.js';
15
- import type { GameConfig, GameStateResponse } from './game.js';
15
+ import type { GameStateResponse } from './game.js';
16
16
  import type { SseEvent } from './index.js';
17
17
 
18
+ import type {
19
+ GameScoreRow,
20
+ GameScoresQuery,
21
+ AchievementRow,
22
+ SessionsResponse,
23
+ SessionTimeseriesQuery,
24
+ SessionTimeseriesResponse,
25
+ GameStatePatch,
26
+ SseEndpointDef,
27
+ } from './game-types.js';
28
+
29
+ export type {
30
+ GameScoreRow,
31
+ GameScoresQuery,
32
+ AchievementRow,
33
+ ActiveSession,
34
+ SessionsResponse,
35
+ SessionDataPoint,
36
+ SessionSeries,
37
+ SessionTimeseriesQuery,
38
+ SessionTimeseriesResponse,
39
+ GameStatePatch,
40
+ SseEndpointDef,
41
+ } from './game-types.js';
42
+
18
43
  // ─── New Response Types (inline) ───────────────────────────────────────────
19
44
 
20
45
  /**
@@ -293,167 +318,17 @@ export interface PerfResponse {
293
318
  }
294
319
 
295
320
  // ─── Game Score & Achievement Types ─────────────────────────────────────────
296
-
297
- /**
298
- * A leaderboard row for GET /api/game-scores. One row per repo per metric.
299
- */
300
- export interface GameScoreRow {
301
- /** Absolute path to the repo root. */
302
- readonly repo_root: string;
303
- /** Score value (interpretation depends on the metric). */
304
- readonly value: number;
305
- /** Unix timestamp (milliseconds) when the score was recorded. */
306
- readonly ts: number;
307
- /** Optional metadata associated with the score event. */
308
- readonly meta: unknown;
309
- }
310
-
311
- /**
312
- * Query parameters for GET /api/game-scores.
313
- */
314
- export interface GameScoresQuery {
315
- /** Leaderboard metric (must be one of: cache, dedupe, turns, repos, mega_cache). Optional. */
316
- readonly metric?: string;
317
- /** Maximum number of rows to return (default: 10, clamped to [1, 100]). Optional. */
318
- readonly limit?: number;
319
- }
320
-
321
- /**
322
- * An achievement row for GET /api/achievements. One row per seeded achievement.
323
- */
324
- export interface AchievementRow {
325
- /** Achievement identifier. */
326
- readonly id: string;
327
- /** Display title of the achievement. */
328
- readonly title: string;
329
- /** Description of the unlock condition. */
330
- readonly description: string;
331
- /** Whether the achievement is hidden (1 = hidden, 0 = visible). */
332
- readonly hidden: number;
333
- /** Icon identifier, or null if no icon. */
334
- readonly icon: string | null;
335
- /** Unix timestamp (seconds) when unlocked, or null if not yet unlocked. */
336
- readonly unlocked_at: number | null;
337
- }
321
+ // (moved to game-types.ts; re-exported for backwards compatibility)
338
322
 
339
323
  // ─── Sessions Memory Graph (S39) ─────────────────────────────────────────
340
-
341
- /**
342
- * A single active session in the GET /api/sessions response. Represents a
343
- * live pi process with its latest token usage and heartbeat.
344
- */
345
- export interface ActiveSession {
346
- /** OS process ID of the pi process. */
347
- readonly pid: number;
348
- /** Session identifier (normalized). */
349
- readonly sessionId: string;
350
- /** Absolute path to the repo root, or null if not in a git repo. */
351
- readonly repoRoot: string | null;
352
- /** Display name (repo basename or state dir basename). */
353
- readonly displayName: string;
354
- /** Model name, or null if not captured. Available from repo_registry join. */
355
- readonly model: string | null;
356
- /** Latest context token count, or null if no sample yet. */
357
- readonly tokens: number | null;
358
- /** Latest context pressure percentage (0–100), or null. */
359
- readonly percent: number | null;
360
- /** Context window size in tokens. */
361
- readonly ctxWindow: number;
362
- /** Unix timestamp (ms) of the last heartbeat. */
363
- readonly lastSeen: number;
364
- /** State directory of the session. */
365
- readonly stateDir: string | null;
366
- }
367
-
368
- /**
369
- * Response for GET /api/sessions. Lists active sessions with their latest
370
- * token usage after pruning stale entries.
371
- */
372
- export interface SessionsResponse {
373
- /** ISO timestamp when the response was generated. */
374
- readonly updatedAt: string;
375
- /** Number of stale sessions pruned during this request. */
376
- readonly pruned: number;
377
- /** Array of active sessions, sorted by lastSeen descending. */
378
- readonly sessions: ActiveSession[];
379
- }
380
-
381
- /** A single time-series data point for a session. */
382
- export interface SessionDataPoint {
383
- /** Unix timestamp (ms). */
384
- readonly ts: number;
385
- /** Token count at this sample. */
386
- readonly tokens: number;
387
- /** Context pressure percentage (0–100). */
388
- readonly percent: number;
389
- }
390
-
391
- /** A recharts-ready per-session series with a stable color. */
392
- export interface SessionSeries {
393
- /** Session identifier. */
394
- readonly sessionId: string;
395
- /** Short display label for the legend. */
396
- readonly label: string;
397
- /** Stable hex color string (e.g. "#60a5fa"). */
398
- readonly color: string;
399
- /** Data points ordered by timestamp ascending. */
400
- readonly data: SessionDataPoint[];
401
- }
402
-
403
- /**
404
- * Query parameters for GET /api/sessions/timeseries. The `minutes` parameter
405
- * controls the rolling window size.
406
- */
407
- export interface SessionTimeseriesQuery {
408
- /** Rolling window size in minutes (default: 30, clamped to [1, 1440]). */
409
- readonly minutes?: number;
410
- }
411
-
412
- /**
413
- * Response for GET /api/sessions/timeseries. Returns recharts-ready stacked
414
- * per-session series + a totals array.
415
- */
416
- export interface SessionTimeseriesResponse {
417
- /** ISO timestamp when the response was generated. */
418
- readonly updatedAt: string;
419
- /** Rolling window size in minutes. */
420
- readonly windowMinutes: number;
421
- /** Per-session series with stable colors. */
422
- readonly series: SessionSeries[];
423
- /** Totals (sum of all sessions) per timestamp. */
424
- readonly totals: { readonly ts: number; readonly tokens: number }[];
425
- }
324
+ // (moved to game-types.ts; re-exported for backwards compatibility)
426
325
 
427
326
  // ─── Game State Patch (PUT request body) ────────────────────────────────────
428
-
429
- /**
430
- * Request body for PUT /api/game-state. A partial patch of the game config.
431
- * Unknown keys are ignored; invalid values result in a 400 response.
432
- */
433
- export type GameStatePatch = Partial<GameConfig>;
327
+ // (moved to game-types.ts; re-exported for backwards compatibility)
434
328
 
435
329
  // ─── SSE Endpoint Definition ────────────────────────────────────────────────
330
+ // (moved to game-types.ts; re-exported for backwards compatibility)
436
331
 
437
- /**
438
- * Endpoint definition for SSE (Server-Sent Events) streaming endpoints.
439
- * Unlike standard REST endpoints, the response is a continuous `text/event-stream`
440
- * with `data:` frames containing JSON-serialized event objects.
441
- * @template Data - The SSE event data type streamed by this endpoint.
442
- */
443
- export interface SseEndpointDef<Data extends SseEvent = SseEvent> {
444
- /** Discriminator: always 'sse' for streaming endpoints. */
445
- readonly type: 'sse';
446
- /** HTTP method (always 'GET' for SSE). */
447
- readonly method: 'GET';
448
- /** URL path of the endpoint. */
449
- readonly path: string;
450
- /** Human-readable description of the endpoint. */
451
- readonly description: string;
452
- /** SSE event name sent with each data frame. */
453
- readonly event: string;
454
- /** The SSE event data type streamed by this endpoint. */
455
- readonly dataType?: Data;
456
- }
457
332
 
458
333
  // ─── ENDPOINTS Registry ─────────────────────────────────────────────────────
459
334
 
@@ -0,0 +1,172 @@
1
+ /**
2
+ * api-contracts/game-types.ts — Split-out dashboard API types.
3
+ *
4
+ * Contains the “Game Score & Achievement Types” and “Sessions Memory Graph”
5
+ * related type definitions that previously lived in endpoints.ts.
6
+ */
7
+
8
+ import type { GameConfig } from './game.js';
9
+ import type { SseEvent } from './index.js';
10
+
11
+ // ─── Game Score & Achievement Types ─────────────────────────────────────────
12
+
13
+ /**
14
+ * A leaderboard row for GET /api/game-scores. One row per repo per metric.
15
+ */
16
+ export interface GameScoreRow {
17
+ /** Absolute path to the repo root. */
18
+ readonly repo_root: string;
19
+ /** Score value (interpretation depends on the metric). */
20
+ readonly value: number;
21
+ /** Unix timestamp (milliseconds) when the score was recorded. */
22
+ readonly ts: number;
23
+ /** Optional metadata associated with the score event. */
24
+ readonly meta: unknown;
25
+ }
26
+
27
+ /**
28
+ * Query parameters for GET /api/game-scores.
29
+ */
30
+ export interface GameScoresQuery {
31
+ /** Leaderboard metric (must be one of: cache, dedupe, turns, repos, mega_cache). Optional. */
32
+ readonly metric?: string;
33
+ /** Maximum number of rows to return (default: 10, clamped to [1, 100]). Optional. */
34
+ readonly limit?: number;
35
+ }
36
+
37
+ /**
38
+ * An achievement row for GET /api/achievements. One row per seeded achievement.
39
+ */
40
+ export interface AchievementRow {
41
+ /** Achievement identifier. */
42
+ readonly id: string;
43
+ /** Display title of the achievement. */
44
+ readonly title: string;
45
+ /** Description of the unlock condition. */
46
+ readonly description: string;
47
+ /** Whether the achievement is hidden (1 = hidden, 0 = visible). */
48
+ readonly hidden: number;
49
+ /** Icon identifier, or null if no icon. */
50
+ readonly icon: string | null;
51
+ /** Unix timestamp (seconds) when unlocked, or null if not yet unlocked. */
52
+ readonly unlocked_at: number | null;
53
+ }
54
+
55
+ // ─── Sessions Memory Graph (S39) ─────────────────────────────────────────
56
+
57
+ /**
58
+ * A single active session in the GET /api/sessions response. Represents a
59
+ * live pi process with its latest token usage and heartbeat.
60
+ */
61
+ export interface ActiveSession {
62
+ /** OS process ID of the pi process. */
63
+ readonly pid: number;
64
+ /** Session identifier (normalized). */
65
+ readonly sessionId: string;
66
+ /** Absolute path to the repo root, or null if not in a git repo. */
67
+ readonly repoRoot: string | null;
68
+ /** Display name (repo basename or state dir basename). */
69
+ readonly displayName: string;
70
+ /** Model name, or null if not captured. Available from repo_registry join. */
71
+ readonly model: string | null;
72
+ /** Latest context token count, or null if no sample yet. */
73
+ readonly tokens: number | null;
74
+ /** Latest context pressure percentage (0–100), or null. */
75
+ readonly percent: number | null;
76
+ /** Context window size in tokens. */
77
+ readonly ctxWindow: number;
78
+ /** Unix timestamp (ms) of the last heartbeat. */
79
+ readonly lastSeen: number;
80
+ /** State directory of the session. */
81
+ readonly stateDir: string | null;
82
+ }
83
+
84
+ /**
85
+ * Response for GET /api/sessions. Lists active sessions with their latest
86
+ * token usage after pruning stale entries.
87
+ */
88
+ export interface SessionsResponse {
89
+ /** ISO timestamp when the response was generated. */
90
+ readonly updatedAt: string;
91
+ /** Number of stale sessions pruned during this request. */
92
+ readonly pruned: number;
93
+ /** Array of active sessions, sorted by lastSeen descending. */
94
+ readonly sessions: ActiveSession[];
95
+ }
96
+
97
+ /** A single time-series data point for a session. */
98
+ export interface SessionDataPoint {
99
+ /** Unix timestamp (ms). */
100
+ readonly ts: number;
101
+ /** Token count at this sample. */
102
+ readonly tokens: number;
103
+ /** Context pressure percentage (0–100). */
104
+ readonly percent: number;
105
+ }
106
+
107
+ /** A recharts-ready per-session series with a stable color. */
108
+ export interface SessionSeries {
109
+ /** Session identifier. */
110
+ readonly sessionId: string;
111
+ /** Short display label for the legend. */
112
+ readonly label: string;
113
+ /** Stable hex color string (e.g. "#60a5fa"). */
114
+ readonly color: string;
115
+ /** Data points ordered by timestamp ascending. */
116
+ readonly data: SessionDataPoint[];
117
+ }
118
+
119
+ /**
120
+ * Query parameters for GET /api/sessions/timeseries. The `minutes` parameter
121
+ * controls the rolling window size.
122
+ */
123
+ export interface SessionTimeseriesQuery {
124
+ /** Rolling window size in minutes (default: 30, clamped to [1, 1440]). */
125
+ readonly minutes?: number;
126
+ }
127
+
128
+ /**
129
+ * Response for GET /api/sessions/timeseries. Returns recharts-ready stacked
130
+ * per-session series + a totals array.
131
+ */
132
+ export interface SessionTimeseriesResponse {
133
+ /** ISO timestamp when the response was generated. */
134
+ readonly updatedAt: string;
135
+ /** Rolling window size in minutes. */
136
+ readonly windowMinutes: number;
137
+ /** Per-session series with stable colors. */
138
+ readonly series: SessionSeries[];
139
+ /** Totals (sum of all sessions) per timestamp. */
140
+ readonly totals: { readonly ts: number; readonly tokens: number }[];
141
+ }
142
+
143
+ // ─── Game State Patch (PUT request body) ────────────────────────────────────
144
+
145
+ /**
146
+ * Request body for PUT /api/game-state. A partial patch of the game config.
147
+ * Unknown keys are ignored; invalid values result in a 400 response.
148
+ */
149
+ export type GameStatePatch = Partial<GameConfig>;
150
+
151
+ // ─── SSE Endpoint Definition ────────────────────────────────────────────────
152
+
153
+ /**
154
+ * Endpoint definition for SSE (Server-Sent Events) streaming endpoints.
155
+ * Unlike standard REST endpoints, the response is a continuous `text/event-stream`
156
+ * with `data:` frames containing JSON-serialized event objects.
157
+ * @template Data - The SSE event data type streamed by this endpoint.
158
+ */
159
+ export interface SseEndpointDef<Data extends SseEvent = SseEvent> {
160
+ /** Discriminator: always 'sse' for streaming endpoints. */
161
+ readonly type: 'sse';
162
+ /** HTTP method (always 'GET' for SSE). */
163
+ readonly method: 'GET';
164
+ /** URL path of the endpoint. */
165
+ readonly path: string;
166
+ /** Human-readable description of the endpoint. */
167
+ readonly description: string;
168
+ /** SSE event name sent with each data frame. */
169
+ readonly event: string;
170
+ /** The SSE event data type streamed by this endpoint. */
171
+ readonly dataType?: Data;
172
+ }
@@ -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
+ }