pi-mega-compact 0.20.82 → 0.20.84

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 (109) hide show
  1. package/dist/extensions/dashboard-server/routes-rag-settings-helpers.js +0 -1
  2. package/dist/extensions/mega-config.js +3 -2
  3. package/dist/extensions/mega-events/agent-handlers/turnEndHandler/recordTurnRow.js +0 -17
  4. package/dist/extensions/mega-events/context-handler/triggerGuard.js +86 -0
  5. package/dist/extensions/mega-events/context-handler.js +12 -0
  6. package/dist/extensions/mega-events/register.js +0 -3
  7. package/dist/src/failback/types.js +11 -0
  8. package/extensions/dashboard-client/dist/assets/{AdminTab-BTCZLMCc.js → AdminTab-BsA8-_bX.js} +2 -2
  9. package/extensions/dashboard-client/dist/assets/{AdminTab-BTCZLMCc.js.map → AdminTab-BsA8-_bX.js.map} +1 -1
  10. package/extensions/dashboard-client/dist/assets/{AreaChart-BTMI0QnR.js → AreaChart-BRHEfWhc.js} +2 -2
  11. package/extensions/dashboard-client/dist/assets/{AreaChart-BTMI0QnR.js.map → AreaChart-BRHEfWhc.js.map} +1 -1
  12. package/extensions/dashboard-client/dist/assets/{BarChart-C1F8wLM3.js → BarChart-C-M0LaFc.js} +2 -2
  13. package/extensions/dashboard-client/dist/assets/{BarChart-C1F8wLM3.js.map → BarChart-C-M0LaFc.js.map} +1 -1
  14. package/extensions/dashboard-client/dist/assets/{CacheTab-ORjrhSbZ.js → CacheTab-Cj3n59U3.js} +2 -2
  15. package/extensions/dashboard-client/dist/assets/{CacheTab-ORjrhSbZ.js.map → CacheTab-Cj3n59U3.js.map} +1 -1
  16. package/extensions/dashboard-client/dist/assets/{ConfigTab-CtXVSxD_.js → ConfigTab-C016XTNC.js} +2 -2
  17. package/extensions/dashboard-client/dist/assets/{ConfigTab-CtXVSxD_.js.map → ConfigTab-C016XTNC.js.map} +1 -1
  18. package/extensions/dashboard-client/dist/assets/{EventsTab-DQdwa9IB.js → EventsTab-DE29fuqe.js} +2 -2
  19. package/extensions/dashboard-client/dist/assets/{EventsTab-DQdwa9IB.js.map → EventsTab-DE29fuqe.js.map} +1 -1
  20. package/extensions/dashboard-client/dist/assets/{HealthTab-BpVO7BeO.js → HealthTab-D4eVI8ar.js} +2 -2
  21. package/extensions/dashboard-client/dist/assets/{HealthTab-BpVO7BeO.js.map → HealthTab-D4eVI8ar.js.map} +1 -1
  22. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-Cl9mWaR8.js → MaintenanceTab-4fJ7M4S4.js} +2 -2
  23. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-Cl9mWaR8.js.map → MaintenanceTab-4fJ7M4S4.js.map} +1 -1
  24. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-D3kIPeCy.js → MemoryMapTab-CDbcK9e4.js} +2 -2
  25. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-D3kIPeCy.js.map → MemoryMapTab-CDbcK9e4.js.map} +1 -1
  26. package/extensions/dashboard-client/dist/assets/{MetricsCards-Cu1zDkfm.js → MetricsCards-VUEcgGzo.js} +2 -2
  27. package/extensions/dashboard-client/dist/assets/{MetricsCards-Cu1zDkfm.js.map → MetricsCards-VUEcgGzo.js.map} +1 -1
  28. package/extensions/dashboard-client/dist/assets/MetricsTab-CKqJ0Zl-.js +2 -0
  29. package/extensions/dashboard-client/dist/assets/{MetricsTab-BAvMzWuF.js.map → MetricsTab-CKqJ0Zl-.js.map} +1 -1
  30. package/extensions/dashboard-client/dist/assets/OverviewTab-9Kxo79mi.js +16 -0
  31. package/extensions/dashboard-client/dist/assets/OverviewTab-9Kxo79mi.js.map +1 -0
  32. package/extensions/dashboard-client/dist/assets/{ReposTab-BUESjauJ.js → ReposTab-C4iveqrP.js} +2 -2
  33. package/extensions/dashboard-client/dist/assets/{ReposTab-BUESjauJ.js.map → ReposTab-C4iveqrP.js.map} +1 -1
  34. package/extensions/dashboard-client/dist/assets/{SessionsTab-DyJmQ_78.js → SessionsTab-DSEwkMQm.js} +2 -2
  35. package/extensions/dashboard-client/dist/assets/{SessionsTab-DyJmQ_78.js.map → SessionsTab-DSEwkMQm.js.map} +1 -1
  36. package/extensions/dashboard-client/dist/assets/{SetupTab-q8wYL5A-.js → SetupTab-CCyWgy4O.js} +2 -2
  37. package/extensions/dashboard-client/dist/assets/{SetupTab-q8wYL5A-.js.map → SetupTab-CCyWgy4O.js.map} +1 -1
  38. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-DIRH1Pq7.js → TimeSavedCard-CyD8e_e_.js} +2 -2
  39. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-DIRH1Pq7.js.map → TimeSavedCard-CyD8e_e_.js.map} +1 -1
  40. package/extensions/dashboard-client/dist/assets/{TurnMemoryView-CrYSOcZX.js → TurnMemoryView-5tLbWkRv.js} +2 -2
  41. package/extensions/dashboard-client/dist/assets/{TurnMemoryView-CrYSOcZX.js.map → TurnMemoryView-5tLbWkRv.js.map} +1 -1
  42. package/extensions/dashboard-client/dist/assets/TurnsTab-C7pm92Hg.js +2 -0
  43. package/extensions/dashboard-client/dist/assets/{TurnsTab-C0jARSvU.js.map → TurnsTab-C7pm92Hg.js.map} +1 -1
  44. package/extensions/dashboard-client/dist/assets/{VcStatusBadge-D83Fz939.js → VcStatusBadge-ObhcIyfN.js} +2 -2
  45. package/extensions/dashboard-client/dist/assets/{VcStatusBadge-D83Fz939.js.map → VcStatusBadge-ObhcIyfN.js.map} +1 -1
  46. package/extensions/dashboard-client/dist/assets/{VectorCortexTab-BFXh5Nu7.js → VectorCortexTab-BtHZb7o7.js} +2 -2
  47. package/extensions/dashboard-client/dist/assets/{VectorCortexTab-BFXh5Nu7.js.map → VectorCortexTab-BtHZb7o7.js.map} +1 -1
  48. package/extensions/dashboard-client/dist/assets/{WikiTab-CEdJXkou.js → WikiTab-lQ4l3QIK.js} +2 -2
  49. package/extensions/dashboard-client/dist/assets/{WikiTab-CEdJXkou.js.map → WikiTab-lQ4l3QIK.js.map} +1 -1
  50. package/extensions/dashboard-client/dist/assets/{button-BlRKWQsj.js → button-DhQQAJGm.js} +2 -2
  51. package/extensions/dashboard-client/dist/assets/{button-BlRKWQsj.js.map → button-DhQQAJGm.js.map} +1 -1
  52. package/extensions/dashboard-client/dist/assets/{card-Cx6WA_2T.js → card-Cfhz9X31.js} +2 -2
  53. package/extensions/dashboard-client/dist/assets/{card-Cx6WA_2T.js.map → card-Cfhz9X31.js.map} +1 -1
  54. package/extensions/dashboard-client/dist/assets/{client-extra-BRFScIiK.js → client-extra-CKNOGEz_.js} +2 -2
  55. package/extensions/dashboard-client/dist/assets/{client-extra-BRFScIiK.js.map → client-extra-CKNOGEz_.js.map} +1 -1
  56. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-DrkPNaOf.js → generateCategoricalChart-JKjK9_cB.js} +2 -2
  57. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-DrkPNaOf.js.map → generateCategoricalChart-JKjK9_cB.js.map} +1 -1
  58. package/extensions/dashboard-client/dist/assets/{index-DUnKr3wN.js → index-iuxgtzEW.js} +6 -6
  59. package/extensions/dashboard-client/dist/assets/index-iuxgtzEW.js.map +1 -0
  60. package/extensions/dashboard-client/dist/assets/{switch-Wr0u4HKw.js → switch-7EHEQMSP.js} +2 -2
  61. package/extensions/dashboard-client/dist/assets/{switch-Wr0u4HKw.js.map → switch-7EHEQMSP.js.map} +1 -1
  62. package/extensions/dashboard-client/dist/assets/{toggle-C_eSNeMT.js → toggle-z1lWY4UM.js} +2 -2
  63. package/extensions/dashboard-client/dist/assets/{toggle-C_eSNeMT.js.map → toggle-z1lWY4UM.js.map} +1 -1
  64. package/extensions/dashboard-client/dist/assets/{useSSE-C_oFqrwF.js → useSSE-sctOZuRM.js} +2 -2
  65. package/extensions/dashboard-client/dist/assets/{useSSE-C_oFqrwF.js.map → useSSE-sctOZuRM.js.map} +1 -1
  66. package/extensions/dashboard-client/dist/index.html +1 -1
  67. package/extensions/dashboard-client/src/App.tsx +1 -11
  68. package/extensions/dashboard-client/src/components/RepoContextStack.tsx +21 -28
  69. package/extensions/dashboard-client/src/tabs/dashHashRouter.ts +1 -31
  70. package/extensions/dashboard-server/routes-rag-settings-helpers.ts +0 -6
  71. package/extensions/mega-config-types.ts +4 -3
  72. package/extensions/mega-config.ts +3 -2
  73. package/extensions/mega-events/agent-handlers/turnEndHandler/recordTurnRow.ts +0 -17
  74. package/extensions/mega-events/context-handler/triggerGuard.ts +137 -0
  75. package/extensions/mega-events/context-handler.ts +11 -0
  76. package/extensions/mega-events/register.ts +0 -3
  77. package/package.json +1 -1
  78. package/src/failback/types.ts +46 -0
  79. package/dist/extensions/mega-events/analytics-handler.js +0 -169
  80. package/dist/src/store/analytics/connection.js +0 -114
  81. package/dist/src/store/analytics/contract-compliance.test/_helpers.js +0 -39
  82. package/dist/src/store/analytics/index.js +0 -25
  83. package/dist/src/store/analytics/memory-store.js +0 -111
  84. package/dist/src/store/analytics/schema.js +0 -108
  85. package/dist/src/store/analytics/sqlite-store/admin.js +0 -84
  86. package/dist/src/store/analytics/sqlite-store/ctx.js +0 -1
  87. package/dist/src/store/analytics/sqlite-store/read.js +0 -25
  88. package/dist/src/store/analytics/sqlite-store/rows.js +0 -44
  89. package/dist/src/store/analytics/sqlite-store/write.js +0 -90
  90. package/dist/src/store/analytics/sqlite-store.js +0 -79
  91. package/dist/src/store/analytics/types.js +0 -17
  92. package/extensions/dashboard-client/dist/assets/MetricsTab-BAvMzWuF.js +0 -2
  93. package/extensions/dashboard-client/dist/assets/OverviewTab-CouEq0qi.js +0 -16
  94. package/extensions/dashboard-client/dist/assets/OverviewTab-CouEq0qi.js.map +0 -1
  95. package/extensions/dashboard-client/dist/assets/TurnsTab-C0jARSvU.js +0 -2
  96. package/extensions/dashboard-client/dist/assets/index-DUnKr3wN.js.map +0 -1
  97. package/extensions/mega-events/analytics-handler.ts +0 -236
  98. package/src/store/analytics/connection.ts +0 -128
  99. package/src/store/analytics/contract-compliance.test/_helpers.ts +0 -47
  100. package/src/store/analytics/index.ts +0 -61
  101. package/src/store/analytics/memory-store.ts +0 -161
  102. package/src/store/analytics/schema.ts +0 -132
  103. package/src/store/analytics/sqlite-store/admin.ts +0 -96
  104. package/src/store/analytics/sqlite-store/ctx.ts +0 -9
  105. package/src/store/analytics/sqlite-store/read.ts +0 -46
  106. package/src/store/analytics/sqlite-store/rows.ts +0 -58
  107. package/src/store/analytics/sqlite-store/write.ts +0 -111
  108. package/src/store/analytics/sqlite-store.ts +0 -119
  109. package/src/store/analytics/types.ts +0 -202
@@ -112,9 +112,6 @@ export interface MegaConfig {
112
112
  /** S51: auto-categorizing wiki (k-means + TF-IDF over real embeddings). Default ON.
113
113
  * Mirrors TurnsConfig.AUTO_WIKI_ENABLED. Rebuild fires every Nth compaction. */
114
114
  autoWikiEnabled: boolean;
115
- /** PMA: provider/model analytics (isolated analytics.db). Default ON. OFF =
116
- * no analytics.db open/write — pre-program behavior byte-for-byte. */
117
- providerModelAnalytics: boolean;
118
115
  /** Cross-repo recall enabled (S17). Resume + /mega-recall --cross-repo can
119
116
  * pull checkpoints from OTHER repos via the PGlite HNSW index. Default true. */
120
117
  crossRepoEnabled: boolean;
@@ -139,6 +136,10 @@ export interface MegaConfig {
139
136
  * the tail of the view when auto is OFF AND no trim action is needed. Default ON
140
137
  * (true). When false, restores the pre-sprint systemPrompt prepend behavior. */
141
138
  recallTailInject: boolean;
139
+ /** 3WF-1: TriggerGuard — re-stage a recall block at the context event seam when
140
+ * session_start never fired, so every session has a staged block (recall hits,
141
+ * else a provenance floor). Default ON; OFF = byte-identical pre-sprint. */
142
+ threeWayFailback: boolean;
142
143
  /** A1 PLAN_V2 Phase 2: Message Separation — isolate user/assistant turns
143
144
  * from volatile tool results so the prompt-cache prefix stays stable.
144
145
  * PC-A: positive sprint flag, now default ON; flag-OFF (=0) is byte-identical
@@ -196,8 +196,6 @@ export function loadConfig(): MegaConfig {
196
196
  // turn path (S48 behavior). Mirrors src/config/turns.ts TURNS_DB_ENABLED.
197
197
  turnsDbEnabled: envBool("MEGACOMPACT_TURNS_DB", true),
198
198
  autoWikiEnabled: envBool("MEGACOMPACT_AUTO_WIKI", true),
199
- // PMA: provider/model analytics (isolated analytics.db). Default ON.
200
- providerModelAnalytics: envBool("MEGACOMPACT_PROVIDER_MODEL_ANALYTICS", true),
201
199
  crossRepoEnabled: envBool("MEGACOMPACT_CROSSREPO_ENABLED", true),
202
200
  crossRepoCosine: Number(process.env.MEGACOMPACT_CROSSREPO_COSINE ?? "0.90"),
203
201
  memoryAutoReview: envBool("MEGACOMPACT_MEMORY_AUTO_REVIEW", true),
@@ -205,6 +203,9 @@ export function loadConfig(): MegaConfig {
205
203
  recallMaxTokens: envFlag("MEGACOMPACT_RECALL_MAX_TOKENS", 1500),
206
204
  windowDedupe: envBool("MEGACOMPACT_WINDOW_DEDUPE", true),
207
205
  recallTailInject: envBool("MEGACOMPACT_RECALL_TAIL_INJECT", true),
206
+ // 3WF-1: TriggerGuard — guarantee a staged recall block on every context
207
+ // event even when session_start never fires. Default ON; OFF = byte-identical.
208
+ threeWayFailback: envBool("MEGACOMPACT_THREE_WAY_FAILBACK", true),
208
209
  // PC-A: positive sprint flag, default ON. =0 byte-identical to the
209
210
  // pre-change OFF state (single gate lives at the call site in tailResult.ts).
210
211
  messageSeparation: envBool("MEGACOMPACT_MESSAGE_SEPARATION", true),
@@ -51,23 +51,6 @@ export function recordTurnRow(
51
51
  turnsDbEnabled: config.turnsDbEnabled,
52
52
  });
53
53
  } catch (e) {
54
- // DuplicateTurnError: the turn was already persisted (the same turnIndex
55
- // was written by an earlier code path — a double-write race). This is a
56
- // SUCCESS, not a failure: the turn IS in turns.db. Emit turn_written with
57
- // duplicate:true so events.log stops showing 289 false "turn_write_failed"
58
- // entries (which made the Turns tab + context engine look broken).
59
- // Uses the same message-pattern idiom as write.ts's UNIQUE constraint guard
60
- // (no new import; DuplicateTurnError isn't re-exported through mega-turn-store
61
- // and the append-only invariant forbids UPDATE/UPSERT in the store layer).
62
- if (e instanceof Error && /Duplicate turn:/.test(e.message)) {
63
- runtime.dashboard.event("turn_written", {
64
- turnIndex: event.turnIndex,
65
- pressureBand: runtime.pressureBand ?? null,
66
- turnsDbEnabled: config.turnsDbEnabled,
67
- duplicate: true,
68
- });
69
- return;
70
- }
71
54
  runtime.dashboard.event("turn_write_failed", {
72
55
  turnIndex: event.turnIndex,
73
56
  turnsDbEnabled: config.turnsDbEnabled,
@@ -0,0 +1,137 @@
1
+ /**
2
+ * extensions/mega-events/context-handler/triggerGuard.ts — 3WF-1 TriggerGuard.
3
+ *
4
+ * Production-incident fix: `session_start` can silently never fire (pi host
5
+ * behavior), so the only recall staging point (session-handlers.ts) is skipped
6
+ * and a session replays with NO recall block and NO telemetry. TriggerGuard
7
+ * re-stages at the `context` event seam — the one place that ALWAYS runs and
8
+ * where the staged block is composed into the model view on this same event.
9
+ *
10
+ * Stack position (context-handler.ts): run DIRECTLY BEFORE `buildTailResult` is
11
+ * built, so a freshly staged block is picked up by the existing tail machinery
12
+ * (recall-tail.ts) with zero changes to it.
13
+ *
14
+ * Contract:
15
+ * - one-shot per MegaRuntime (WeakMap) — recall does NOT re-run on every event.
16
+ * - if runtime.pendingRecallBlock != null already, no-op (session_start won).
17
+ * - read-only recall via engine.recall -> formatRecallBlock; no vectorMarkInjected,
18
+ * no S43 telemetry, no turn writes (this is a guard, not an inline).
19
+ * - an empty store (checkpointCount == 0) is a genuinely new session: no crash,
20
+ * no floor staged, just marked done.
21
+ * - a store WITH checkpoints whose recall returns nothing: stage the provenance
22
+ * floor (newest checkpoint summary) instead of silence.
23
+ * - never throws (whole body guarded), non-fatal, PREVENT-PI-003 (block is plain
24
+ * text; role decided downstream by withRecallTail as user).
25
+ */
26
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
27
+ import type { MegaRuntime } from "../../mega-runtime.js";
28
+ import type { MegaConfig } from "../../mega-config.js";
29
+ import { normalizeSessionId } from "../../../src/store.js";
30
+ import { recall } from "../../../src/engine.js";
31
+ import { formatRecallBlock } from "../../../src/recall.js";
32
+ import { vectorStats, vectorList } from "../../../src/vectorStore.js";
33
+ import { recentUserQuery } from "../../mega-runtime.js";
34
+
35
+ /** One-shot completion marker per MegaRuntime (dies with the runtime). */
36
+ const guardDone = new WeakMap<MegaRuntime, { done: true }>();
37
+
38
+ /** The minimal ctx surface the guard reads (query + session id). Accepted instead
39
+ * of the full ExtensionContext so tests can pass a thin stub without a full
40
+ * pi ctx; structurally satisfied by the real ExtensionContext. */
41
+ interface GuardCtx {
42
+ sessionManager: { getSessionId(): string };
43
+ }
44
+
45
+
46
+ /**
47
+ * Run the TriggerGuard for this context event. Best-effort: never throws; any
48
+ * failure degrades to the pre-sprint path (no staged block).
49
+ */
50
+ export function runTriggerGuard(
51
+ runtime: MegaRuntime,
52
+ config: MegaConfig,
53
+ ctx: GuardCtx,
54
+ ): void {
55
+ try {
56
+ // Flag OFF => byte-identical pre-sprint behavior (no touch, no telemetry).
57
+ if (!config.threeWayFailback) return;
58
+ // session_start already staged a block => the normal path wins, no-op.
59
+ if (runtime.pendingRecallBlock != null) return;
60
+ // One-shot: we've already decided for this runtime.
61
+ if (guardDone.has(runtime)) return;
62
+
63
+ // recentUserQuery only reads sessionManager.getEntries(); the structural
64
+ // GuardCtx satisfies it at runtime (cast to the full type it expects).
65
+ const query = recentUserQuery(ctx as unknown as ExtensionContext);
66
+ if (!query) {
67
+ guardDone.set(runtime, { done: true });
68
+ return;
69
+ }
70
+
71
+ const sid = normalizeSessionId(ctx.sessionManager.getSessionId());
72
+ const stats = vectorStats(runtime.store, sid);
73
+ // Empty store => genuinely new session; not a recall failure. Mark done so
74
+ // we don't re-check forever, stage nothing (no crash, no floor).
75
+ if (stats.checkpointCount <= 0) {
76
+ guardDone.set(runtime, { done: true });
77
+ return;
78
+ }
79
+
80
+ // Read-only recall: search + rank only; skipInjected:false means we keep
81
+ // hits already injected this session, but we never call vectorMarkInjected
82
+ // ourselves (A1) — formatRecallBlock does the same as session_start uses.
83
+ const hits = recall(
84
+ { sessionId: sid, query, limit: config.autoInlineK, skipInjected: false },
85
+ runtime.store,
86
+ ).hits;
87
+
88
+ if (hits.length > 0) {
89
+ runtime.pendingRecallBlock = formatRecallBlock(hits);
90
+ guardDone.set(runtime, { done: true });
91
+ runtime.appendEvent("three_way_guard_fired", {
92
+ source: "recall",
93
+ hitCount: hits.length,
94
+ topScore: hits[0]?.score ?? null,
95
+ });
96
+ return;
97
+ }
98
+
99
+ // Store HAS checkpoints but recall found nothing relevant: stage a
100
+ // provenance floor built from the newest checkpoint summary rather than
101
+ // silence (the incident's "recall silently never ran" shape).
102
+ const floor = buildFloorBlock(runtime, sid);
103
+ runtime.pendingRecallBlock = floor;
104
+ guardDone.set(runtime, { done: true });
105
+ runtime.appendEvent("three_way_floor_used", { basis: "lastCheckpoint" });
106
+ } catch {
107
+ /* never throws; best-effort guard */
108
+ }
109
+ }
110
+
111
+ /** Build the provenance floor string from the session's newest checkpoint. */
112
+ function buildFloorBlock(runtime: MegaRuntime, sid: string): string {
113
+ try {
114
+ const cps = vectorList(runtime.store, sid);
115
+ let newest = cps[0];
116
+ for (const cp of cps) {
117
+ if (!newest || (cp.timestamp ?? 0) > (newest.timestamp ?? 0)) newest = cp;
118
+ }
119
+ const summary = newest?.summary?.trim();
120
+ if (summary) {
121
+ return (
122
+ "The following compacted context is the most recent checkpoint from " +
123
+ "this session (recall found no query-relevant match):\n\n" + summary
124
+ );
125
+ }
126
+ return (
127
+ "This session has compacted context but recall could not surface a " +
128
+ "checkpoint relevant to the current request; the most recent checkpoint " +
129
+ "summary is unavailable."
130
+ );
131
+ } catch {
132
+ return (
133
+ "This session has compacted context but recall could not surface a " +
134
+ "checkpoint relevant to the current request."
135
+ );
136
+ }
137
+ }
@@ -23,6 +23,7 @@ import type { MegaRuntime } from "../mega-runtime.js";
23
23
  import { piCompactWouldNoop } from "../mega-pipeline.js";
24
24
  import type { MegaConfig } from "../mega-config.js";
25
25
  import { buildTailResult } from "./context-handler/tailResult.js";
26
+ import { runTriggerGuard } from "./context-handler/triggerGuard.js";
26
27
  import { persistEpochAndMaintain } from "./context-handler/afterCompact.js";
27
28
  import { appendMirrorAndLedger } from "./context-handler/dbMirrorAppend.js";
28
29
  import { evaluateGate } from "./context-handler/gateCheck.js";
@@ -53,6 +54,16 @@ export function registerContextHandler(
53
54
  const usage = ctx.getContextUsage();
54
55
  const pct = usage?.percent;
55
56
  const messages = event.messages;
57
+ // 3WF-1 TriggerGuard: guarantee a staged recall block on this event even
58
+ // when session_start never fired. One-shot per session; a block already
59
+ // staged by session_start takes precedence (no-op). Run BEFORE building the
60
+ // tail factory so a freshly staged block is composed into THIS event's view.
61
+ // Best-effort — a failure falls through to the pre-sprint path.
62
+ try {
63
+ runTriggerGuard(runtime, config, ctx);
64
+ } catch {
65
+ /* non-fatal */
66
+ }
56
67
  // S53: helper to inject the staged recall/memory block as a user-role
57
68
  // tail message at any view-return point. Returns undefined when nothing
58
69
  // is staged (or the flag is OFF) so the caller falls through to its
@@ -13,7 +13,6 @@ import { registerAgentHandlers } from "./agent-handlers.js";
13
13
  import { registerContextHandler } from "./context-handler.js";
14
14
  import { registerCompactHandlers } from "./compact-handlers.js";
15
15
  import { registerPerfHandler } from "./perf-handler.js";
16
- import { registerAnalyticsHandler } from "./analytics-handler.js";
17
16
  import { registerVectorCortexRender } from "../mega-context/vector-cortex.js";
18
17
 
19
18
  /**
@@ -50,6 +49,4 @@ export function registerEventHandlers(
50
49
  registerContextHandler(pi, runtime, config);
51
50
  registerCompactHandlers(pi, runtime, config);
52
51
  registerPerfHandler(pi, runtime);
53
- // PMA-2: analytics ingestion — flag-gated (OFF = no-op, no DB opened).
54
- registerAnalyticsHandler(pi, runtime, config);
55
52
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-mega-compact",
3
- "version": "0.20.82",
3
+ "version": "0.20.84",
4
4
  "description": "Layered, local, vector-backed context compressor for pi — supersede/collapse/cluster compaction with deduped inline recall.",
5
5
  "type": "module",
6
6
  "license": "BSD-3-Clause",
@@ -0,0 +1,46 @@
1
+ /**
2
+ * src/failback/types.ts — 3WF Three-Way Failback contract types (contract-first).
3
+ *
4
+ * Pure, pi-agnostic interfaces describing the staged-recall guarantee chain:
5
+ * every session should have a staged recall block even when `session_start`
6
+ * never fires. 3WF-1 (TriggerGuard) is the first consumer; 3WF-2/3/4 extend
7
+ * these shapes for the compaction ladder, the 3-source recall vote, and
8
+ * InjectionConfirm. No pi runtime imports, no store mutation — these are the
9
+ * shape contract only.
10
+ */
11
+
12
+ /** One-shot guard state for a session (observable result of a TriggerGuard run). */
13
+ export interface TriggerGuardState {
14
+ /** True once a recall/floor attempt has run for this session. */
15
+ recallRan: boolean;
16
+ /** The block staged into runtime.pendingRecallBlock (null when nothing staged). */
17
+ stagedBlock: string | null;
18
+ /** True when the provenance floor (not a recall hit) was staged. */
19
+ usedFloor: boolean;
20
+ }
21
+
22
+ /** A provenance floor string built from the newest checkpoint / session basis. */
23
+ export interface FloorBlock {
24
+ /** The model-visible floor text. */
25
+ text: string;
26
+ /** Why this floor was produced. */
27
+ basis: "lastCheckpoint" | "sessionProvenance" | "none";
28
+ }
29
+
30
+ /** Result of a single TriggerGuard evaluation (for tests + telemetry). */
31
+ export interface GuardRunResult {
32
+ /** How the block (or non-block) was produced. */
33
+ source: "already-staged" | "recall" | "floor" | "none";
34
+ /** The staged block text; null when nothing was staged. */
35
+ block: string | null;
36
+ }
37
+
38
+ /** Options the TriggerGuard needs to run a staged recall. */
39
+ export interface GuardOpts {
40
+ /** The latest user message used as the recall query; empty => 'none'. */
41
+ query: string | null;
42
+ /** Normalized session id. */
43
+ sessionId: string;
44
+ /** Max hits to recall (mirrors autoInlineK). */
45
+ limit: number;
46
+ }
@@ -1,169 +0,0 @@
1
- import { createAnalyticsStore, closeAllAnalyticsDbs, } from "../../src/store/analytics/index.js";
2
- // ── Per-stateDir store cache (mirrors mega-turn-store.ts storeFor) ─────
3
- const stores = new Map();
4
- function storeFor(stateDir) {
5
- let s = stores.get(stateDir);
6
- if (!s) {
7
- s = createAnalyticsStore({ stateDir });
8
- stores.set(stateDir, s);
9
- }
10
- return s;
11
- }
12
- /** Close all cached analytics stores (test teardown / shutdown). */
13
- export function closeAnalyticsStores() {
14
- closeAllAnalyticsDbs();
15
- stores.clear();
16
- }
17
- const pmaTimings = new WeakMap();
18
- function getPma(rt) {
19
- let s = pmaTimings.get(rt);
20
- if (!s) {
21
- s = { providerStart: 0, ttft: 0, correlationId: null };
22
- pmaTimings.set(rt, s);
23
- }
24
- return s;
25
- }
26
- /** @internal Test-only accessor — production code uses getPma() in-module. */
27
- export function __pmaTimingForTest(rt) {
28
- return getPma(rt);
29
- }
30
- function usageOf(msg) {
31
- if (msg.role === "assistant" && msg.usage)
32
- return msg.usage;
33
- return null;
34
- }
35
- // ── Helpers ───────────────────────────────────────────────────────────
36
- /** Build a QualityNote with provider/model availability. */
37
- function qualityFor(provider, model) {
38
- if (!provider || !model)
39
- return { unavailable: true, note: "provider/model not captured" };
40
- return {};
41
- }
42
- // ── Registration ──────────────────────────────────────────────────────
43
- /** Register the analytics event handlers. Flag-gated: OFF = complete no-op. */
44
- export function registerAnalyticsHandler(pi, runtime, config) {
45
- if (!config.providerModelAnalytics)
46
- return; // flag OFF — no DB, no handlers
47
- // ── turn_start: stamp the request start + emit request_started ──────
48
- pi.on("turn_start", async (event) => {
49
- try {
50
- const now = Date.now();
51
- const corrId = `req_${runtime.rt.sessionId}_${event.turnIndex}`;
52
- const pma = getPma(runtime);
53
- pma.correlationId = corrId;
54
- pma.providerStart = 0; // reset; stamped at before_provider_request
55
- pma.ttft = 0;
56
- const provider = runtime.currentModel?.provider;
57
- const model = runtime.currentModel?.modelId;
58
- const fact = {
59
- id: `${corrId}_started`,
60
- correlationId: corrId,
61
- sessionId: runtime.rt.sessionId,
62
- turnId: String(event.turnIndex),
63
- eventKind: "request_started",
64
- observedAt: now,
65
- provider,
66
- model,
67
- source: "host_adapter",
68
- quality: qualityFor(provider, model),
69
- };
70
- storeFor(runtime.currentStateDir).asWriter().appendRequestEvent(fact);
71
- }
72
- catch {
73
- /* non-fatal: analytics never breaks the agent loop */
74
- }
75
- });
76
- // ── before_provider_request: stamp provider start + emit provider_selected
77
- pi.on("before_provider_request", async () => {
78
- try {
79
- const pma = getPma(runtime);
80
- pma.providerStart = Date.now();
81
- const corrId = pma.correlationId;
82
- if (!corrId)
83
- return;
84
- const provider = runtime.currentModel?.provider;
85
- const model = runtime.currentModel?.modelId;
86
- const fact = {
87
- id: `${corrId}_selected`,
88
- correlationId: corrId,
89
- sessionId: runtime.rt.sessionId,
90
- eventKind: "provider_selected",
91
- observedAt: pma.providerStart,
92
- provider,
93
- model,
94
- source: "host_adapter",
95
- quality: qualityFor(provider, model),
96
- };
97
- storeFor(runtime.currentStateDir).asWriter().appendRequestEvent(fact);
98
- }
99
- catch {
100
- /* non-fatal */
101
- }
102
- });
103
- // ── message_update: capture TTFT on first text token ────────────────
104
- // Net-new seam — no existing handler registers message_update.
105
- // Proven available in PMA-0 (agent-loop.js:201-227).
106
- pi.on("message_update", async (event) => {
107
- try {
108
- const pma = getPma(runtime);
109
- if (pma.ttft > 0)
110
- return; // already captured this turn
111
- if (pma.providerStart <= 0)
112
- return; // no provider start stamp
113
- const chunkType = event
114
- .assistantMessageEvent?.type;
115
- if (chunkType !== "text_start" && chunkType !== "text_delta")
116
- return;
117
- pma.ttft = Date.now() - pma.providerStart;
118
- }
119
- catch {
120
- /* non-fatal */
121
- }
122
- });
123
- // ── turn_end: emit terminal fact with tokens, latency, ttft, status ─
124
- pi.on("turn_end", async (event) => {
125
- try {
126
- const pma = getPma(runtime);
127
- const corrId = pma.correlationId;
128
- if (!corrId)
129
- return;
130
- const now = Date.now();
131
- const durationMs = runtime.perfTurnStart > 0 ? now - runtime.perfTurnStart : undefined;
132
- const u = usageOf(event.message);
133
- const stopReason = event.message.stopReason;
134
- const provider = runtime.currentModel?.provider;
135
- const model = runtime.currentModel?.modelId;
136
- const isFailed = stopReason === "error" || stopReason === "aborted";
137
- const eventKind = isFailed ? "request_failed" : "request_completed";
138
- const fact = {
139
- id: `${corrId}_${eventKind}`,
140
- correlationId: corrId,
141
- sessionId: runtime.rt.sessionId,
142
- turnId: String(event.turnIndex),
143
- eventKind,
144
- observedAt: now,
145
- provider,
146
- model,
147
- status: stopReason ?? undefined,
148
- inputTokens: u?.input,
149
- outputTokens: u?.output,
150
- cacheReadTokens: u?.cacheRead,
151
- cacheWriteTokens: u?.cacheWrite,
152
- durationMs,
153
- ttftMs: pma.ttft > 0 ? pma.ttft : undefined,
154
- source: "host_adapter",
155
- quality: {
156
- ...qualityFor(provider, model),
157
- ...(pma.ttft > 0 ? {} : { note: "TTFT not captured (no first-token event)" }),
158
- },
159
- };
160
- storeFor(runtime.currentStateDir).asWriter().appendRequestEvent(fact);
161
- // Reset per-turn state.
162
- pma.correlationId = null;
163
- pma.ttft = 0;
164
- }
165
- catch {
166
- /* non-fatal */
167
- }
168
- });
169
- }
@@ -1,114 +0,0 @@
1
- /**
2
- * connection.ts — PMA-1 connection manager for analytics.db.
3
- *
4
- * Own module-level connection cache, deliberately SEPARATE from the main
5
- * memory store (openStore) and the turns store (openTurnDb) so an analytics-DB
6
- * failure can never touch the authoritative memory DB or the turns DB (PMA §3.1).
7
- *
8
- * Mirrors src/store/turns/connection.ts. PREVENT-PI-004: node:sqlite in-process,
9
- * no network. PREVENT-002: parameterized path binding.
10
- */
11
- import { existsSync, mkdirSync, statSync } from "node:fs";
12
- import { join } from "node:path";
13
- import { DatabaseSync as Db } from "node:sqlite";
14
- import { initAnalyticsSchema } from "./schema.js";
15
- export const ANALYTICS_DB_FILE = "analytics.db";
16
- /** Resolve the analytics DB path, honoring the env override. */
17
- export function analyticsDbPath(stateDir) {
18
- const env = process.env.MEGACOMPACT_ANALYTICS_DB_PATH;
19
- if (env && env.trim() !== "")
20
- return env.trim();
21
- return join(stateDir, ANALYTICS_DB_FILE);
22
- }
23
- // Module-private cache — separate from openStore's cache and turns' cache.
24
- const cache = new Map();
25
- /** File size helper (0 for in-memory). */
26
- function dbFileSize(dbPath) {
27
- try {
28
- return statSync(dbPath).size;
29
- }
30
- catch {
31
- return 0;
32
- }
33
- }
34
- /**
35
- * Open (or reuse from cache) the analytics.db for `stateDir`.
36
- * Sets PRAGMAs, initializes the schema, and caches the connection.
37
- * In-memory mode (`:memory:`) is supported for tests.
38
- */
39
- export function openAnalyticsDb(stateDir, options) {
40
- const inMemory = options?.inMemory === true;
41
- const resolvedPath = inMemory ? ":memory:" : (options?.dbPath ?? analyticsDbPath(stateDir));
42
- const cacheKey = inMemory ? `:memory:${stateDir}` : resolvedPath;
43
- const cached = cache.get(cacheKey);
44
- if (cached) {
45
- try {
46
- cached.exec("SELECT 1");
47
- return cached;
48
- }
49
- catch {
50
- // Stale handle — evict and re-open.
51
- cache.delete(cacheKey);
52
- }
53
- }
54
- // Ensure the state dir exists for file-backed mode.
55
- if (!inMemory && !existsSync(stateDir)) {
56
- mkdirSync(stateDir, { recursive: true });
57
- }
58
- const db = inMemory ? new Db(":memory:") : new Db(resolvedPath);
59
- db.exec("PRAGMA busy_timeout = 5000");
60
- db.exec("PRAGMA journal_mode = WAL");
61
- db.exec("PRAGMA foreign_keys = ON");
62
- initAnalyticsSchema(db);
63
- cache.set(cacheKey, db);
64
- return db;
65
- }
66
- /** Close and evict the analytics DB for `stateDir` (idempotent). */
67
- export function closeAnalyticsDb(stateDir, options) {
68
- const inMemory = options?.inMemory === true;
69
- const resolvedPath = inMemory ? ":memory:" : (options?.dbPath ?? analyticsDbPath(stateDir));
70
- const cacheKey = inMemory ? `:memory:${stateDir}` : resolvedPath;
71
- const db = cache.get(cacheKey);
72
- if (!db)
73
- return;
74
- try {
75
- db.close();
76
- }
77
- catch {
78
- /* already closed */
79
- }
80
- cache.delete(cacheKey);
81
- }
82
- /** Close ALL cached analytics DBs (test teardown / graceful shutdown). */
83
- export function closeAllAnalyticsDbs() {
84
- for (const db of cache.values()) {
85
- try {
86
- db.close();
87
- }
88
- catch {
89
- /* already closed */
90
- }
91
- }
92
- cache.clear();
93
- }
94
- /** Report the file size of analytics.db for a state dir (0 if absent/in-memory). */
95
- export function analyticsDbSize(stateDir) {
96
- return dbFileSize(analyticsDbPath(stateDir));
97
- }
98
- /**
99
- * SAVEPOINT-based transaction wrapper (nests safely under an outer transaction).
100
- * Uses a distinct savepoint name from the turns store (`turns_tx`) and the main
101
- * store (`mc_tx`) to avoid collisions.
102
- */
103
- export function withTx(db, fn) {
104
- db.exec("SAVEPOINT analytics_tx");
105
- try {
106
- fn();
107
- db.exec("RELEASE analytics_tx");
108
- }
109
- catch (e) {
110
- db.exec("ROLLBACK TO analytics_tx");
111
- db.exec("RELEASE analytics_tx");
112
- throw e;
113
- }
114
- }
@@ -1,39 +0,0 @@
1
- let counter = 0;
2
- /** Create a valid RequestEventFact with sensible defaults + overrides. */
3
- export function makeRequestEvent(overrides = {}) {
4
- return {
5
- id: `evt_${++counter}`,
6
- correlationId: "corr_test",
7
- sessionId: "sess_test",
8
- eventKind: "request_started",
9
- observedAt: Date.now(),
10
- provider: "anthropic",
11
- model: "claude-sonnet-4",
12
- source: "test",
13
- quality: {},
14
- ...overrides,
15
- };
16
- }
17
- /** Create a valid MeasurementFact. */
18
- export function makeMeasurement(overrides = {}) {
19
- return {
20
- observedAt: Date.now(),
21
- sampleKind: "cache_hit_pct",
22
- value: 72.5,
23
- unit: "percent",
24
- source: "test",
25
- quality: {},
26
- ...overrides,
27
- };
28
- }
29
- /** Create a valid IdentityObservation. */
30
- export function makeIdentity(overrides = {}) {
31
- return {
32
- observedAt: Date.now(),
33
- provider: "anthropic",
34
- model: "claude-sonnet-4",
35
- source: "test",
36
- metadata: { contextWindow: 200000 },
37
- ...overrides,
38
- };
39
- }
@@ -1,25 +0,0 @@
1
- /**
2
- * index.ts — Barrel for src/store/analytics/ (PMA-1).
3
- *
4
- * Hosts import types + the factory. SQL schemas + internals stay private.
5
- *
6
- * PREVENT-PI-004: no network. PREVENT-002: parameterized SQL only.
7
- */
8
- export { SqliteAnalyticsStore } from "./sqlite-store.js";
9
- export { InMemoryAnalyticsStore } from "./memory-store.js";
10
- export { openAnalyticsDb, closeAnalyticsDb, closeAllAnalyticsDbs, analyticsDbPath, analyticsDbSize, ANALYTICS_DB_FILE, withTx, } from "./connection.js";
11
- export { initAnalyticsSchema, getAnalyticsSchemaVersion } from "./schema.js";
12
- import { SqliteAnalyticsStore } from "./sqlite-store.js";
13
- import { InMemoryAnalyticsStore } from "./memory-store.js";
14
- /**
15
- * Factory: create an AnalyticsStore from options.
16
- *
17
- * - Default: SqliteAnalyticsStore backed by analytics.db in stateDir
18
- * - inMemory: InMemoryAnalyticsStore (no file I/O)
19
- */
20
- export function createAnalyticsStore(options) {
21
- if (options.inMemory) {
22
- return new InMemoryAnalyticsStore(options);
23
- }
24
- return new SqliteAnalyticsStore(options);
25
- }