pi-mega-compact 0.13.0 → 0.13.2

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 (212) hide show
  1. package/dist/extensions/dashboard-server/api-contracts/embedder-health.js +11 -0
  2. package/dist/extensions/dashboard-server/api-contracts/endpoints/registry.js +225 -0
  3. package/dist/extensions/dashboard-server/api-contracts/endpoints/types.js +1 -0
  4. package/dist/extensions/dashboard-server/api-contracts/endpoints.js +5 -224
  5. package/dist/extensions/dashboard-server/api-contracts/raptor.js +8 -0
  6. package/dist/extensions/dashboard-server/api-contracts.test/_helpers.js +32 -0
  7. package/dist/extensions/dashboard-server/routes-cache.test/_helpers.js +48 -0
  8. package/dist/extensions/dashboard-server/routes-embedder-health.js +67 -0
  9. package/dist/extensions/dashboard-server/routes-raptor.js +88 -0
  10. package/dist/extensions/dashboard-server/routes-setup.js +1 -1
  11. package/dist/extensions/dashboard-server/routes.js +2 -0
  12. package/dist/extensions/dashboard-server/server.js +5 -1
  13. package/dist/extensions/mega-cache-replay.test/_helpers.js +175 -0
  14. package/dist/extensions/mega-commands/dataCommands.js +151 -0
  15. package/dist/extensions/mega-commands/helpers.js +61 -0
  16. package/dist/extensions/mega-commands/historyCommands.js +97 -0
  17. package/dist/extensions/mega-commands/setupCommand.js +200 -0
  18. package/dist/extensions/mega-commands.js +14 -499
  19. package/dist/extensions/mega-compact-s38.test/_helpers.js +194 -0
  20. package/dist/extensions/mega-compact.js +12 -2
  21. package/dist/extensions/mega-compact.test/_helpers.js +225 -0
  22. package/dist/extensions/mega-events/agent-handlers/agentEndHandler.js +211 -0
  23. package/dist/extensions/mega-events/agent-handlers/turnEndHandler/cacheStripe.js +49 -0
  24. package/dist/extensions/mega-events/agent-handlers/turnEndHandler/contextHealth.js +33 -0
  25. package/dist/extensions/mega-events/agent-handlers/turnEndHandler/errorRetry.js +387 -0
  26. package/dist/extensions/mega-events/agent-handlers/turnEndHandler/event.js +7 -0
  27. package/dist/extensions/mega-events/agent-handlers/turnEndHandler/gameScoring.js +55 -0
  28. package/dist/extensions/mega-events/agent-handlers/turnEndHandler/lengthStop.js +12 -0
  29. package/dist/extensions/mega-events/agent-handlers/turnEndHandler/memoryReview.js +22 -0
  30. package/dist/extensions/mega-events/agent-handlers/turnEndHandler/recordTurnRow.js +38 -0
  31. package/dist/extensions/mega-events/agent-handlers/turnEndHandler.js +36 -0
  32. package/dist/extensions/mega-events/agent-handlers.js +4 -799
  33. package/dist/extensions/mega-events/context-handler/afterCompact.js +163 -0
  34. package/dist/extensions/mega-events/context-handler/messageText.js +12 -0
  35. package/dist/extensions/mega-events/context-handler/tailResult.js +46 -0
  36. package/dist/extensions/mega-events/context-handler.js +11 -198
  37. package/dist/extensions/mega-events/separated-prompt.test/_helpers.js +25 -0
  38. package/dist/src/config.js +2 -0
  39. package/dist/src/dedup-engine.test/_helpers.js +55 -0
  40. package/dist/src/e2e.test/_helpers.js +52 -0
  41. package/dist/src/embedder.js +1 -0
  42. package/dist/src/httpEmbedder.js +6 -0
  43. package/dist/src/hyde.js +146 -0
  44. package/dist/src/importance.test/_helpers.js +5 -0
  45. package/dist/src/memoryGraph/sources/checkpoints.js +78 -0
  46. package/dist/src/memoryGraph/sources/edges.js +17 -0
  47. package/dist/src/memoryGraph/sources/flags.js +22 -0
  48. package/dist/src/memoryGraph/sources/helpers.js +14 -0
  49. package/dist/src/memoryGraph/sources/memories.js +88 -0
  50. package/dist/src/memoryGraph/sources/raptor.js +26 -0
  51. package/dist/src/memoryGraph/sources/turns.js +176 -0
  52. package/dist/src/memoryGraph/sources.js +15 -441
  53. package/dist/src/memoryGraph-gates.test/_helpers.js +120 -0
  54. package/dist/src/ratio.bench.test/_helpers.js +198 -0
  55. package/dist/src/recall/async.js +112 -0
  56. package/dist/src/recall/format.js +149 -0
  57. package/dist/src/recall/memory.js +62 -0
  58. package/dist/src/recall/reformulate.js +82 -0
  59. package/dist/src/recall/sync.js +169 -0
  60. package/dist/src/recall/types.js +1 -0
  61. package/dist/src/recall.js +13 -530
  62. package/dist/src/store/sqlite/schema/core.js +232 -0
  63. package/dist/src/store/sqlite/schema/game.js +65 -0
  64. package/dist/src/store/sqlite/schema/plan-v2.js +107 -0
  65. package/dist/src/store/sqlite/schema/turns.js +59 -0
  66. package/dist/src/store/sqlite/schema.js +8 -441
  67. package/dist/src/store/turns/contract-compliance.test/_helpers.js +21 -0
  68. package/dist/src/store/turns/sqlite-store/admin.js +171 -0
  69. package/dist/src/store/turns/sqlite-store/ctx.js +1 -0
  70. package/dist/src/store/turns/sqlite-store/read.js +111 -0
  71. package/dist/src/store/turns/sqlite-store/rows.js +42 -0
  72. package/dist/src/store/turns/sqlite-store/write.js +102 -0
  73. package/dist/src/store/turns/sqlite-store.js +31 -367
  74. package/dist/src/vectorStore/class.js +356 -0
  75. package/dist/src/vectorStore/hash.js +10 -0
  76. package/dist/src/vectorStore/types.js +2 -0
  77. package/dist/src/vectorStore.js +8 -359
  78. package/dist/src/vectorStore.test/_helpers.js +17 -0
  79. package/extensions/dashboard-client/dist/assets/{CacheTab-BZNnKhh5.js → CacheTab-DPRHjYZ0.js} +2 -2
  80. package/extensions/dashboard-client/dist/assets/{CacheTab-BZNnKhh5.js.map → CacheTab-DPRHjYZ0.js.map} +1 -1
  81. package/extensions/dashboard-client/dist/assets/{EventsTab-CzF5L42U.js → EventsTab-CgwYq-Qf.js} +2 -2
  82. package/extensions/dashboard-client/dist/assets/{EventsTab-CzF5L42U.js.map → EventsTab-CgwYq-Qf.js.map} +1 -1
  83. package/extensions/dashboard-client/dist/assets/{HealthTab-BLpidMrX.js → HealthTab-BgOCjVzR.js} +2 -2
  84. package/extensions/dashboard-client/dist/assets/{HealthTab-BLpidMrX.js.map → HealthTab-BgOCjVzR.js.map} +1 -1
  85. package/extensions/dashboard-client/dist/assets/MaintenanceTab-Yw1yFpsy.js +2 -0
  86. package/extensions/dashboard-client/dist/assets/MaintenanceTab-Yw1yFpsy.js.map +1 -0
  87. package/extensions/dashboard-client/dist/assets/MemoryMapTab-yHUX7Yzi.js +2 -0
  88. package/extensions/dashboard-client/dist/assets/MemoryMapTab-yHUX7Yzi.js.map +1 -0
  89. package/extensions/dashboard-client/dist/assets/{MetricsTab-B0PCysqs.js → MetricsTab-CcH9fP8P.js} +2 -2
  90. package/extensions/dashboard-client/dist/assets/{MetricsTab-B0PCysqs.js.map → MetricsTab-CcH9fP8P.js.map} +1 -1
  91. package/extensions/dashboard-client/dist/assets/{OverviewTab-DsOhqFu4.js → OverviewTab-BOs8ZNev.js} +2 -2
  92. package/extensions/dashboard-client/dist/assets/{OverviewTab-DsOhqFu4.js.map → OverviewTab-BOs8ZNev.js.map} +1 -1
  93. package/extensions/dashboard-client/dist/assets/{ReposTab-B3YKYuOf.js → ReposTab-B-MfSPvg.js} +2 -2
  94. package/extensions/dashboard-client/dist/assets/{ReposTab-B3YKYuOf.js.map → ReposTab-B-MfSPvg.js.map} +1 -1
  95. package/extensions/dashboard-client/dist/assets/{SessionsTab-B96FWcrC.js → SessionsTab-BmeN649Q.js} +2 -2
  96. package/extensions/dashboard-client/dist/assets/{SessionsTab-B96FWcrC.js.map → SessionsTab-BmeN649Q.js.map} +1 -1
  97. package/extensions/dashboard-client/dist/assets/SetupTab-nOknoph5.js +2 -0
  98. package/extensions/dashboard-client/dist/assets/SetupTab-nOknoph5.js.map +1 -0
  99. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-BfkUVTvA.js → TimeSavedCard-DnPg0L0z.js} +2 -2
  100. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-BfkUVTvA.js.map → TimeSavedCard-DnPg0L0z.js.map} +1 -1
  101. package/extensions/dashboard-client/dist/assets/{TopicsTab-DLRsZmXJ.js → TopicsTab-BODHOg_b.js} +2 -2
  102. package/extensions/dashboard-client/dist/assets/{TopicsTab-DLRsZmXJ.js.map → TopicsTab-BODHOg_b.js.map} +1 -1
  103. package/extensions/dashboard-client/dist/assets/{TurnsTab-DKGU2Bf6.js → TurnsTab-BK0I7hKr.js} +2 -2
  104. package/extensions/dashboard-client/dist/assets/{TurnsTab-DKGU2Bf6.js.map → TurnsTab-BK0I7hKr.js.map} +1 -1
  105. package/extensions/dashboard-client/dist/assets/{index-D-lPLRWB.js → index-mOlljTFI.js} +10 -10
  106. package/extensions/dashboard-client/dist/assets/index-mOlljTFI.js.map +1 -0
  107. package/extensions/dashboard-client/dist/assets/{useSSE-ClbbcgIp.js → useSSE-BYOtv1-3.js} +2 -2
  108. package/extensions/dashboard-client/dist/assets/{useSSE-ClbbcgIp.js.map → useSSE-BYOtv1-3.js.map} +1 -1
  109. package/extensions/dashboard-client/dist/index.html +1 -1
  110. package/extensions/dashboard-client/src/App.tsx +0 -4
  111. package/extensions/dashboard-client/src/api/client.ts +16 -0
  112. package/extensions/dashboard-client/src/tabs/MaintenanceTab/ActionsCard.tsx +178 -0
  113. package/extensions/dashboard-client/src/tabs/MaintenanceTab/DbStatsCard.tsx +140 -0
  114. package/extensions/dashboard-client/src/tabs/MaintenanceTab/DebugBundleCard.tsx +109 -0
  115. package/extensions/dashboard-client/src/tabs/MaintenanceTab/HealthMitigationCard.tsx +98 -0
  116. package/extensions/dashboard-client/src/tabs/MaintenanceTab/SchemaHealthCard.tsx +108 -0
  117. package/extensions/dashboard-client/src/tabs/MaintenanceTab.tsx +12 -598
  118. package/extensions/dashboard-client/src/tabs/MemoryMapTab/MemoryMapView.tsx +476 -0
  119. package/extensions/dashboard-client/src/tabs/MemoryMapTab/RaptorTreeView.tsx +163 -0
  120. package/extensions/dashboard-client/src/tabs/MemoryMapTab.tsx +56 -466
  121. package/extensions/dashboard-client/src/tabs/SetupTab/EmbedderHealthCard.tsx +109 -0
  122. package/extensions/dashboard-client/src/tabs/SetupTab/EmbedderSetup.tsx +468 -0
  123. package/extensions/dashboard-client/src/tabs/SetupTab.tsx +23 -484
  124. package/extensions/dashboard-server/api-contracts/embedder-health.ts +32 -0
  125. package/extensions/dashboard-server/api-contracts/endpoints/registry.ts +360 -0
  126. package/extensions/dashboard-server/api-contracts/endpoints/types.ts +350 -0
  127. package/extensions/dashboard-server/api-contracts/endpoints.ts +27 -648
  128. package/extensions/dashboard-server/api-contracts/index.ts +6 -0
  129. package/extensions/dashboard-server/api-contracts/raptor.ts +49 -0
  130. package/extensions/dashboard-server/api-contracts.test/_helpers.ts +47 -0
  131. package/extensions/dashboard-server/routes-cache.test/_helpers.ts +94 -0
  132. package/extensions/dashboard-server/routes-embedder-health.ts +77 -0
  133. package/extensions/dashboard-server/routes-raptor.ts +128 -0
  134. package/extensions/dashboard-server/routes-setup.ts +1 -1
  135. package/extensions/dashboard-server/routes.ts +2 -0
  136. package/extensions/dashboard-server/server.ts +4 -0
  137. package/extensions/mega-cache-replay.test/_helpers.ts +188 -0
  138. package/extensions/mega-commands/dataCommands.ts +169 -0
  139. package/extensions/mega-commands/helpers.ts +62 -0
  140. package/extensions/mega-commands/historyCommands.ts +117 -0
  141. package/extensions/mega-commands/setupCommand.ts +235 -0
  142. package/extensions/mega-commands.ts +25 -538
  143. package/extensions/mega-compact-s38.test/_helpers.ts +201 -0
  144. package/extensions/mega-compact.test/_helpers.ts +237 -0
  145. package/extensions/mega-compact.ts +12 -2
  146. package/extensions/mega-events/agent-handlers/agentEndHandler.ts +238 -0
  147. package/extensions/mega-events/agent-handlers/turnEndHandler/cacheStripe.ts +74 -0
  148. package/extensions/mega-events/agent-handlers/turnEndHandler/contextHealth.ts +50 -0
  149. package/extensions/mega-events/agent-handlers/turnEndHandler/errorRetry.ts +421 -0
  150. package/extensions/mega-events/agent-handlers/turnEndHandler/event.ts +16 -0
  151. package/extensions/mega-events/agent-handlers/turnEndHandler/gameScoring.ts +73 -0
  152. package/extensions/mega-events/agent-handlers/turnEndHandler/lengthStop.ts +29 -0
  153. package/extensions/mega-events/agent-handlers/turnEndHandler/memoryReview.ts +42 -0
  154. package/extensions/mega-events/agent-handlers/turnEndHandler/recordTurnRow.ts +61 -0
  155. package/extensions/mega-events/agent-handlers/turnEndHandler.ts +64 -0
  156. package/extensions/mega-events/agent-handlers.ts +15 -861
  157. package/extensions/mega-events/context-handler/afterCompact.ts +213 -0
  158. package/extensions/mega-events/context-handler/messageText.ts +20 -0
  159. package/extensions/mega-events/context-handler/tailResult.ts +63 -0
  160. package/extensions/mega-events/context-handler.ts +17 -235
  161. package/extensions/mega-events/separated-prompt.test/_helpers.ts +35 -0
  162. package/package.json +1 -1
  163. package/src/config.ts +4 -0
  164. package/src/dedup-engine.test/_helpers.ts +79 -0
  165. package/src/e2e.test/_helpers.ts +59 -0
  166. package/src/embedder.ts +4 -0
  167. package/src/httpEmbedder.ts +7 -0
  168. package/src/hyde.ts +162 -0
  169. package/src/importance.test/_helpers.ts +5 -0
  170. package/src/memoryGraph/sources/checkpoints.ts +99 -0
  171. package/src/memoryGraph/sources/edges.ts +23 -0
  172. package/src/memoryGraph/sources/flags.ts +26 -0
  173. package/src/memoryGraph/sources/helpers.ts +13 -0
  174. package/src/memoryGraph/sources/memories.ts +108 -0
  175. package/src/memoryGraph/sources/raptor.ts +37 -0
  176. package/src/memoryGraph/sources/turns.ts +226 -0
  177. package/src/memoryGraph/sources.ts +19 -544
  178. package/src/memoryGraph-gates.test/_helpers.ts +164 -0
  179. package/src/ratio.bench.test/_helpers.ts +268 -0
  180. package/src/recall/async.ts +142 -0
  181. package/src/recall/format.ts +173 -0
  182. package/src/recall/memory.ts +90 -0
  183. package/src/recall/reformulate.ts +111 -0
  184. package/src/recall/sync.ts +196 -0
  185. package/src/recall/types.ts +63 -0
  186. package/src/recall.ts +25 -712
  187. package/src/store/sqlite/schema/core.ts +233 -0
  188. package/src/store/sqlite/schema/game.ts +66 -0
  189. package/src/store/sqlite/schema/plan-v2.ts +108 -0
  190. package/src/store/sqlite/schema/turns.ts +60 -0
  191. package/src/store/sqlite/schema.ts +13 -441
  192. package/src/store/turns/contract-compliance.test/_helpers.ts +38 -0
  193. package/src/store/turns/sqlite-store/admin.ts +249 -0
  194. package/src/store/turns/sqlite-store/ctx.ts +14 -0
  195. package/src/store/turns/sqlite-store/read.ts +174 -0
  196. package/src/store/turns/sqlite-store/rows.ts +55 -0
  197. package/src/store/turns/sqlite-store/write.ts +193 -0
  198. package/src/store/turns/sqlite-store.ts +33 -514
  199. package/src/vectorStore/class.ts +433 -0
  200. package/src/vectorStore/hash.ts +11 -0
  201. package/src/vectorStore/types.ts +58 -0
  202. package/src/vectorStore.test/_helpers.ts +20 -0
  203. package/src/vectorStore.ts +13 -489
  204. package/extensions/dashboard-client/dist/assets/ConfigTab-DG0WkuuG.js +0 -2
  205. package/extensions/dashboard-client/dist/assets/ConfigTab-DG0WkuuG.js.map +0 -1
  206. package/extensions/dashboard-client/dist/assets/MaintenanceTab-BQF7W7xX.js +0 -2
  207. package/extensions/dashboard-client/dist/assets/MaintenanceTab-BQF7W7xX.js.map +0 -1
  208. package/extensions/dashboard-client/dist/assets/MemoryMapTab-CPtHLv0A.js +0 -2
  209. package/extensions/dashboard-client/dist/assets/MemoryMapTab-CPtHLv0A.js.map +0 -1
  210. package/extensions/dashboard-client/dist/assets/SetupTab-hbzQ_NZv.js +0 -2
  211. package/extensions/dashboard-client/dist/assets/SetupTab-hbzQ_NZv.js.map +0 -1
  212. package/extensions/dashboard-client/dist/assets/index-D-lPLRWB.js.map +0 -1
@@ -0,0 +1,201 @@
1
+ /**
2
+ * Shared harness for the mega-compact-s38 split test files.
3
+ *
4
+ * Extracted from the top of mega-compact-s38.test.ts: the mock-pi harness that
5
+ * loads the compiled extension, the eventTypes/eventPayloads log readers, the
6
+ * s38TurnEnd helpers, and the classifyError fixtures.
7
+ *
8
+ * Every split file imports this module, which also registers an `after()` hook
9
+ * that force-exits the test subprocess. Each harness() creates a MegaRuntime
10
+ * with an fs.watch game-state watcher that is never disposed (no
11
+ * session_shutdown in tests); those handles keep the event loop alive
12
+ * indefinitely, so without a force-exit `node --test` (without
13
+ * --test-force-exit) would hang. The hook drains stdout/stderr then defers the
14
+ * exit — a bare process.exit() discards unflushed pipe buffers (observed
15
+ * 2026-07-30: piped test results silently vanished).
16
+ *
17
+ * Relative paths are one directory deeper than the original file, hence
18
+ * `../mega-compact.js` / `../mega-events.js`.
19
+ */
20
+ import { mkdtempSync, rmSync, existsSync } from "node:fs";
21
+ import { tmpdir } from "node:os";
22
+ import { join } from "node:path";
23
+ import { createRequire } from "node:module";
24
+ import { after } from "node:test";
25
+ import assert from "node:assert/strict";
26
+ import { closeVectorIndex } from "../../src/store/vectorIndex.js";
27
+ import type { AgentMessage } from "@earendil-works/pi-agent-core";
28
+
29
+ export const require = createRequire(import.meta.url);
30
+ const baseTmp = mkdtempSync(join(tmpdir(), "mc-s38-"));
31
+ export const baseTmpDir = baseTmp;
32
+ export { closeVectorIndex };
33
+ // Isolate the machine-wide repo index so test runs never pollute the real one.
34
+ process.env.MEGACOMPACT_INDEX_DIR = join(baseTmp, "index");
35
+ let counter = 0;
36
+
37
+ after(async () => {
38
+ // PGlite WASM close can hang; race with a timeout to prevent 40-min hangs.
39
+ try {
40
+ await Promise.race([closeVectorIndex(), new Promise((r) => setTimeout(r, 3000))]);
41
+ } catch { /* ignore */ }
42
+ if (existsSync(baseTmp)) rmSync(baseTmp, { recursive: true, force: true });
43
+ // Drain stdout/stderr, then defer the exit so the final test report flushes.
44
+ await new Promise((r) => process.stdout.write("", r));
45
+ await new Promise((r) => process.stderr.write("", r));
46
+ await new Promise((r) => setTimeout(r, 1500));
47
+ process.exit(0);
48
+ });
49
+
50
+ /** Read events.log and return an array of event type strings. */
51
+ export function eventTypes(stateDir: string): string[] {
52
+ const { readFileSync: rf, existsSync: ex } =
53
+ require("node:fs") as typeof import("node:fs");
54
+ const { join: j } = require("node:path") as typeof import("node:path");
55
+ const logPath = j(stateDir, "events.log");
56
+ if (!ex(logPath)) return [];
57
+ const content = rf(logPath, "utf-8").trim();
58
+ if (content.length === 0) return [];
59
+ return content
60
+ .split("\n")
61
+ .map((line) => { try { return JSON.parse(line).type; } catch { return undefined; } })
62
+ .filter((t): t is string => typeof t === "string");
63
+ }
64
+
65
+ /** R11: read events.log and return full JSON payloads for a given type. */
66
+ export function eventPayloads(stateDir: string, type: string): Record<string, unknown>[] {
67
+ const { readFileSync: rf, existsSync: ex } =
68
+ require("node:fs") as typeof import("node:fs");
69
+ const { join: j } = require("node:path") as typeof import("node:path");
70
+ const logPath = j(stateDir, "events.log");
71
+ if (!ex(logPath)) return [];
72
+ const content = rf(logPath, "utf-8").trim();
73
+ if (content.length === 0) return [];
74
+ return content
75
+ .split("\n")
76
+ .map((line) => { try { return JSON.parse(line); } catch { return undefined; } })
77
+ .filter((p): p is Record<string, unknown> => typeof p === "object" && p !== null && p.type === type);
78
+ }
79
+
80
+ /** Build a mock pi + ctx and load the extension into them. */
81
+ export function harness(opts: { keepTier?: boolean; keepThreshold?: boolean } = {}) {
82
+ const stateDir = join(baseTmp, `run-${counter++}`);
83
+ process.env.MEGACOMPACT_STATE_DIR = stateDir;
84
+ process.env.MEGACOMPACT_DEBUG = "true";
85
+ if (!opts.keepThreshold) process.env.MEGACOMPACT_THRESHOLD_TOKENS = "50";
86
+ if (!opts.keepTier) delete process.env.MEGACOMPACT_TIER;
87
+ process.env.MEGACOMPACT_FAST_GATE_PCT = "1";
88
+
89
+ const handlers: Record<string, Function[]> = {};
90
+ const appended: any[] = [];
91
+ const sendUserMessages: string[] = [];
92
+ const compactCalls: any[] = [];
93
+
94
+ const session: AgentMessage[] = [
95
+ { role: "user", content: "read src/vec.ts and understand the index", timestamp: 0 } as unknown as AgentMessage,
96
+ { role: "assistant", content: [{ type: "toolCall", name: "Read", id: "c1", arguments: {} }], api: "anthropic-messages", provider: "anthropic", model: "m", usage: { inputTokens: 1, outputTokens: 1, cacheReadTokens: 0, cacheWriteTokens: 0 }, stopReason: "tool_use", timestamp: 0 } as unknown as AgentMessage,
97
+ { role: "user", content: "edit src/vec.ts to add a cosine helper", timestamp: 0 } as unknown as AgentMessage,
98
+ { role: "assistant", content: [{ type: "toolCall", name: "Edit", id: "c1", arguments: {} }], api: "anthropic-messages", provider: "anthropic", model: "m", usage: { inputTokens: 1, outputTokens: 1, cacheReadTokens: 0, cacheWriteTokens: 0 }, stopReason: "tool_use", timestamp: 0 } as unknown as AgentMessage,
99
+ ];
100
+ const toEntry = (m: AgentMessage, i: number): any => ({ type: "message", id: `e${i}`, parentId: null, timestamp: String(i), message: m });
101
+ const sessionManager = { getSessionId: () => "sess_ext_001", getEntries: () => session.map(toEntry), getBranch: () => session.map(toEntry) };
102
+
103
+ function makeCtx(over: Partial<any> = {}) {
104
+ return {
105
+ ui: { setStatus: () => {}, notify: () => {}, select: () => {}, confirm: async () => true, input: async () => "", setWidget: () => {} },
106
+ mode: "tui" as any, hasUI: true, cwd: stateDir, sessionManager,
107
+ modelRegistry: {} as any, model: undefined,
108
+ isIdle: () => true, isProjectTrusted: () => true,
109
+ signal: undefined, abort: () => {}, hasPendingMessages: () => false, shutdown: () => {},
110
+ getContextUsage: () => ({ tokens: 200000, contextWindow: 200000, percent: 100 }),
111
+ compact: (opts?: any) => {
112
+ compactCalls.push(opts);
113
+ const _sbc = handlers["session_before_compact"];
114
+ if (_sbc && _sbc.length) return _sbc[0]({ type: "session_before_compact", reason: "threshold", willRetry: false, signal: undefined, preparation: { firstKeptEntryId: "e2", messagesToSummarize: session.slice(0, 2), tokensBefore: 500 } } as any, makeCtx());
115
+ return undefined;
116
+ },
117
+ getSystemPrompt: () => "system base", ...over,
118
+ } as any;
119
+ }
120
+ const pi = {
121
+ on: (ev: string, h: Function) => { if (!handlers[ev]) handlers[ev] = []; handlers[ev].push(h); },
122
+ registerCommand: () => {}, registerTool: () => {}, registerShortcut: () => {},
123
+ registerFlag: () => {}, getFlag: () => undefined,
124
+ registerMessageRenderer: () => {}, registerEntryRenderer: () => {},
125
+ sendMessage: () => {}, sendUserMessage: (m: string) => { sendUserMessages.push(m); },
126
+ appendEntry: (t: string, d: any) => appended.push({ t, d }),
127
+ setSessionName: () => {}, getSessionName: () => undefined, setLabel: () => {},
128
+ exec: async () => ({ stdout: "", stderr: "", code: 0 }),
129
+ getActiveTools: () => [], getAllTools: () => {}, setActiveTools: () => {},
130
+ getCommands: () => [], setModel: async () => false,
131
+ getThinkingLevel: () => "off" as any, setThinkingLevel: () => {},
132
+ } as any;
133
+ const mod = require("../mega-compact.js") as { default: (p: any) => void };
134
+ mod.default(pi);
135
+ return {
136
+ stateDir, handlers, appended, compactCalls, sendUserMessages,
137
+ fire: async (ev: string, event: any, ctx: any) => { let r: any; for (const h of handlers[ev] || []) r = await h(event, ctx); return r; },
138
+ ctx: makeCtx, session,
139
+ };
140
+ }
141
+
142
+ export type Harness = ReturnType<typeof harness>;
143
+
144
+ export const { classifyError: classifyErrorFn, classifyErrorDetailed: classifyErrorDetailedFn, extractErrorSignature: extractErrorSignatureFn } =
145
+ require("../mega-events.js") as { classifyError: typeof import("../mega-events.js").classifyError; classifyErrorDetailed: typeof import("../mega-events.js").classifyErrorDetailed; extractErrorSignature: typeof import("../mega-events.js").extractErrorSignature };
146
+
147
+ /** S38 helper: fire a turn_end with a given stopReason + optional text, using a
148
+ * low-pressure ctx so the durable-trim branch (ctx.compact) does NOT fire. */
149
+ export async function s38TurnEnd(h: Harness, stopReason: string | undefined, text?: string) {
150
+ const lowCtx = h.ctx({ isIdle: () => true, hasPendingMessages: () => false, getContextUsage: () => ({ tokens: 100, contextWindow: 200000, percent: 0 }) });
151
+ const message: any = { role: "assistant" };
152
+ if (stopReason !== undefined) message.stopReason = stopReason;
153
+ if (text) message.content = text;
154
+ await h.fire("turn_end", { type: "turn_end", turnIndex: 1, message }, lowCtx);
155
+ }
156
+
157
+ /** R3 helper: like s38TurnEnd but attaches a `usage` object so the classifier's
158
+ * 0-token poisoned-context signal is exercised (usage PRESENT with 0 tokens).
159
+ * Pass `tokens` > 0 to simulate a turn that reached the model. */
160
+ export async function s38TurnEndUsage(h: Harness, stopReason: string | undefined, text: string | undefined, tokens: number) {
161
+ const lowCtx = h.ctx({ isIdle: () => true, hasPendingMessages: () => false, getContextUsage: () => ({ tokens: 100, contextWindow: 200000, percent: 0 }) });
162
+ const message: any = { role: "assistant", usage: { inputTokens: tokens, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 } };
163
+ if (stopReason !== undefined) message.stopReason = stopReason;
164
+ if (text) message.content = text;
165
+ await h.fire("turn_end", { type: "turn_end", turnIndex: 1, message }, lowCtx);
166
+ }
167
+
168
+ /** R7 helper: fire the same transient error text `count` times with turn_start
169
+ * + elapsed backoff between turns so each turn's nudge can fire. */
170
+ export async function r7RepeatTurns(h: Harness, text: string, count: number) {
171
+ for (let i = 0; i < count; i++) {
172
+ await s38TurnEnd(h, "error", text);
173
+ await h.fire("turn_start", { type: "turn_start", turnIndex: i + 2 }, h.ctx());
174
+ await new Promise((r) => setTimeout(r, 3));
175
+ }
176
+ }
177
+
178
+ /** R7 helper: shared assertions for "stays transient" — no poisoned upgrade,
179
+ * no /clear advise, and the transient retry path actually ran. */
180
+ export function assertStaysTransient(h: Harness, label: string) {
181
+ assert.ok(
182
+ !eventTypes(h.stateDir).includes("poisoned_context"),
183
+ `${label}: no poisoned_context event (network errors never upgrade)`,
184
+ );
185
+ assert.ok(
186
+ !h.sendUserMessages.some((m) => m.includes("/clear")),
187
+ `${label}: no /clear advise message`,
188
+ );
189
+ assert.ok(
190
+ eventTypes(h.stateDir).includes("error_retry"),
191
+ `${label}: transient retry path ran (error_retry event)`,
192
+ );
193
+ }
194
+
195
+ /** The exact error bodies from the 2026-07-30 incident (GLM router flapping). */
196
+ export const R8_NO_HEALTHY_TARGET =
197
+ '{"message":"No healthy target selected for alias \'hf:zai-org/GLM-4.7\'","type":"api_error"}';
198
+ export const R8_SOCKET_CLOSED =
199
+ "All targets failed: modal/zai-org/GLM-5.1-FP8. Last error: The socket connection was closed unexpectedly. For more information, pass `verbose: true` in the second argument to fetch()"; // guardrails-allow PREVENT-PI-004: verbatim 2026-07-30 incident error text (string fixture, not a network call)
200
+ export const R8_TOO_MANY_CONCURRENT =
201
+ 'All targets failed: modal/zai-org/GLM-5.1-FP8. Last error: {"error": "Too many concurrent requests for this model"}';
@@ -0,0 +1,237 @@
1
+ /**
2
+ * Shared harness for the mega-compact split test files.
3
+ *
4
+ * Extracted from the top of mega-compact.test.ts: the mock-pi harness that loads
5
+ * the compiled extension and lets tests fire event/command handlers against it.
6
+ * Each split test file imports `harness()` (and `require`/`baseTmp` where
7
+ * needed) so the per-test isolated state-dir environment is preserved.
8
+ *
9
+ * Note: relative paths are one directory deeper than the original file, hence
10
+ * `../mega-compact.js` for the extension entry and `../../src/...` for src.
11
+ */
12
+ import { mkdtempSync } from "node:fs";
13
+ import { tmpdir } from "node:os";
14
+ import { join } from "node:path";
15
+ import { createRequire } from "node:module";
16
+ import { closeVectorIndex } from "../../src/store/vectorIndex.js";
17
+ import type { AgentMessage } from "@earendil-works/pi-agent-core";
18
+
19
+ export const require = createRequire(import.meta.url);
20
+ const baseTmp = mkdtempSync(join(tmpdir(), "mc-ext-"));
21
+ // Isolate the machine-wide repo index so test runs (which call bindRepo ->
22
+ // upsertRepoRegistry) never pollute the developer's real ~/.mega-compact-index.
23
+ process.env.MEGACOMPACT_INDEX_DIR = join(baseTmp, "index");
24
+ // This end-to-end extension test drives the compact/recall flow through the
25
+ // synchronous node:sqlite store (the authoritative path). The redundant PGlite
26
+ // HNSW cross-repo index is additive and exercised in its own dedicated test
27
+ // (store/vectorIndex.test.ts); starting the WASM worker here only risks the
28
+ // teardown stall that hangs node --test on some machines. Disable it for this
29
+ // suite — the extension behavior under test is identical with it off.
30
+ process.env.MEGACOMPACT_PGLITE_DISABLED = "1";
31
+ export const baseTmpDir = baseTmp;
32
+ export { closeVectorIndex };
33
+ let counter = 0;
34
+
35
+ /** Build a mock pi + ctx and load the extension into them. */
36
+ export function harness(opts: { keepTier?: boolean; keepThreshold?: boolean } = {}) {
37
+ const stateDir = join(baseTmp, `run-${counter++}`);
38
+ process.env.MEGACOMPACT_STATE_DIR = stateDir;
39
+ process.env.MEGACOMPACT_DEBUG = "true";
40
+ // Low threshold so the auto-trigger gate trips on our small mock context.
41
+ // Tier tests opt out (keepTier/keepThreshold) so they can drive the real
42
+ // tier resolution instead of the forced 50-token threshold.
43
+ if (!opts.keepThreshold) process.env.MEGACOMPACT_THRESHOLD_TOKENS = "50";
44
+ if (!opts.keepTier) delete process.env.MEGACOMPACT_TIER;
45
+ process.env.MEGACOMPACT_FAST_GATE_PCT = "1";
46
+
47
+ const handlers: Record<string, Function[]> = {};
48
+ const commands: Record<
49
+ string,
50
+ { handler: (a: string, c: any) => Promise<void> }
51
+ > = {};
52
+ const appended: any[] = [];
53
+ let statusKey: string | undefined;
54
+ let statusText: string | undefined;
55
+ const notifies: string[] = [];
56
+ const compactCalls: any[] = [];
57
+ const sendUserMessages: string[] = [];
58
+
59
+ // Minimal AgentMessage factory for the session we project into the extension.
60
+ function msg(role: string, text: string, toolName?: string): AgentMessage {
61
+ if (role === "assistant" && toolName) {
62
+ return {
63
+ role: "assistant",
64
+ content: [
65
+ { type: "toolCall", name: toolName, id: "c1", arguments: {} },
66
+ ],
67
+ api: "anthropic-messages",
68
+ provider: "anthropic",
69
+ model: "m",
70
+ usage: {
71
+ inputTokens: 1,
72
+ outputTokens: 1,
73
+ cacheReadTokens: 0,
74
+ cacheWriteTokens: 0,
75
+ },
76
+ stopReason: "tool_use",
77
+ timestamp: 0,
78
+ } as unknown as AgentMessage;
79
+ }
80
+ if (role === "toolResult" && toolName) {
81
+ return {
82
+ role: "toolResult",
83
+ toolCallId: "c1",
84
+ toolName,
85
+ content: [{ type: "text", text }],
86
+ isError: false,
87
+ timestamp: 0,
88
+ } as unknown as AgentMessage;
89
+ }
90
+ return {
91
+ role: "user",
92
+ content: text,
93
+ timestamp: 0,
94
+ } as unknown as AgentMessage;
95
+ }
96
+
97
+ const session: AgentMessage[] = [
98
+ msg("user", "read src/vec.ts and understand the index"),
99
+ msg("assistant", "ok", "Read"),
100
+ msg("user", "edit src/vec.ts to add a cosine helper"),
101
+ msg("assistant", "ok", "Edit"),
102
+ msg("user", "now fix the dedupe bug in store.ts"),
103
+ msg("assistant", "ok", "Edit"),
104
+ msg("user", "actually we should add recall sorting too"),
105
+ msg("assistant", "ok", "Edit"),
106
+ ];
107
+
108
+ // Mirror the REAL SessionManager: getEntries() returns SessionEntry objects,
109
+ // which the extension projects to messages via the SDK's
110
+ // sessionEntryToContextMessages(entry). The harness must use the same shape
111
+ // (type:"message" with a .message) or recentUserQuery() silently queries "".
112
+ const toEntry = (m: AgentMessage, i: number): any => ({
113
+ type: "message",
114
+ id: `e${i}`,
115
+ parentId: null,
116
+ timestamp: String(i),
117
+ message: m,
118
+ });
119
+ const sessionManager = {
120
+ getSessionId: () => "sess_ext_001",
121
+ getEntries: () => session.map(toEntry),
122
+ // Faithful mock: getBranch() returns the current branch's entries, which
123
+ // piCompactWouldNoop() reads to predict whether ctx.compact() would no-op.
124
+ getBranch: () => session.map(toEntry),
125
+ };
126
+
127
+ function makeCtx(over: Partial<any> = {}) {
128
+ return {
129
+ ui: {
130
+ setStatus: (k: string, t: string | undefined) => {
131
+ statusKey = k;
132
+ statusText = t;
133
+ },
134
+ notify: (s: string) => notifies.push(s),
135
+ select: () => {},
136
+ confirm: async () => true,
137
+ input: async () => "",
138
+ setWidget: () => {},
139
+ },
140
+ mode: "tui" as any,
141
+ hasUI: true,
142
+ cwd: stateDir,
143
+ sessionManager,
144
+ modelRegistry: {} as any,
145
+ model: undefined,
146
+ isIdle: () => true,
147
+ isProjectTrusted: () => true,
148
+ signal: undefined,
149
+ abort: () => {},
150
+ hasPendingMessages: () => false,
151
+ shutdown: () => {},
152
+ getContextUsage: () => ({
153
+ tokens: 200000,
154
+ contextWindow: 200000,
155
+ percent: 100,
156
+ }),
157
+ // Faithful mock: ctx.compact() starts pi's flow, which fires the
158
+ // session_before_compact handler (where WE supply the durable trim).
159
+ compact: (opts?: any) => {
160
+ compactCalls.push(opts);
161
+ const _sbc = handlers["session_before_compact"]; if (_sbc && _sbc.length) {
162
+ return _sbc[0](
163
+ {
164
+ type: "session_before_compact",
165
+ reason: "threshold",
166
+ willRetry: false,
167
+ signal: undefined,
168
+ // pi computed the cut honoring anchor floor + tool-pair (PREVENT-PI-002);
169
+ // our handler reuses it as firstKeptEntryId.
170
+ preparation: {
171
+ firstKeptEntryId: "e2",
172
+ messagesToSummarize: session.slice(0, 2),
173
+ tokensBefore: 500,
174
+ },
175
+ } as any,
176
+ makeCtx(),
177
+ );
178
+ }
179
+ return undefined;
180
+ },
181
+ getSystemPrompt: () => "system base",
182
+ ...over,
183
+ } as any;
184
+ }
185
+
186
+ const pi = {
187
+ on: (ev: string, h: Function) => {
188
+ if (!handlers[ev]) handlers[ev] = [];
189
+ handlers[ev].push(h);
190
+ },
191
+ registerCommand: (name: string, opts: any) => {
192
+ commands[name] = opts;
193
+ },
194
+ registerTool: () => {},
195
+ registerShortcut: () => {},
196
+ registerFlag: () => {},
197
+ getFlag: () => undefined,
198
+ registerMessageRenderer: () => {},
199
+ registerEntryRenderer: () => {},
200
+ sendMessage: (_m: any) => {},
201
+ sendUserMessage: (m: string) => {
202
+ sendUserMessages.push(m);
203
+ },
204
+ appendEntry: (t: string, d: any) => appended.push({ t, d }),
205
+ setSessionName: () => {},
206
+ getSessionName: () => undefined,
207
+ setLabel: () => {},
208
+ exec: async () => ({ stdout: "", stderr: "", code: 0 }),
209
+ getActiveTools: () => [],
210
+ getAllTools: () => [],
211
+ setActiveTools: () => {},
212
+ getCommands: () => [],
213
+ setModel: async () => false,
214
+ getThinkingLevel: () => "off" as any,
215
+ setThinkingLevel: () => {},
216
+ } as any;
217
+
218
+ // Import the compiled extension (same dist dir as this test's parent).
219
+ const mod = require("../mega-compact.js") as { default: (p: any) => void };
220
+ mod.default(pi);
221
+
222
+ return {
223
+ stateDir,
224
+ handlers,
225
+ commands,
226
+ appended,
227
+ get status() {
228
+ return { statusKey, statusText };
229
+ },
230
+ notifies,
231
+ compactCalls,
232
+ sendUserMessages,
233
+ fire: async (ev: string, event: any, ctx: any) => { let r: any; for (const h of handlers[ev] || []) r = await h(event, ctx); return r; },
234
+ ctx: makeCtx,
235
+ session,
236
+ };
237
+ }
@@ -29,7 +29,7 @@
29
29
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
30
30
  import { closeVectorIndex } from "../src/store/vectorIndex.js";
31
31
  import { closeMemoryIndex } from "../src/store/memoryIndex.js";
32
- import { loadConfig } from "./mega-config.js";
32
+ import { loadConfig, repoStateDir } from "./mega-config.js";
33
33
  import { loadMegaEnv } from "./mega-runtime/env-loader.js";
34
34
  import { MegaRuntime } from "./mega-runtime.js";
35
35
  import { registerEventHandlers } from "./mega-events.js";
@@ -45,7 +45,17 @@ export default function (pi: ExtensionAPI) {
45
45
  // Load .mega-compact.env (written by /mega-setup + dashboard Setup tab) BEFORE
46
46
  // config resolution so the wizard's choices take effect. Shell env wins over
47
47
  // the file (see env-loader.ts). Non-fatal: missing/malformed file is a no-op.
48
- loadMegaEnv(loadConfig().stateDir);
48
+ //
49
+ // The dashboard Setup tab + /mega-setup write the file to the PER-REPO state
50
+ // dir (<repo>/.pi/mega-compact, resolved via repoStateDir), NOT the global
51
+ // default (~/.pi/agent/extensions/pi-mega-compact). Loading from the global
52
+ // dir was the root cause of the embedder-never-activates bug: the file sat in
53
+ // the per-repo dir, the loader read the empty global dir, and the configured
54
+ // embedder silently never loaded after restart. Load per-repo first (where
55
+ // the wizard writes), then the global dir as a legacy fallback.
56
+ const globalStateDir = loadConfig().stateDir;
57
+ loadMegaEnv(repoStateDir(process.cwd(), globalStateDir));
58
+ loadMegaEnv(globalStateDir);
49
59
  const config = loadConfig();
50
60
  // S38.9: preflight env validation — check for obviously invalid values at startup.
51
61
  // Non-fatal: log warnings and fall back to defaults.