pi-mega-compact 0.21.3 → 0.21.5
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 +2 -0
- package/dist/extensions/mega-config.js +41 -24
- package/dist/extensions/mega-events/agent-handlers/turnEndHandler/lengthStop.js +7 -0
- package/dist/extensions/mega-events/agent-handlers/turnEndHandler/recordTurnRow.js +12 -2
- package/dist/extensions/mega-events/context-handler/afterCompact.js +11 -0
- package/dist/extensions/mega-events/context-handler/dbMirrorAppend.js +8 -0
- package/dist/extensions/mega-events/context-handler/gateCheck.js +17 -0
- package/dist/extensions/mega-events/health-handler.js +12 -2
- package/dist/extensions/mega-events/session-handlers.js +1 -0
- package/dist/extensions/mega-pipeline/compact/run.js +11 -0
- package/dist/extensions/mega-runtime/helpers.js +46 -0
- package/dist/extensions/mega-runtime/internal-errors.js +14 -0
- package/dist/extensions/mega-runtime/pressure-getters.js +12 -10
- package/dist/extensions/mega-runtime/reset-runtime.js +3 -1
- package/dist/extensions/mega-runtime/runtime-instrumentation.js +171 -0
- package/dist/extensions/mega-runtime/runtime.js +27 -193
- package/dist/extensions/mega-runtime/state.js +1 -1
- package/dist/extensions/mega-runtime/widget.js +1 -1
- package/dist/extensions/mega-turn-store.js +5 -0
- package/dist/src/bridge/factory.js +15 -1
- package/dist/src/compact-summary.js +99 -0
- package/dist/src/compact.js +15 -99
- package/dist/src/contextHealth.js +13 -5
- package/dist/src/metrics/turns.js +6 -1
- package/dist/src/store/sqlite/context-health-cache-poison.js +60 -0
- package/dist/src/store/sqlite/context-health.js +6 -48
- package/dist/src/store/sqlite/embedding-cache.js +4 -0
- package/dist/src/store/sqlite/schema.js +4 -0
- package/dist/src/store/turns/memory-store/admin.js +119 -0
- package/dist/src/store/turns/memory-store/ctx.js +28 -0
- package/dist/src/store/turns/memory-store/read.js +110 -0
- package/dist/src/store/turns/memory-store/write.js +88 -0
- package/dist/src/store/turns/memory-store.js +44 -273
- package/dist/src/store/turns/migrations.js +4 -0
- package/dist/src/store/turns/schema.js +1 -0
- package/dist/src/store/turns/sqlite-store/read.js +13 -0
- package/dist/src/store/turns/sqlite-store/rows.js +4 -0
- package/dist/src/store/turns/sqlite-store/write.js +3 -3
- package/dist/src/store/turns/sqlite-store.js +4 -0
- package/extensions/dashboard-client/dist/assets/{AdminTab-BsA8-_bX.js → AdminTab-C4Y0_UVx.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{AdminTab-BsA8-_bX.js.map → AdminTab-C4Y0_UVx.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{AreaChart-BRHEfWhc.js → AreaChart-MCWK9rn6.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{AreaChart-BRHEfWhc.js.map → AreaChart-MCWK9rn6.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{BarChart-C-M0LaFc.js → BarChart-CvSGAiWV.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{BarChart-C-M0LaFc.js.map → BarChart-CvSGAiWV.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{CacheTab-Cj3n59U3.js → CacheTab-DnMEVRro.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{CacheTab-Cj3n59U3.js.map → CacheTab-DnMEVRro.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{ConfigTab-C016XTNC.js → ConfigTab-BExuZYy2.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ConfigTab-C016XTNC.js.map → ConfigTab-BExuZYy2.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{EventsTab-DE29fuqe.js → EventsTab-CspsEslY.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{EventsTab-DE29fuqe.js.map → EventsTab-CspsEslY.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/HealthTab-DiWzo2ML.js +2 -0
- package/extensions/dashboard-client/dist/assets/HealthTab-DiWzo2ML.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-4fJ7M4S4.js → MaintenanceTab-9NJuv3dB.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-4fJ7M4S4.js.map → MaintenanceTab-9NJuv3dB.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-CDbcK9e4.js → MemoryMapTab-YpJK1h17.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-CDbcK9e4.js.map → MemoryMapTab-YpJK1h17.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MetricsCards-VUEcgGzo.js → MetricsCards-D2Mom1M7.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MetricsCards-VUEcgGzo.js.map → MetricsCards-D2Mom1M7.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/MetricsTab-DqWF7wud.js +2 -0
- package/extensions/dashboard-client/dist/assets/{MetricsTab-CKqJ0Zl-.js.map → MetricsTab-DqWF7wud.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{OverviewTab-9Kxo79mi.js → OverviewTab-YuRxjV9U.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{OverviewTab-9Kxo79mi.js.map → OverviewTab-YuRxjV9U.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{ReposTab-C4iveqrP.js → ReposTab-sXMrR7je.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ReposTab-C4iveqrP.js.map → ReposTab-sXMrR7je.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SessionsTab-DSEwkMQm.js → SessionsTab-4YCXeGNR.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SessionsTab-DSEwkMQm.js.map → SessionsTab-4YCXeGNR.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SetupTab-CCyWgy4O.js → SetupTab-67pZcM_W.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SetupTab-CCyWgy4O.js.map → SetupTab-67pZcM_W.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-CyD8e_e_.js → TimeSavedCard-DInjNETP.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-CyD8e_e_.js.map → TimeSavedCard-DInjNETP.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TurnMemoryView-5tLbWkRv.js → TurnMemoryView-C3Bif8ix.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TurnMemoryView-5tLbWkRv.js.map → TurnMemoryView-C3Bif8ix.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/TurnsTab-BUGfiANp.js +2 -0
- package/extensions/dashboard-client/dist/assets/{TurnsTab-C7pm92Hg.js.map → TurnsTab-BUGfiANp.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{VcStatusBadge-ObhcIyfN.js → VcStatusBadge-DKU7qz9f.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{VcStatusBadge-ObhcIyfN.js.map → VcStatusBadge-DKU7qz9f.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{VectorCortexTab-BtHZb7o7.js → VectorCortexTab-BsVSf4xm.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{VectorCortexTab-BtHZb7o7.js.map → VectorCortexTab-BsVSf4xm.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{WikiTab-lQ4l3QIK.js → WikiTab-DZw9_Fwd.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{WikiTab-lQ4l3QIK.js.map → WikiTab-DZw9_Fwd.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{button-DhQQAJGm.js → button-BLRPN489.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{button-DhQQAJGm.js.map → button-BLRPN489.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{card-Cfhz9X31.js → card-BGkkcM4Q.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{card-Cfhz9X31.js.map → card-BGkkcM4Q.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{client-extra-CKNOGEz_.js → client-extra-0OJClxHD.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{client-extra-CKNOGEz_.js.map → client-extra-0OJClxHD.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-JKjK9_cB.js → generateCategoricalChart-Dl5lmrHF.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-JKjK9_cB.js.map → generateCategoricalChart-Dl5lmrHF.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/health-lSeDFTz7.js.map +1 -1
- package/extensions/dashboard-client/dist/assets/{index-iuxgtzEW.js → index-CH_AD-S_.js} +3 -3
- package/extensions/dashboard-client/dist/assets/{index-iuxgtzEW.js.map → index-CH_AD-S_.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{switch-7EHEQMSP.js → switch-BPP2-d12.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{switch-7EHEQMSP.js.map → switch-BPP2-d12.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{toggle-z1lWY4UM.js → toggle-Dez0JaMV.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{toggle-z1lWY4UM.js.map → toggle-Dez0JaMV.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{useSSE-sctOZuRM.js → useSSE-Cx2W7BTo.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{useSSE-sctOZuRM.js.map → useSSE-Cx2W7BTo.js.map} +1 -1
- package/extensions/dashboard-client/dist/index.html +1 -1
- package/extensions/dashboard-client/src/api/health.ts +1 -0
- package/extensions/dashboard-client/src/tabs/HealthTab.tsx +1 -0
- package/extensions/mega-config-types.ts +8 -0
- package/extensions/mega-config.ts +51 -27
- package/extensions/mega-events/agent-handlers/turnEndHandler/lengthStop.ts +7 -0
- package/extensions/mega-events/agent-handlers/turnEndHandler/recordTurnRow.ts +12 -2
- package/extensions/mega-events/context-handler/afterCompact.ts +11 -0
- package/extensions/mega-events/context-handler/dbMirrorAppend.ts +8 -0
- package/extensions/mega-events/context-handler/gateCheck.ts +18 -0
- package/extensions/mega-events/health-handler.ts +13 -3
- package/extensions/mega-events/session-handlers.ts +1 -0
- package/extensions/mega-pipeline/compact/run.ts +11 -0
- package/extensions/mega-runtime/helpers.ts +51 -0
- package/extensions/mega-runtime/internal-errors.ts +30 -0
- package/extensions/mega-runtime/pressure-getters.ts +12 -12
- package/extensions/mega-runtime/reset-runtime.ts +3 -1
- package/extensions/mega-runtime/runtime-instrumentation.ts +200 -0
- package/extensions/mega-runtime/runtime.ts +29 -209
- package/extensions/mega-runtime/state.ts +1 -1
- package/extensions/mega-runtime/widget.ts +1 -1
- package/extensions/mega-turn-store.ts +9 -0
- package/package.json +2 -2
- package/src/bridge/factory.ts +15 -1
- package/src/bridge/types.ts +2 -0
- package/src/compact-summary.ts +120 -0
- package/src/compact.ts +19 -109
- package/src/contextHealth.ts +26 -6
- package/src/metrics/turns.ts +6 -1
- package/src/store/sqlite/context-health-cache-poison.ts +97 -0
- package/src/store/sqlite/context-health.ts +12 -84
- package/src/store/sqlite/embedding-cache.ts +4 -0
- package/src/store/sqlite/schema.ts +4 -0
- package/src/store/turns/memory-store/admin.ts +171 -0
- package/src/store/turns/memory-store/ctx.ts +62 -0
- package/src/store/turns/memory-store/read.ts +172 -0
- package/src/store/turns/memory-store/write.ts +126 -0
- package/src/store/turns/memory-store.ts +51 -330
- package/src/store/turns/migrations.ts +4 -0
- package/src/store/turns/schema.ts +1 -0
- package/src/store/turns/sqlite-store/read.ts +19 -0
- package/src/store/turns/sqlite-store/rows.ts +5 -0
- package/src/store/turns/sqlite-store/write.ts +3 -2
- package/src/store/turns/sqlite-store.ts +5 -0
- package/src/store/turns/types.ts +15 -0
- package/extensions/dashboard-client/dist/assets/HealthTab-D4eVI8ar.js +0 -2
- package/extensions/dashboard-client/dist/assets/HealthTab-D4eVI8ar.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/MetricsTab-CKqJ0Zl-.js +0 -2
- package/extensions/dashboard-client/dist/assets/TurnsTab-C7pm92Hg.js +0 -2
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { DEFAULT_SAVED_GOAL } from "../mega-config.js";
|
|
2
|
+
/**
|
|
3
|
+
* DIAG counters for the "team run doesn't relieve context" investigation.
|
|
4
|
+
* Plain integers, incremented at the three compaction decision points. They
|
|
5
|
+
* let a headless test drive the real event handlers and assert the firing
|
|
6
|
+
* cadence without scraping log files. Inert in production (the live-trim and
|
|
7
|
+
* before-compact probes also emit logger.info, but these counters are always
|
|
8
|
+
* updated and cost nothing).
|
|
9
|
+
*/
|
|
10
|
+
export class RuntimeInstrumentation {
|
|
11
|
+
diagLiveTrimFires = 0; // context handler returned a trimmed view
|
|
12
|
+
diagLiveTrimReplays = 0; // v0.8.6: trim view returned via cached replay (skipped re-compact)
|
|
13
|
+
diagBeforeCompactFires = 0; // session_before_compact handler entered
|
|
14
|
+
diagBeforeCompactSupplied = 0; // session_before_compact supplied our trim
|
|
15
|
+
diagAgentEndIdle = 0; // agent_end with activeAgents===0
|
|
16
|
+
diagAgentEndDurable = 0; // agent_end fired ctx.compact() (mid-run durable trim)
|
|
17
|
+
diagAgentEndDurableSkipRecent = 0; // agent_end skipped ctx.compact() — compaction in last 10s (race guard)
|
|
18
|
+
// Per-skip-path counters for the team-run diagnosis.
|
|
19
|
+
diagCtxFastGate = 0; // returned at token fast-gate (below threshold)
|
|
20
|
+
diagCtxNoCompact = 0; // autoCompactCheck().shouldCompact === false
|
|
21
|
+
diagCtxDebounce = 0; // debounceUntil not yet elapsed
|
|
22
|
+
diagCtxRunSkipped = 0; // runCompact() returned skipped
|
|
23
|
+
diagCtxCutNull = 0; // computeLiveTrimCut returned null (anchor/boundary)
|
|
24
|
+
diagCtxThrown = 0; // live-trim try threw (caught)
|
|
25
|
+
diagCtxOutputErrorTrip = 0; // Phase H: output-error catch tripped a forced compaction
|
|
26
|
+
// Context health instrumentation (v0.12): rolling ring buffers for
|
|
27
|
+
// drift detection + cache poison Layer 1 hash baseline.
|
|
28
|
+
recentTurnEmbeddings = [];
|
|
29
|
+
recentErrorCategories = [];
|
|
30
|
+
lastPrefixHash = null;
|
|
31
|
+
lastErrorCategory = null;
|
|
32
|
+
// Sprint H (Finding 3): a SEPARATE internal-error ring fed by explicit
|
|
33
|
+
// `recordInternalError(category)` calls at each store/service-write failure
|
|
34
|
+
// emit site (Option A — NOT a central events.log filter). This captures
|
|
35
|
+
// internal store-write failures which the API-error `errorRate` ring never
|
|
36
|
+
// sees. The health-handler computes a distinct `storeErrorRate` 6th axis.
|
|
37
|
+
// Mirrors `recentErrorCategories` / `lastErrorCategory` exactly (cap = RING_MAX).
|
|
38
|
+
recentInternalErrors = [];
|
|
39
|
+
/**
|
|
40
|
+
* S26 capture instrumentation: the "model_snapshots empty → $0.00 cost card"
|
|
41
|
+
* bug was invisible because captureModel swallowed the DB write in a silent
|
|
42
|
+
* `catch {}`. These always-updated counters (zero cost) let a headless test or
|
|
43
|
+
* a live capture tell whether captureModel ran and whether the snapshot landed.
|
|
44
|
+
*/
|
|
45
|
+
diagCaptureModelCalls = 0; // captureModel entered with a populated ctx.model
|
|
46
|
+
diagCaptureModelFails = 0; // recordModelSnapshot threw → model_snapshots stays empty
|
|
47
|
+
// ── Live display / cache-stability state (moved from runtime.ts shell) ────
|
|
48
|
+
// v0.8.6 cache-stability: the cached live-trim view for the current
|
|
49
|
+
// compaction epoch. Set after a fresh runCompact + computeLiveTrimCut, and
|
|
50
|
+
// replayed verbatim on subsequent gated context events in the SAME epoch
|
|
51
|
+
// (same checkpointId) so the provider KV-cache prefix stays stable instead
|
|
52
|
+
// of being invalidated by a freshly regenerated summary + sentinel every
|
|
53
|
+
// fire. Invalidated on session restart (resetRuntime) and on any native
|
|
54
|
+
// durable compaction (session_compact) that truncates the transcript.
|
|
55
|
+
trimCache = null;
|
|
56
|
+
debounceUntil = 0;
|
|
57
|
+
// S16: debounce for the agent_end resume nudge (avoid busy-loops).
|
|
58
|
+
resumeNudgeUntil = 0;
|
|
59
|
+
// Agent tracking for real-time widget updates
|
|
60
|
+
activeAgents = 0;
|
|
61
|
+
currentTurn = 0;
|
|
62
|
+
// S33: transient MEGA CACHE flare flag (armed by the turn_end scoring hook
|
|
63
|
+
// when cachePct > 100). Copied into widgetData.megaCacheFlare on the next
|
|
64
|
+
// snapshot() so the widget renders the oopsie gag, then reset (one cycle).
|
|
65
|
+
megaCacheFlare = false;
|
|
66
|
+
/** v0.8.3: ambient effect state for animated panel borders keyed off
|
|
67
|
+
* status transitions (level-up, mega-cache overshoot, achievement unlock,
|
|
68
|
+
* compaction start). Threaded into widgetData as `activeEffect`; the widget
|
|
69
|
+
* computes the per-frame phase from startedAt vs Date.now() (non-expired).
|
|
70
|
+
* Null when idle/expired. */
|
|
71
|
+
activeEffect = null;
|
|
72
|
+
megaCacheFlarePct = 0;
|
|
73
|
+
levelUpFlare = false;
|
|
74
|
+
lastLevel = 0;
|
|
75
|
+
// S35: transient achievement-unlock flare (armed by the scoring hooks after
|
|
76
|
+
// evaluateAndUnlockAchievements returns newly-unlocked titles). Copied into
|
|
77
|
+
// widgetData.achievementFlare on the next snapshot() so the widget renders the
|
|
78
|
+
// unlock toast, then reset (one cycle — mirrors megaCacheFlare/levelUpFlare).
|
|
79
|
+
achievementFlare = false;
|
|
80
|
+
achievementFlareTitles = [];
|
|
81
|
+
// S33: last cumulative dedup-collapsed count seen by the session_compact
|
|
82
|
+
// hook, so we only record the DELTA as the dedupe score (leaderboard sums).
|
|
83
|
+
lastDedupCollapsed = 0;
|
|
84
|
+
// Recall block produced by auto-inline (resume/branch) that the next
|
|
85
|
+
// before_agent_start should prepend to the system prompt. Unset after use.
|
|
86
|
+
pendingRecallBlock;
|
|
87
|
+
// S21: memory recall block, parallel to pendingRecallBlock. Same one-shot
|
|
88
|
+
// semantics; composed with the checkpoint block in before_agent_start.
|
|
89
|
+
pendingMemoryRecallBlock;
|
|
90
|
+
statusKey; // current status text for dashboard
|
|
91
|
+
// Active model/provider (for real cost estimation). Captured from ctx.model
|
|
92
|
+
// on model_select + session_start; persisted to SQL so cost + the dashboard
|
|
93
|
+
// can read it without a live ctx.
|
|
94
|
+
currentModel;
|
|
95
|
+
// Live "what it's doing right now" timestamp, used for the fresh-window.
|
|
96
|
+
lastActivityAt = 0;
|
|
97
|
+
// Live per-tier dedup trace (Phase 1): e.g. "L0 ✓ → L1 ✓ → L2 0.91 → stored".
|
|
98
|
+
// Built from the store's sync onTier callback during a compaction so the user
|
|
99
|
+
// watches each tier evaluate in real time. Cleared once the outcome settles.
|
|
100
|
+
tierTrace;
|
|
101
|
+
// Phase 3 — standout toolbar state.
|
|
102
|
+
// Recall/activity ticker: a small ring buffer (≤5) of recent compact/recall
|
|
103
|
+
// events so the widget shows a live history instead of a single last action.
|
|
104
|
+
ticker = [];
|
|
105
|
+
TICKER_MAX = 5;
|
|
106
|
+
// Pulsing status: set true while a compaction is in flight, cleared on result.
|
|
107
|
+
pulsing = false;
|
|
108
|
+
// S21.2: set by `applyMemoryOps` when a memory add/replace/remove lands in
|
|
109
|
+
// the current compaction. The pipeline reads this after a successful compact
|
|
110
|
+
// to decide whether to fire `consolidateMemories` (skip the work entirely
|
|
111
|
+
// when no memory rows changed).
|
|
112
|
+
memoriesTouchedThisCompaction = 0;
|
|
113
|
+
// Rolling "saved" goal for the progress bar — grows as we save more, so the
|
|
114
|
+
// bar always has a meaningful denominator (never sits at 100% forever).
|
|
115
|
+
// Seeded from the %-derived DEFAULT_SAVED_GOAL (low-tier fire point at the
|
|
116
|
+
// default context window); resetRuntime re-seeds from the live
|
|
117
|
+
// effectiveThreshold when the window is known. Display-only.
|
|
118
|
+
savedGoal = DEFAULT_SAVED_GOAL;
|
|
119
|
+
// Last explain-why line (dedup reason / anchor-kept / superseded), surfaced
|
|
120
|
+
// while fresh.
|
|
121
|
+
lastWhy = undefined;
|
|
122
|
+
// v0.8.8 Perf dashboard instrumentation: turn/provider start timestamps +
|
|
123
|
+
// the 5s cpu/mem interval handle (one per MegaRuntime, cleared in dispose()).
|
|
124
|
+
perfTurnStart = 0;
|
|
125
|
+
perfProviderStart = 0;
|
|
126
|
+
perfCpuInterval = null;
|
|
127
|
+
perfCpuBaseline;
|
|
128
|
+
// Context tracking for the dashboard (updated in the context handler).
|
|
129
|
+
lastCtxTokens = null;
|
|
130
|
+
lastCtxPercent = null;
|
|
131
|
+
lastCtxWindow = 0;
|
|
132
|
+
// Latest computed widget payload (recomputed per snapshot, rendered per frame).
|
|
133
|
+
widgetData = null;
|
|
134
|
+
// v0.8.5: material-change signature from the last full snapshot() body. When
|
|
135
|
+
// the next snapshot()'s signature matches, the expensive recompute (6 sync
|
|
136
|
+
// SQLite opens) + writeFileSync(dashboard.json) are skipped — only the
|
|
137
|
+
// (already-registered) widget factory is refreshed. Kills the per-event
|
|
138
|
+
// main-thread block during typing/idle streaming with no material change.
|
|
139
|
+
lastSnapshotSig = null;
|
|
140
|
+
lastHeartbeatAt = 0;
|
|
141
|
+
// v0.8.5: bumped whenever the cached game-state memo is evicted (bumpGameState
|
|
142
|
+
// for in-process /mega-game writes, the fs.watch callback for cross-process
|
|
143
|
+
// dashboard-server writes, and bindRepo on repo switch) so the snapshot gate
|
|
144
|
+
// invalidates and the widget re-reads theme/mode after the change.
|
|
145
|
+
gameStateBump = 0;
|
|
146
|
+
// Cached cross-repo drift status (recomputed at most every 30s — it opens the
|
|
147
|
+
// machine-wide registry DB, so we don't want to do it on every render frame).
|
|
148
|
+
driftCache = null;
|
|
149
|
+
// S31: cached game-mode state (game_mode_on/theme/tui_display_mode). Lazily
|
|
150
|
+
// read from the game_state SQLite row on the first widget render, then
|
|
151
|
+
// memoized until bumpGameState() evicts it (called by /mega-game after a
|
|
152
|
+
// write) so the widget picks up theme/mode/level changes live without
|
|
153
|
+
// re-querying the DB on every render frame.
|
|
154
|
+
cachedGameState;
|
|
155
|
+
// S32: fs.watch on the current repo's sqlite.db so cross-process writes
|
|
156
|
+
// (e.g. the dashboard server's PUT /api/game-state, which runs as a detached
|
|
157
|
+
// child with no MegaRuntime ref) evict the cached game-state memo. Without
|
|
158
|
+
// this, /mega-game's in-process bumpGameState() is the only eviction trigger
|
|
159
|
+
// and the widget would keep showing stale theme/mode/toggle after a dashboard
|
|
160
|
+
// edit until a restart. The watcher tracks currentStateDir — closed + re-opened
|
|
161
|
+
// by ensureGameStateWatcher() on every bindRepo repo switch. Non-fatal: any
|
|
162
|
+
// fs.watch failure (missing file / platform issue) is swallowed; the next
|
|
163
|
+
// getCachedGameState() snapshot re-queries the DB anyway.
|
|
164
|
+
gameStateWatcher;
|
|
165
|
+
gameStateWatchDir;
|
|
166
|
+
// P2: the last ExtensionContext handed to snapshot()/renderWidget(), stashed
|
|
167
|
+
// so the fs.watch game-state callback can force a widget re-render without
|
|
168
|
+
// a context event (cross-process dashboard edits while pi is idle). Cleared
|
|
169
|
+
// implicitly on construction (undefined → watcher skips until first snap).
|
|
170
|
+
lastWidgetCtx;
|
|
171
|
+
}
|
|
@@ -13,9 +13,10 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { join } from "node:path";
|
|
15
15
|
import { VectorStore } from "../../src/vectorStore.js";
|
|
16
|
-
import { normalizeSessionId } from "../../src/store.js";
|
|
17
16
|
import { Logger } from "../../src/log.js";
|
|
17
|
+
export { RING_MAX } from "./internal-errors.js";
|
|
18
18
|
import { Dashboard } from "../mega-dashboard.js";
|
|
19
|
+
import { createSessionRuntime } from "./helpers.js";
|
|
19
20
|
import { ensureGameStateWatcherImpl, getCachedGameStateImpl, refreshWidgetGameStateImpl, bumpGameStateImpl, disposeRuntimeImpl, } from "./game-state.js";
|
|
20
21
|
import { setEffectImpl, armMegaCacheFlareImpl, armAchievementFlareImpl, makeTierCallbackImpl, pushTickerImpl, } from "./effects.js";
|
|
21
22
|
import { ensurePerfIntervalImpl } from "./perf.js";
|
|
@@ -25,11 +26,13 @@ import { snapshotImpl } from "./runtime-snapshot.js";
|
|
|
25
26
|
import { pressureImpl, effectiveThresholdImpl, pressureBandImpl, } from "./pressure-getters.js";
|
|
26
27
|
import { resetRuntimeImpl } from "./reset-runtime.js";
|
|
27
28
|
import { appendEventImpl } from "./append-event.js";
|
|
29
|
+
import { recordInternalErrorImpl } from "./internal-errors.js";
|
|
30
|
+
import { RuntimeInstrumentation } from "./runtime-instrumentation.js";
|
|
28
31
|
import { getStateDirImpl } from "./get-state-dir.js";
|
|
29
32
|
import { renderWidgetImpl } from "./render-widget.js";
|
|
30
33
|
import { setStatusImpl } from "./status.js";
|
|
31
34
|
import { engineViewImpl } from "./engine-view.js";
|
|
32
|
-
export class MegaRuntime {
|
|
35
|
+
export class MegaRuntime extends RuntimeInstrumentation {
|
|
33
36
|
config;
|
|
34
37
|
// Store/dashboard/logger are rebound per-repo by bindRepo() so each git repo
|
|
35
38
|
// gets its own isolated state dir. They start bound to the global default.
|
|
@@ -39,197 +42,13 @@ export class MegaRuntime {
|
|
|
39
42
|
activeRepoRoot = null;
|
|
40
43
|
currentStateDir;
|
|
41
44
|
// The only mutable per-session state. Reset on session_start / session_tree.
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
dedupAttempts: 0,
|
|
50
|
-
tokensSaved: 0,
|
|
51
|
-
lastCompactAt: null,
|
|
52
|
-
lastRecallAt: null,
|
|
53
|
-
lastInjectAt: null,
|
|
54
|
-
_prevCacheHitPct: null,
|
|
55
|
-
_lastCacheHealthScore: undefined,
|
|
56
|
-
lastNativeCompactAt: null,
|
|
57
|
-
compactCount: 0,
|
|
58
|
-
recallInjections: 0,
|
|
59
|
-
cacheHitTokens: 0,
|
|
60
|
-
lengthStopPending: false,
|
|
61
|
-
errorRetryCount: 0,
|
|
62
|
-
errorRetryUntil: 0,
|
|
63
|
-
consecutiveErrors: 0,
|
|
64
|
-
lastErrorRetryAt: 0,
|
|
65
|
-
retryNudgePending: false,
|
|
66
|
-
errorRetrySessionCount: 0,
|
|
67
|
-
lastErrorText: undefined,
|
|
68
|
-
errorTextRepeatCount: 0,
|
|
69
|
-
poisonedAdviseSent: false,
|
|
70
|
-
providerOutageAdvised: false,
|
|
71
|
-
poisonedCompactSignatures: new Set(),
|
|
72
|
-
// S53: tracks whether withRecallTail injected this turn (consumed at turn_end).
|
|
73
|
-
recallInjectedThisTurn: false,
|
|
74
|
-
poisonedCount: 0,
|
|
75
|
-
extensionInitiatedTurn: false,
|
|
76
|
-
};
|
|
77
|
-
// v0.8.6 cache-stability: the cached live-trim view for the current
|
|
78
|
-
// compaction epoch. Set after a fresh runCompact + computeLiveTrimCut, and
|
|
79
|
-
// replayed verbatim on subsequent gated context events in the SAME epoch
|
|
80
|
-
// (same checkpointId) so the provider KV-cache prefix stays stable instead
|
|
81
|
-
// of being invalidated by a freshly regenerated summary + sentinel every
|
|
82
|
-
// fire. Invalidated on session restart (resetRuntime) and on any native
|
|
83
|
-
// durable compaction (session_compact) that truncates the transcript.
|
|
84
|
-
trimCache = null;
|
|
85
|
-
debounceUntil = 0;
|
|
86
|
-
// S16: debounce for the agent_end resume nudge (avoid busy-loops).
|
|
87
|
-
resumeNudgeUntil = 0;
|
|
88
|
-
// Agent tracking for real-time widget updates
|
|
89
|
-
activeAgents = 0;
|
|
90
|
-
currentTurn = 0;
|
|
91
|
-
// S33: transient MEGA CACHE flare flag (armed by the turn_end scoring hook
|
|
92
|
-
// when cachePct > 100). Copied into widgetData.megaCacheFlare on the next
|
|
93
|
-
// snapshot() so the widget renders the oopsie gag, then reset (one cycle).
|
|
94
|
-
megaCacheFlare = false;
|
|
95
|
-
/** v0.8.3: ambient effect state for animated panel borders keyed off
|
|
96
|
-
* status transitions (level-up, mega-cache overshoot, achievement unlock,
|
|
97
|
-
* compaction start). Threaded into widgetData as `activeEffect`; the widget
|
|
98
|
-
* computes the per-frame phase from startedAt vs Date.now() (non-expired).
|
|
99
|
-
* Null when idle/expired. */
|
|
100
|
-
activeEffect = null;
|
|
101
|
-
megaCacheFlarePct = 0;
|
|
102
|
-
levelUpFlare = false;
|
|
103
|
-
lastLevel = 0;
|
|
104
|
-
// S35: transient achievement-unlock flare (armed by the scoring hooks after
|
|
105
|
-
// evaluateAndUnlockAchievements returns newly-unlocked titles). Copied into
|
|
106
|
-
// widgetData.achievementFlare on the next snapshot() so the widget renders the
|
|
107
|
-
// unlock toast, then reset (one cycle — mirrors megaCacheFlare/levelUpFlare).
|
|
108
|
-
achievementFlare = false;
|
|
109
|
-
achievementFlareTitles = [];
|
|
110
|
-
// S33: last cumulative dedup-collapsed count seen by the session_compact
|
|
111
|
-
// hook, so we only record the DELTA as the dedupe score (leaderboard sums).
|
|
112
|
-
lastDedupCollapsed = 0;
|
|
113
|
-
// Recall block produced by auto-inline (resume/branch) that the next
|
|
114
|
-
// before_agent_start should prepend to the system prompt. Unset after use.
|
|
115
|
-
pendingRecallBlock;
|
|
116
|
-
// S21: memory recall block, parallel to pendingRecallBlock. Same one-shot
|
|
117
|
-
// semantics; composed with the checkpoint block in before_agent_start.
|
|
118
|
-
pendingMemoryRecallBlock;
|
|
119
|
-
statusKey; // current status text for dashboard
|
|
120
|
-
// Active model/provider (for real cost estimation). Captured from ctx.model
|
|
121
|
-
// on model_select + session_start; persisted to SQL so cost + the dashboard
|
|
122
|
-
// can read it without a live ctx.
|
|
123
|
-
currentModel;
|
|
124
|
-
// Live "what it's doing right now" timestamp, used for the fresh-window.
|
|
125
|
-
lastActivityAt = 0;
|
|
126
|
-
// Live per-tier dedup trace (Phase 1): e.g. "L0 ✓ → L1 ✓ → L2 0.91 → stored".
|
|
127
|
-
// Built from the store's sync onTier callback during a compaction so the user
|
|
128
|
-
// watches each tier evaluate in real time. Cleared once the outcome settles.
|
|
129
|
-
tierTrace;
|
|
130
|
-
// Phase 3 — standout toolbar state.
|
|
131
|
-
// Recall/activity ticker: a small ring buffer (≤5) of recent compact/recall
|
|
132
|
-
// events so the widget shows a live history instead of a single last action.
|
|
133
|
-
ticker = [];
|
|
134
|
-
TICKER_MAX = 5;
|
|
135
|
-
// Pulsing status: set true while a compaction is in flight, cleared on result.
|
|
136
|
-
pulsing = false;
|
|
137
|
-
// S21.2: set by `applyMemoryOps` when a memory add/replace/remove lands in
|
|
138
|
-
// the current compaction. The pipeline reads this after a successful compact
|
|
139
|
-
// to decide whether to fire `consolidateMemories` (skip the work entirely
|
|
140
|
-
// when no memory rows changed).
|
|
141
|
-
memoriesTouchedThisCompaction = 0;
|
|
142
|
-
// Rolling "saved" goal for the progress bar — grows as we save more, so the
|
|
143
|
-
// bar always has a meaningful denominator (never sits at 100% forever).
|
|
144
|
-
savedGoal = 50_000;
|
|
145
|
-
// Last explain-why line (dedup reason / anchor-kept / superseded), surfaced
|
|
146
|
-
// while fresh.
|
|
147
|
-
lastWhy = undefined;
|
|
148
|
-
// v0.8.8 Perf dashboard instrumentation: turn/provider start timestamps +
|
|
149
|
-
// the 5s cpu/mem interval handle (one per MegaRuntime, cleared in dispose()).
|
|
150
|
-
perfTurnStart = 0;
|
|
151
|
-
perfProviderStart = 0;
|
|
152
|
-
perfCpuInterval = null;
|
|
153
|
-
perfCpuBaseline;
|
|
154
|
-
// Context tracking for the dashboard (updated in the context handler).
|
|
155
|
-
lastCtxTokens = null;
|
|
156
|
-
lastCtxPercent = null;
|
|
157
|
-
lastCtxWindow = 0;
|
|
158
|
-
// Latest computed widget payload (recomputed per snapshot, rendered per frame).
|
|
159
|
-
widgetData = null;
|
|
160
|
-
// v0.8.5: material-change signature from the last full snapshot() body. When
|
|
161
|
-
// the next snapshot()'s signature matches, the expensive recompute (6 sync
|
|
162
|
-
// SQLite opens) + writeFileSync(dashboard.json) are skipped — only the
|
|
163
|
-
// (already-registered) widget factory is refreshed. Kills the per-event
|
|
164
|
-
// main-thread block during typing/idle streaming with no material change.
|
|
165
|
-
lastSnapshotSig = null;
|
|
166
|
-
lastHeartbeatAt = 0;
|
|
167
|
-
// v0.8.5: bumped whenever the cached game-state memo is evicted (bumpGameState
|
|
168
|
-
// for in-process /mega-game writes, the fs.watch callback for cross-process
|
|
169
|
-
// dashboard-server writes, and bindRepo on repo switch) so the snapshot gate
|
|
170
|
-
// invalidates and the widget re-reads theme/mode after the change.
|
|
171
|
-
gameStateBump = 0;
|
|
172
|
-
// Cached cross-repo drift status (recomputed at most every 30s — it opens the
|
|
173
|
-
// machine-wide registry DB, so we don't want to do it on every render frame).
|
|
174
|
-
driftCache = null;
|
|
175
|
-
// S31: cached game-mode state (game_mode_on/theme/tui_display_mode). Lazily
|
|
176
|
-
// read from the game_state SQLite row on the first widget render, then
|
|
177
|
-
// memoized until bumpGameState() evicts it (called by /mega-game after a
|
|
178
|
-
// write) so the widget picks up theme/mode/level changes live without
|
|
179
|
-
// re-querying the DB on every render frame.
|
|
180
|
-
cachedGameState;
|
|
181
|
-
// S32: fs.watch on the current repo's sqlite.db so cross-process writes
|
|
182
|
-
// (e.g. the dashboard server's PUT /api/game-state, which runs as a detached
|
|
183
|
-
// child with no MegaRuntime ref) evict the cached game-state memo. Without
|
|
184
|
-
// this, /mega-game's in-process bumpGameState() is the only eviction trigger
|
|
185
|
-
// and the widget would keep showing stale theme/mode/toggle after a dashboard
|
|
186
|
-
// edit until a restart. The watcher tracks currentStateDir — closed + re-opened
|
|
187
|
-
// by ensureGameStateWatcher() on every bindRepo repo switch. Non-fatal: any
|
|
188
|
-
// fs.watch failure (missing file / platform issue) is swallowed; the next
|
|
189
|
-
// getCachedGameState() snapshot re-queries the DB anyway.
|
|
190
|
-
gameStateWatcher;
|
|
191
|
-
gameStateWatchDir;
|
|
192
|
-
// P2: the last ExtensionContext handed to snapshot()/renderWidget(), stashed
|
|
193
|
-
// so the fs.watch game-state callback can force a widget re-render without
|
|
194
|
-
// a context event (cross-process dashboard edits while pi is idle). Cleared
|
|
195
|
-
// implicitly on construction (undefined → watcher skips until first snap).
|
|
196
|
-
lastWidgetCtx;
|
|
197
|
-
/**
|
|
198
|
-
* DIAG counters for the "team run doesn't relieve context" investigation.
|
|
199
|
-
* Plain integers, incremented at the three compaction decision points. They
|
|
200
|
-
* let a headless test drive the real event handlers and assert the firing
|
|
201
|
-
* cadence without scraping log files. Inert in production (the live-trim and
|
|
202
|
-
* before-compact probes also emit logger.info, but these counters are always
|
|
203
|
-
* updated and cost nothing).
|
|
204
|
-
*/
|
|
205
|
-
diagLiveTrimFires = 0; // context handler returned a trimmed view
|
|
206
|
-
diagLiveTrimReplays = 0; // v0.8.6: trim view returned via cached replay (skipped re-compact)
|
|
207
|
-
diagBeforeCompactFires = 0; // session_before_compact handler entered
|
|
208
|
-
diagBeforeCompactSupplied = 0; // session_before_compact supplied our trim
|
|
209
|
-
diagAgentEndIdle = 0; // agent_end with activeAgents===0
|
|
210
|
-
diagAgentEndDurable = 0; // agent_end fired ctx.compact() (mid-run durable trim)
|
|
211
|
-
diagAgentEndDurableSkipRecent = 0; // agent_end skipped ctx.compact() — compaction in last 10s (race guard)
|
|
212
|
-
// Per-skip-path counters for the team-run diagnosis.
|
|
213
|
-
diagCtxFastGate = 0; // returned at token fast-gate (below threshold)
|
|
214
|
-
diagCtxNoCompact = 0; // autoCompactCheck().shouldCompact === false
|
|
215
|
-
diagCtxDebounce = 0; // debounceUntil not yet elapsed
|
|
216
|
-
diagCtxRunSkipped = 0; // runCompact() returned skipped
|
|
217
|
-
diagCtxCutNull = 0; // computeLiveTrimCut returned null (anchor/boundary)
|
|
218
|
-
diagCtxThrown = 0; // live-trim try threw (caught)
|
|
219
|
-
// Context health instrumentation (v0.12): rolling ring buffers for
|
|
220
|
-
// drift detection + cache poison Layer 1 hash baseline.
|
|
221
|
-
recentTurnEmbeddings = [];
|
|
222
|
-
recentErrorCategories = [];
|
|
223
|
-
lastPrefixHash = null;
|
|
224
|
-
lastErrorCategory = null;
|
|
225
|
-
/**
|
|
226
|
-
* S26 capture instrumentation: the "model_snapshots empty → $0.00 cost card"
|
|
227
|
-
* bug was invisible because captureModel swallowed the DB write in a silent
|
|
228
|
-
* `catch {}`. These always-updated counters (zero cost) let a headless test or
|
|
229
|
-
* a live capture tell whether captureModel ran and whether the snapshot landed.
|
|
230
|
-
*/
|
|
231
|
-
diagCaptureModelCalls = 0; // captureModel entered with a populated ctx.model
|
|
232
|
-
diagCaptureModelFails = 0; // recordModelSnapshot threw → model_snapshots stays empty
|
|
45
|
+
// Initialized via createSessionRuntime() (helpers.ts delegate-shell split).
|
|
46
|
+
rt = createSessionRuntime();
|
|
47
|
+
// All other field declarations (instrumentation counters, context-health
|
|
48
|
+
// rings, the Sprint-H internal-error ring, live display / cache-stability
|
|
49
|
+
// state) live on the RuntimeInstrumentation base class — see
|
|
50
|
+
// runtime-instrumentation.ts. Field names are unchanged (this.diagXxx,
|
|
51
|
+
// this.trimCache, this.recentInternalErrors, etc).
|
|
233
52
|
// ---- pressure accessors (bodies in pressure-getters.ts) -------------------
|
|
234
53
|
/** Live 0–1 pressure — see `pressureImpl` in pressure-getters.ts for the
|
|
235
54
|
* dual-basis (percent vs token) reconciliation notes. Thin delegate. */
|
|
@@ -247,6 +66,7 @@ export class MegaRuntime {
|
|
|
247
66
|
return pressureBandImpl(this);
|
|
248
67
|
}
|
|
249
68
|
constructor(config) {
|
|
69
|
+
super();
|
|
250
70
|
this.config = config;
|
|
251
71
|
this.store = new VectorStore({
|
|
252
72
|
dedupSim: config.dedupSim,
|
|
@@ -299,6 +119,20 @@ export class MegaRuntime {
|
|
|
299
119
|
appendEvent(event, fields) {
|
|
300
120
|
appendEventImpl(this, event, fields);
|
|
301
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* Sprint H (Finding 3 / Option A): record an internal store/service-write
|
|
124
|
+
* failure category into the `recentInternalErrors` ring. Called AT each
|
|
125
|
+
* failure emit site (see the §2.3a audit in
|
|
126
|
+
* docs/specs/c2-resume-and-health-fixes.md) — never by a central log filter.
|
|
127
|
+
* Mirrors `recentErrorCategories` (cap = RING_MAX, shift when over).
|
|
128
|
+
*
|
|
129
|
+
* NOTE (process boundary): the ring is per-runtime / in-memory. A child
|
|
130
|
+
* subprocess's failures do NOT reach the parent's ring; the parent-side
|
|
131
|
+
* dashboard already aggregates events.log cross-process, so nothing is lost.
|
|
132
|
+
*/
|
|
133
|
+
recordInternalError(category) {
|
|
134
|
+
recordInternalErrorImpl(this, category);
|
|
135
|
+
}
|
|
302
136
|
/** S21: state dir of the currently bound repo (where memories live) — thin
|
|
303
137
|
* delegate to `getStateDirImpl` (get-state-dir.ts). */
|
|
304
138
|
getStateDir() {
|
|
@@ -85,7 +85,7 @@ export function buildWidgetLines(wd, width, activeAgents) {
|
|
|
85
85
|
`${C.gray}all-time${C.reset} ${fmtTokens(wd.repoIn)}→${fmtTokens(wd.repoKept)} kept ${C.blue}(${wd.rTxt}% freed)${C.reset}`,
|
|
86
86
|
`${wd.repoChk} chk/${wd.repoSess} sess`,
|
|
87
87
|
`${C.gray}mem${C.reset} ${wd.embedderName} · ${wd.chk} chunks · ${C.blue}comp ${wd.compStr}${C.reset}`,
|
|
88
|
-
`${C.gray}
|
|
88
|
+
`${C.gray}comp lag${C.reset} ${wd.driftStatus === "ok" ? C.green : C.amber}${wd.driftStatus}${C.reset}`,
|
|
89
89
|
`${C.gray}compact${C.reset} ${sinceCompactStr(wd.sinceCompact)}`,
|
|
90
90
|
...(wd.perTurnCacheHitPct != null
|
|
91
91
|
? [
|
|
@@ -24,6 +24,10 @@ function storeFor(stateDir) {
|
|
|
24
24
|
}
|
|
25
25
|
return s;
|
|
26
26
|
}
|
|
27
|
+
/** Read-only view of the turn store for a state dir (e.g. nextTurnIndexFor). */
|
|
28
|
+
export function turnReaderFor(stateDir) {
|
|
29
|
+
return storeFor(stateDir).asReader();
|
|
30
|
+
}
|
|
27
31
|
/** Map a legacy RecallSource onto the contract `TurnRecallEntry.source` enum. */
|
|
28
32
|
function mapSource(s) {
|
|
29
33
|
switch (s) {
|
|
@@ -52,6 +56,7 @@ export function recordTurnWrite(config, input, stateDir) {
|
|
|
52
56
|
conversationId: input.conversationId,
|
|
53
57
|
sessionId: input.sessionId,
|
|
54
58
|
turnIndex: input.turnIndex,
|
|
59
|
+
sessionTurnIndex: input.sessionTurnIndex,
|
|
55
60
|
role: input.role,
|
|
56
61
|
endedAt: input.endedAt ?? input.startedAt ?? Date.now(),
|
|
57
62
|
ctxTokens: input.ctxTokens,
|
|
@@ -136,6 +136,10 @@ export function createMegaBridge(opts) {
|
|
|
136
136
|
if (e instanceof ForkError) {
|
|
137
137
|
return { error: e.code };
|
|
138
138
|
}
|
|
139
|
+
// The bridge is pi-agnostic (no runtime handle) — the host-side adapter
|
|
140
|
+
// (ithacus) owns the internal-error ring for fork failures, so this
|
|
141
|
+
// re-thrown non-ForkError is recorded there, not here.
|
|
142
|
+
// guardrails-allow INTERNAL-ERR: bridge is pi-agnostic; no runtime handle — host adapter records fork failures
|
|
139
143
|
throw e;
|
|
140
144
|
}
|
|
141
145
|
},
|
|
@@ -155,16 +159,26 @@ export function createMegaBridge(opts) {
|
|
|
155
159
|
}, null, stateDir);
|
|
156
160
|
},
|
|
157
161
|
recordTurn(input) {
|
|
162
|
+
// S49R: child turns get the conversation-monotonic index (MAX+1) so a
|
|
163
|
+
// re-dispatched child (same conversation, restarted session counter) never
|
|
164
|
+
// collides with UNIQUE(conversation_id, turn_index). Carry the child's
|
|
165
|
+
// session counter as sessionTurnIndex for the raw_transcript join.
|
|
166
|
+
const turnIndex = getTurnStore().asReader().nextTurnIndexFor(input.conversationId);
|
|
158
167
|
const turn = {
|
|
159
168
|
conversationId: input.conversationId,
|
|
160
169
|
sessionId: input.sessionId,
|
|
161
|
-
turnIndex
|
|
170
|
+
turnIndex,
|
|
171
|
+
sessionTurnIndex: input.turnIndex,
|
|
162
172
|
role: input.role ?? "assistant",
|
|
163
173
|
endedAt: input.endedAt ?? Date.now(),
|
|
164
174
|
ctxTokens: input.ctxTokens,
|
|
165
175
|
ctxPercent: input.ctxPercent,
|
|
166
176
|
model: input.model,
|
|
167
177
|
};
|
|
178
|
+
// The bridge is pi-agnostic (no runtime handle); the host-side adapter
|
|
179
|
+
// (ithacus) owns the internal-error ring for appendTurn failures. The
|
|
180
|
+
// recordTurn child-path twin of Finding 2 is recorded there, not here.
|
|
181
|
+
// guardrails-allow INTERNAL-ERR: bridge is pi-agnostic; no runtime handle — host adapter records appendTurn failures
|
|
168
182
|
getTurnStore().asWriter().appendTurn(turn);
|
|
169
183
|
},
|
|
170
184
|
close() {
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
function truncate(s, max) {
|
|
2
|
+
return s.length <= max ? s : `${s.slice(0, max)}…`;
|
|
3
|
+
}
|
|
4
|
+
function summarizeBlock(m) {
|
|
5
|
+
if (m.role === "tool")
|
|
6
|
+
return `tool_result ${m.toolName ?? "?"}: ${truncate(m.output ?? m.text, 160)}`;
|
|
7
|
+
if (m.toolName)
|
|
8
|
+
return `tool_use ${m.toolName}(${truncate(m.input ?? "", 160)})`;
|
|
9
|
+
return truncate(m.text, 160);
|
|
10
|
+
}
|
|
11
|
+
function stripTag(block, tag) {
|
|
12
|
+
const start = `<${tag}>`;
|
|
13
|
+
const end = `</${tag}>`;
|
|
14
|
+
const s = block.indexOf(start);
|
|
15
|
+
const e = block.indexOf(end);
|
|
16
|
+
if (s === -1 || e === -1)
|
|
17
|
+
return block;
|
|
18
|
+
return block.slice(0, s) + block.slice(e + end.length);
|
|
19
|
+
}
|
|
20
|
+
function extractTag(block, tag) {
|
|
21
|
+
const s = block.indexOf(`<${tag}>`);
|
|
22
|
+
const e = block.indexOf(`</${tag}>`);
|
|
23
|
+
if (s === -1 || e === -1)
|
|
24
|
+
return undefined;
|
|
25
|
+
return block.slice(s + `<${tag}>`.length, e);
|
|
26
|
+
}
|
|
27
|
+
/** Normalize a raw summary into user-facing "Summary: ..." text. */
|
|
28
|
+
export function formatCompactSummary(summary) {
|
|
29
|
+
const withoutAnalysis = stripTag(summary, "analysis");
|
|
30
|
+
let formatted = withoutAnalysis;
|
|
31
|
+
const content = extractTag(withoutAnalysis, "summary");
|
|
32
|
+
if (content !== undefined) {
|
|
33
|
+
formatted = withoutAnalysis.replace(`<summary>${content}</summary>`, `Summary:\n${content.trim()}`);
|
|
34
|
+
}
|
|
35
|
+
return formatted.replace(/\n{3,}/g, "\n\n").trim();
|
|
36
|
+
}
|
|
37
|
+
/** Extract the prior "highlights" + "timeline" sections from an existing summary. */
|
|
38
|
+
export function extractSummaryHighlights(summary) {
|
|
39
|
+
const lines = formatCompactSummary(summary).split("\n");
|
|
40
|
+
const out = [];
|
|
41
|
+
let inTimeline = false;
|
|
42
|
+
for (const line of lines) {
|
|
43
|
+
const t = line.trimEnd();
|
|
44
|
+
if (!t || t === "Summary:" || t === "Conversation summary:")
|
|
45
|
+
continue;
|
|
46
|
+
if (t === "- Key timeline:") {
|
|
47
|
+
inTimeline = true;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (inTimeline)
|
|
51
|
+
continue;
|
|
52
|
+
out.push(t);
|
|
53
|
+
}
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
export function extractSummaryTimeline(summary) {
|
|
57
|
+
const lines = formatCompactSummary(summary).split("\n");
|
|
58
|
+
const out = [];
|
|
59
|
+
let inTimeline = false;
|
|
60
|
+
for (const line of lines) {
|
|
61
|
+
const t = line.trimEnd();
|
|
62
|
+
if (t === "- Key timeline:") {
|
|
63
|
+
inTimeline = true;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (!inTimeline)
|
|
67
|
+
continue;
|
|
68
|
+
if (!t)
|
|
69
|
+
break;
|
|
70
|
+
out.push(t);
|
|
71
|
+
}
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
/** Merge an existing compact summary with a new one (accumulate, don't overwrite). */
|
|
75
|
+
export function mergeCompactSummaries(existing, newSummary) {
|
|
76
|
+
if (!existing)
|
|
77
|
+
return newSummary;
|
|
78
|
+
const prevHighlights = extractSummaryHighlights(existing);
|
|
79
|
+
const newHighlights = extractSummaryHighlights(formatCompactSummary(newSummary));
|
|
80
|
+
const newTimeline = extractSummaryTimeline(formatCompactSummary(newSummary));
|
|
81
|
+
const lines = ["<summary>", "Conversation summary:"];
|
|
82
|
+
if (prevHighlights.length) {
|
|
83
|
+
lines.push("- Previously compacted context:");
|
|
84
|
+
prevHighlights.forEach((l) => lines.push(` ${l}`));
|
|
85
|
+
}
|
|
86
|
+
if (newHighlights.length) {
|
|
87
|
+
lines.push("- Newly compacted context:");
|
|
88
|
+
newHighlights.forEach((l) => lines.push(` ${l}`));
|
|
89
|
+
}
|
|
90
|
+
if (newTimeline.length) {
|
|
91
|
+
lines.push("- Key timeline:");
|
|
92
|
+
newTimeline.forEach((l) => lines.push(` ${l}`));
|
|
93
|
+
}
|
|
94
|
+
lines.push("</summary>");
|
|
95
|
+
return lines.join("\n");
|
|
96
|
+
}
|
|
97
|
+
// Private helpers re-exported for the shell's summarizeMessages (which stays
|
|
98
|
+
// in compact.ts because it depends on the inference helpers there).
|
|
99
|
+
export { truncate, summarizeBlock };
|