pi-mega-compact 0.6.7 → 0.7.0

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.
@@ -30,422 +30,743 @@ let counter = 0;
30
30
 
31
31
  /** Build a mock pi + ctx and load the extension into them. */
32
32
  function harness(opts: { keepTier?: boolean; keepThreshold?: boolean } = {}) {
33
- const stateDir = join(baseTmp, `run-${counter++}`);
34
- process.env.MEGACOMPACT_STATE_DIR = stateDir;
35
- process.env.MEGACOMPACT_DEBUG = "true";
36
- // Low threshold so the auto-trigger gate trips on our small mock context.
37
- // Tier tests opt out (keepTier/keepThreshold) so they can drive the real
38
- // tier resolution instead of the forced 50-token threshold.
39
- if (!opts.keepThreshold) process.env.MEGACOMPACT_THRESHOLD_TOKENS = "50";
40
- if (!opts.keepTier) delete process.env.MEGACOMPACT_TIER;
41
- process.env.MEGACOMPACT_FAST_GATE_PCT = "1";
42
-
43
- const handlers: Record<string, Function> = {};
44
- const commands: Record<string, { handler: (a: string, c: any) => Promise<void> }> = {};
45
- const appended: any[] = [];
46
- let statusKey: string | undefined;
47
- let statusText: string | undefined;
48
- const notifies: string[] = [];
49
- const compactCalls: any[] = [];
50
-
51
- // Minimal AgentMessage factory for the session we project into the extension.
52
- function msg(role: string, text: string, toolName?: string): AgentMessage {
53
- if (role === "assistant" && toolName) {
54
- return { role: "assistant", content: [{ type: "toolCall", name: toolName, 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;
55
- }
56
- if (role === "toolResult" && toolName) {
57
- return { role: "toolResult", toolCallId: "c1", toolName, content: [{ type: "text", text }], isError: false, timestamp: 0 } as unknown as AgentMessage;
58
- }
59
- return { role: "user", content: text, timestamp: 0 } as unknown as AgentMessage;
60
- }
61
-
62
- const session: AgentMessage[] = [
63
- msg("user", "read src/vec.ts and understand the index"),
64
- msg("assistant", "ok", "Read"),
65
- msg("user", "edit src/vec.ts to add a cosine helper"),
66
- msg("assistant", "ok", "Edit"),
67
- msg("user", "now fix the dedupe bug in store.ts"),
68
- msg("assistant", "ok", "Edit"),
69
- msg("user", "actually we should add recall sorting too"),
70
- msg("assistant", "ok", "Edit"),
71
- ];
72
-
73
- // Mirror the REAL SessionManager: getEntries() returns SessionEntry objects,
74
- // which the extension projects to messages via the SDK's
75
- // sessionEntryToContextMessages(entry). The harness must use the same shape
76
- // (type:"message" with a .message) or recentUserQuery() silently queries "".
77
- const toEntry = (m: AgentMessage, i: number): any => ({
78
- type: "message",
79
- id: `e${i}`,
80
- parentId: null,
81
- timestamp: String(i),
82
- message: m,
83
- });
84
- const sessionManager = {
85
- getSessionId: () => "sess_ext_001",
86
- getEntries: () => session.map(toEntry),
87
- // Faithful mock: getBranch() returns the current branch's entries, which
88
- // piCompactWouldNoop() reads to predict whether ctx.compact() would no-op.
89
- getBranch: () => session.map(toEntry),
90
- };
91
-
92
- function makeCtx(over: Partial<any> = {}) {
93
- return {
94
- ui: {
95
- setStatus: (k: string, t: string | undefined) => { statusKey = k; statusText = t; },
96
- notify: (s: string) => notifies.push(s),
97
- select: () => {},
98
- confirm: async () => true,
99
- input: async () => "",
100
- setWidget: () => {},
101
- },
102
- mode: "tui" as any,
103
- hasUI: true,
104
- cwd: stateDir,
105
- sessionManager,
106
- modelRegistry: {} as any,
107
- model: undefined,
108
- isIdle: () => true,
109
- isProjectTrusted: () => true,
110
- signal: undefined,
111
- abort: () => {},
112
- hasPendingMessages: () => false,
113
- shutdown: () => {},
114
- getContextUsage: () => ({ tokens: 200000, contextWindow: 200000, percent: 100 }),
115
- // Faithful mock: ctx.compact() starts pi's flow, which fires the
116
- // session_before_compact handler (where WE supply the durable trim).
117
- compact: (opts?: any) => {
118
- compactCalls.push(opts);
119
- if (handlers["session_before_compact"]) {
120
- return handlers["session_before_compact"](
121
- {
122
- type: "session_before_compact",
123
- reason: "threshold",
124
- willRetry: false,
125
- signal: undefined,
126
- // pi computed the cut honoring anchor floor + tool-pair (PREVENT-PI-002);
127
- // our handler reuses it as firstKeptEntryId.
128
- preparation: {
129
- firstKeptEntryId: "e2",
130
- messagesToSummarize: session.slice(0, 2),
131
- tokensBefore: 500,
132
- },
133
- } as any,
134
- makeCtx(),
135
- );
136
- }
137
- return undefined;
138
- },
139
- getSystemPrompt: () => "system base",
140
- ...over,
141
- } as any;
142
- }
143
-
144
- const pi = {
145
- on: (ev: string, h: Function) => { handlers[ev] = h; },
146
- registerCommand: (name: string, opts: any) => { commands[name] = opts; },
147
- registerTool: () => {},
148
- registerShortcut: () => {},
149
- registerFlag: () => {},
150
- getFlag: () => undefined,
151
- registerMessageRenderer: () => {},
152
- registerEntryRenderer: () => {},
153
- sendMessage: (_m: any) => {},
154
- sendUserMessage: () => {},
155
- appendEntry: (t: string, d: any) => appended.push({ t, d }),
156
- setSessionName: () => {},
157
- getSessionName: () => undefined,
158
- setLabel: () => {},
159
- exec: async () => ({ stdout: "", stderr: "", code: 0 }),
160
- getActiveTools: () => [],
161
- getAllTools: () => [],
162
- setActiveTools: () => {},
163
- getCommands: () => [],
164
- setModel: async () => false,
165
- getThinkingLevel: () => "off" as any,
166
- setThinkingLevel: () => {},
167
- } as any;
168
-
169
- // Import the compiled extension (same dist/extensions dir as this test).
170
- const mod = require("./mega-compact.js") as { default: (p: any) => void };
171
- mod.default(pi);
172
-
173
- return {
174
- stateDir, handlers, commands, appended, get status() { return { statusKey, statusText }; }, notifies, compactCalls,
175
- fire: (ev: string, event: any, ctx: any) => handlers[ev](event, ctx),
176
- ctx: makeCtx,
177
- session,
178
- };
33
+ const stateDir = join(baseTmp, `run-${counter++}`);
34
+ process.env.MEGACOMPACT_STATE_DIR = stateDir;
35
+ process.env.MEGACOMPACT_DEBUG = "true";
36
+ // Low threshold so the auto-trigger gate trips on our small mock context.
37
+ // Tier tests opt out (keepTier/keepThreshold) so they can drive the real
38
+ // tier resolution instead of the forced 50-token threshold.
39
+ if (!opts.keepThreshold) process.env.MEGACOMPACT_THRESHOLD_TOKENS = "50";
40
+ if (!opts.keepTier) delete process.env.MEGACOMPACT_TIER;
41
+ process.env.MEGACOMPACT_FAST_GATE_PCT = "1";
42
+
43
+ const handlers: Record<string, Function> = {};
44
+ const commands: Record<
45
+ string,
46
+ { handler: (a: string, c: any) => Promise<void> }
47
+ > = {};
48
+ const appended: any[] = [];
49
+ let statusKey: string | undefined;
50
+ let statusText: string | undefined;
51
+ const notifies: string[] = [];
52
+ const compactCalls: any[] = [];
53
+
54
+ // Minimal AgentMessage factory for the session we project into the extension.
55
+ function msg(role: string, text: string, toolName?: string): AgentMessage {
56
+ if (role === "assistant" && toolName) {
57
+ return {
58
+ role: "assistant",
59
+ content: [
60
+ { type: "toolCall", name: toolName, id: "c1", arguments: {} },
61
+ ],
62
+ api: "anthropic-messages",
63
+ provider: "anthropic",
64
+ model: "m",
65
+ usage: {
66
+ inputTokens: 1,
67
+ outputTokens: 1,
68
+ cacheReadTokens: 0,
69
+ cacheWriteTokens: 0,
70
+ },
71
+ stopReason: "tool_use",
72
+ timestamp: 0,
73
+ } as unknown as AgentMessage;
74
+ }
75
+ if (role === "toolResult" && toolName) {
76
+ return {
77
+ role: "toolResult",
78
+ toolCallId: "c1",
79
+ toolName,
80
+ content: [{ type: "text", text }],
81
+ isError: false,
82
+ timestamp: 0,
83
+ } as unknown as AgentMessage;
84
+ }
85
+ return {
86
+ role: "user",
87
+ content: text,
88
+ timestamp: 0,
89
+ } as unknown as AgentMessage;
90
+ }
91
+
92
+ const session: AgentMessage[] = [
93
+ msg("user", "read src/vec.ts and understand the index"),
94
+ msg("assistant", "ok", "Read"),
95
+ msg("user", "edit src/vec.ts to add a cosine helper"),
96
+ msg("assistant", "ok", "Edit"),
97
+ msg("user", "now fix the dedupe bug in store.ts"),
98
+ msg("assistant", "ok", "Edit"),
99
+ msg("user", "actually we should add recall sorting too"),
100
+ msg("assistant", "ok", "Edit"),
101
+ ];
102
+
103
+ // Mirror the REAL SessionManager: getEntries() returns SessionEntry objects,
104
+ // which the extension projects to messages via the SDK's
105
+ // sessionEntryToContextMessages(entry). The harness must use the same shape
106
+ // (type:"message" with a .message) or recentUserQuery() silently queries "".
107
+ const toEntry = (m: AgentMessage, i: number): any => ({
108
+ type: "message",
109
+ id: `e${i}`,
110
+ parentId: null,
111
+ timestamp: String(i),
112
+ message: m,
113
+ });
114
+ const sessionManager = {
115
+ getSessionId: () => "sess_ext_001",
116
+ getEntries: () => session.map(toEntry),
117
+ // Faithful mock: getBranch() returns the current branch's entries, which
118
+ // piCompactWouldNoop() reads to predict whether ctx.compact() would no-op.
119
+ getBranch: () => session.map(toEntry),
120
+ };
121
+
122
+ function makeCtx(over: Partial<any> = {}) {
123
+ return {
124
+ ui: {
125
+ setStatus: (k: string, t: string | undefined) => {
126
+ statusKey = k;
127
+ statusText = t;
128
+ },
129
+ notify: (s: string) => notifies.push(s),
130
+ select: () => {},
131
+ confirm: async () => true,
132
+ input: async () => "",
133
+ setWidget: () => {},
134
+ },
135
+ mode: "tui" as any,
136
+ hasUI: true,
137
+ cwd: stateDir,
138
+ sessionManager,
139
+ modelRegistry: {} as any,
140
+ model: undefined,
141
+ isIdle: () => true,
142
+ isProjectTrusted: () => true,
143
+ signal: undefined,
144
+ abort: () => {},
145
+ hasPendingMessages: () => false,
146
+ shutdown: () => {},
147
+ getContextUsage: () => ({
148
+ tokens: 200000,
149
+ contextWindow: 200000,
150
+ percent: 100,
151
+ }),
152
+ // Faithful mock: ctx.compact() starts pi's flow, which fires the
153
+ // session_before_compact handler (where WE supply the durable trim).
154
+ compact: (opts?: any) => {
155
+ compactCalls.push(opts);
156
+ if (handlers["session_before_compact"]) {
157
+ return handlers["session_before_compact"](
158
+ {
159
+ type: "session_before_compact",
160
+ reason: "threshold",
161
+ willRetry: false,
162
+ signal: undefined,
163
+ // pi computed the cut honoring anchor floor + tool-pair (PREVENT-PI-002);
164
+ // our handler reuses it as firstKeptEntryId.
165
+ preparation: {
166
+ firstKeptEntryId: "e2",
167
+ messagesToSummarize: session.slice(0, 2),
168
+ tokensBefore: 500,
169
+ },
170
+ } as any,
171
+ makeCtx(),
172
+ );
173
+ }
174
+ return undefined;
175
+ },
176
+ getSystemPrompt: () => "system base",
177
+ ...over,
178
+ } as any;
179
+ }
180
+
181
+ const pi = {
182
+ on: (ev: string, h: Function) => {
183
+ handlers[ev] = h;
184
+ },
185
+ registerCommand: (name: string, opts: any) => {
186
+ commands[name] = opts;
187
+ },
188
+ registerTool: () => {},
189
+ registerShortcut: () => {},
190
+ registerFlag: () => {},
191
+ getFlag: () => undefined,
192
+ registerMessageRenderer: () => {},
193
+ registerEntryRenderer: () => {},
194
+ sendMessage: (_m: any) => {},
195
+ sendUserMessage: () => {},
196
+ appendEntry: (t: string, d: any) => appended.push({ t, d }),
197
+ setSessionName: () => {},
198
+ getSessionName: () => undefined,
199
+ setLabel: () => {},
200
+ exec: async () => ({ stdout: "", stderr: "", code: 0 }),
201
+ getActiveTools: () => [],
202
+ getAllTools: () => [],
203
+ setActiveTools: () => {},
204
+ getCommands: () => [],
205
+ setModel: async () => false,
206
+ getThinkingLevel: () => "off" as any,
207
+ setThinkingLevel: () => {},
208
+ } as any;
209
+
210
+ // Import the compiled extension (same dist/extensions dir as this test).
211
+ const mod = require("./mega-compact.js") as { default: (p: any) => void };
212
+ mod.default(pi);
213
+
214
+ return {
215
+ stateDir,
216
+ handlers,
217
+ commands,
218
+ appended,
219
+ get status() {
220
+ return { statusKey, statusText };
221
+ },
222
+ notifies,
223
+ compactCalls,
224
+ fire: (ev: string, event: any, ctx: any) => handlers[ev](event, ctx),
225
+ ctx: makeCtx,
226
+ session,
227
+ };
179
228
  }
180
229
 
181
230
  test("auto-trigger (legacy): past threshold persists a chkpt and starts a durable trim via ctx.compact", async () => {
182
- const h = harness();
183
- const messages = h.session;
184
- // The mock session is tiny (~100 tokens). piCompactWouldNoop() would skip
185
- // ctx.compact() for a transcript under pi's keepRecentTokens budget — so
186
- // lower the floor to 0 to simulate a transcript large enough that pi WOULD
187
- // compact (the positive path this test exercises).
188
- // S16: this is the LEGACY path — the default no longer calls ctx.compact()
189
- // (it returns a live-trimmed view instead). Set the legacy flag to exercise
190
- // the v0.4.28 ctx.compact durable-trim flow this test asserts.
191
- process.env.MEGACOMPACT_DURABLE_TRIM_FLOOR = "0";
192
- process.env.MEGACOMPACT_LEGACY_DURABLE_TRIM = "true";
193
- try {
194
- const ctx = h.ctx({ getContextUsage: () => ({ tokens: 200000, contextWindow: 200000, percent: 100 }) });
195
- const res = await h.fire("context", { type: "context", messages }, ctx);
196
- // L1->L4 ran: a checkpoint was persisted to the SQLite store + a marker entry written.
197
- const { listCheckpoints } = await import("../src/store/sqlite.js");
198
- assert.ok(listCheckpoints("sess_ext_001", h.stateDir).length > 0, "checkpoint persisted to local vector db");
199
- assert.equal(h.appended.some((a) => a.t === "mega-compact-marker"), true, "marker sentinel appended");
200
- // The legacy context handler triggers pi's compaction flow (ctx.compact),
201
- // which calls our session_before_compact handler to supply the DURABLE trim.
202
- assert.equal(res, undefined, "legacy context handler returns nothing (no local drop)");
203
- assert.equal(h.compactCalls.length, 1, "ctx.compact() called to start durable trim (legacy path)");
204
- // The durable trim was supplied (summary + firstKeptEntryId from pi's prep).
205
- assert.ok(h.compactCalls[0] !== undefined, "compaction flow executed");
206
- } finally {
207
- delete process.env.MEGACOMPACT_DURABLE_TRIM_FLOOR;
208
- delete process.env.MEGACOMPACT_LEGACY_DURABLE_TRIM;
209
- }
231
+ const h = harness();
232
+ const messages = h.session;
233
+ // The mock session is tiny (~100 tokens). piCompactWouldNoop() would skip
234
+ // ctx.compact() for a transcript under pi's keepRecentTokens budget — so
235
+ // lower the floor to 0 to simulate a transcript large enough that pi WOULD
236
+ // compact (the positive path this test exercises).
237
+ // S16: this is the LEGACY path — the default no longer calls ctx.compact()
238
+ // (it returns a live-trimmed view instead). Set the legacy flag to exercise
239
+ // the v0.4.28 ctx.compact durable-trim flow this test asserts.
240
+ process.env.MEGACOMPACT_DURABLE_TRIM_FLOOR = "0";
241
+ process.env.MEGACOMPACT_LEGACY_DURABLE_TRIM = "true";
242
+ try {
243
+ const ctx = h.ctx({
244
+ getContextUsage: () => ({
245
+ tokens: 200000,
246
+ contextWindow: 200000,
247
+ percent: 100,
248
+ }),
249
+ });
250
+ const res = await h.fire("context", { type: "context", messages }, ctx);
251
+ // L1->L4 ran: a checkpoint was persisted to the SQLite store + a marker entry written.
252
+ const { listCheckpoints } = await import("../src/store/sqlite.js");
253
+ assert.ok(
254
+ listCheckpoints("sess_ext_001", h.stateDir).length > 0,
255
+ "checkpoint persisted to local vector db",
256
+ );
257
+ assert.equal(
258
+ h.appended.some((a) => a.t === "mega-compact-marker"),
259
+ true,
260
+ "marker sentinel appended",
261
+ );
262
+ // The legacy context handler triggers pi's compaction flow (ctx.compact),
263
+ // which calls our session_before_compact handler to supply the DURABLE trim.
264
+ assert.equal(
265
+ res,
266
+ undefined,
267
+ "legacy context handler returns nothing (no local drop)",
268
+ );
269
+ assert.equal(
270
+ h.compactCalls.length,
271
+ 1,
272
+ "ctx.compact() called to start durable trim (legacy path)",
273
+ );
274
+ // The durable trim was supplied (summary + firstKeptEntryId from pi's prep).
275
+ assert.ok(h.compactCalls[0] !== undefined, "compaction flow executed");
276
+ } finally {
277
+ delete process.env.MEGACOMPACT_DURABLE_TRIM_FLOOR;
278
+ delete process.env.MEGACOMPACT_LEGACY_DURABLE_TRIM;
279
+ }
210
280
  });
211
281
 
212
282
  test("auto-trigger: skips ctx.compact() when pi would no-op (session too small, legacy path)", async () => {
213
- const h = harness();
214
- const messages = h.session;
215
- // Default floor (20000): the tiny mock transcript is below pi's
216
- // keepRecentTokens budget, so piCompactWouldNoop() must skip ctx.compact()
217
- // rather than surface pi's "Nothing to compact (session too small)" throw.
218
- // S16: exercised under the legacy flag (the default path never calls ctx.compact).
219
- delete process.env.MEGACOMPACT_DURABLE_TRIM_FLOOR;
220
- process.env.MEGACOMPACT_LEGACY_DURABLE_TRIM = "true";
221
- try {
222
- const ctx = h.ctx({ getContextUsage: () => ({ tokens: 200000, contextWindow: 200000, percent: 100 }) });
223
- const res = await h.fire("context", { type: "context", messages }, ctx);
224
- assert.equal(res, undefined, "legacy context handler returns nothing (no local drop)");
225
- assert.equal(h.compactCalls.length, 0, "ctx.compact() NOT called — pi would no-op");
226
- // Our recall checkpoint still persisted (Path A) — the durable trim is the
227
- // only thing skipped; recall is independent of it.
228
- const { listCheckpoints } = await import("../src/store/sqlite.js");
229
- assert.ok(listCheckpoints("sess_ext_001", h.stateDir).length > 0, "recall checkpoint still persisted");
230
- assert.equal(h.appended.some((a) => a.t === "mega-compact-marker"), true, "marker sentinel still appended");
231
- } finally {
232
- delete process.env.MEGACOMPACT_LEGACY_DURABLE_TRIM;
233
- }
283
+ const h = harness();
284
+ const messages = h.session;
285
+ // Default floor (20000): the tiny mock transcript is below pi's
286
+ // keepRecentTokens budget, so piCompactWouldNoop() must skip ctx.compact()
287
+ // rather than surface pi's "Nothing to compact (session too small)" throw.
288
+ // S16: exercised under the legacy flag (the default path never calls ctx.compact).
289
+ delete process.env.MEGACOMPACT_DURABLE_TRIM_FLOOR;
290
+ process.env.MEGACOMPACT_LEGACY_DURABLE_TRIM = "true";
291
+ try {
292
+ const ctx = h.ctx({
293
+ getContextUsage: () => ({
294
+ tokens: 200000,
295
+ contextWindow: 200000,
296
+ percent: 100,
297
+ }),
298
+ });
299
+ const res = await h.fire("context", { type: "context", messages }, ctx);
300
+ assert.equal(
301
+ res,
302
+ undefined,
303
+ "legacy context handler returns nothing (no local drop)",
304
+ );
305
+ assert.equal(
306
+ h.compactCalls.length,
307
+ 0,
308
+ "ctx.compact() NOT called — pi would no-op",
309
+ );
310
+ // Our recall checkpoint still persisted (Path A) — the durable trim is the
311
+ // only thing skipped; recall is independent of it.
312
+ const { listCheckpoints } = await import("../src/store/sqlite.js");
313
+ assert.ok(
314
+ listCheckpoints("sess_ext_001", h.stateDir).length > 0,
315
+ "recall checkpoint still persisted",
316
+ );
317
+ assert.equal(
318
+ h.appended.some((a) => a.t === "mega-compact-marker"),
319
+ true,
320
+ "marker sentinel still appended",
321
+ );
322
+ } finally {
323
+ delete process.env.MEGACOMPACT_LEGACY_DURABLE_TRIM;
324
+ }
234
325
  });
235
326
 
236
327
  test("auto-trigger (S16): trims the live view and does NOT call ctx.compact()", async () => {
237
- const h = harness();
238
- const messages = h.session;
239
- // S16 default: live context-event trim. No legacy flag. Lower the anchor floor
240
- // so the trimmed recent window (4 messages, 2 user) clears the anchor check
241
- // and the live trim actually fires — mirrors how the legacy test lowers the
242
- // durable floor to exercise its positive path.
243
- delete process.env.MEGACOMPACT_LEGACY_DURABLE_TRIM;
244
- delete process.env.MEGACOMPACT_DURABLE_TRIM_FLOOR;
245
- process.env.MEGACOMPACT_ANCHOR_USER_MESSAGES = "1";
246
- try {
247
- const ctx = h.ctx({ getContextUsage: () => ({ tokens: 200000, contextWindow: 200000, percent: 100 }) });
248
- const res = await h.fire("context", { type: "context", messages }, ctx);
249
- // S16: context handler returns a TRIMMED messages array (live trim), not undefined.
250
- assert.ok(res && typeof res === "object", "context handler returns a result object (live trim)");
251
- assert.ok(Array.isArray((res as any).messages), "result has a trimmed messages array");
252
- // The trimmed view starts with the compacted summary (user-role) + is shorter.
253
- assert.ok((res as any).messages.length < messages.length, "trimmed view is shorter than the full session");
254
- // S16: ctx.compact() is NEVER called (it would stop the agent).
255
- assert.equal(h.compactCalls.length, 0, "ctx.compact() NOT called compact-and-continue");
256
- // The recall checkpoint is still persisted (the durable value).
257
- const { listCheckpoints } = await import("../src/store/sqlite.js");
258
- assert.ok(listCheckpoints("sess_ext_001", h.stateDir).length > 0, "recall checkpoint persisted under live trim");
259
- } finally {
260
- delete process.env.MEGACOMPACT_ANCHOR_USER_MESSAGES;
261
- }
328
+ const h = harness();
329
+ const messages = h.session;
330
+ // S16 default: live context-event trim. No legacy flag. Lower the anchor floor
331
+ // so the trimmed recent window (4 messages, 2 user) clears the anchor check
332
+ // and the live trim actually fires — mirrors how the legacy test lowers the
333
+ // durable floor to exercise its positive path.
334
+ delete process.env.MEGACOMPACT_LEGACY_DURABLE_TRIM;
335
+ delete process.env.MEGACOMPACT_DURABLE_TRIM_FLOOR;
336
+ process.env.MEGACOMPACT_ANCHOR_USER_MESSAGES = "1";
337
+ try {
338
+ const ctx = h.ctx({
339
+ getContextUsage: () => ({
340
+ tokens: 200000,
341
+ contextWindow: 200000,
342
+ percent: 100,
343
+ }),
344
+ });
345
+ const res = await h.fire("context", { type: "context", messages }, ctx);
346
+ // S16: context handler returns a TRIMMED messages array (live trim), not undefined.
347
+ assert.ok(
348
+ res && typeof res === "object",
349
+ "context handler returns a result object (live trim)",
350
+ );
351
+ assert.ok(
352
+ Array.isArray((res as any).messages),
353
+ "result has a trimmed messages array",
354
+ );
355
+ // The trimmed view starts with the compacted summary (user-role) + is shorter.
356
+ assert.ok(
357
+ (res as any).messages.length < messages.length,
358
+ "trimmed view is shorter than the full session",
359
+ );
360
+ // S16: ctx.compact() is NEVER called (it would stop the agent).
361
+ assert.equal(
362
+ h.compactCalls.length,
363
+ 0,
364
+ "ctx.compact() NOT called — compact-and-continue",
365
+ );
366
+ // The recall checkpoint is still persisted (the durable value).
367
+ const { listCheckpoints } = await import("../src/store/sqlite.js");
368
+ assert.ok(
369
+ listCheckpoints("sess_ext_001", h.stateDir).length > 0,
370
+ "recall checkpoint persisted under live trim",
371
+ );
372
+ } finally {
373
+ delete process.env.MEGACOMPACT_ANCHOR_USER_MESSAGES;
374
+ }
262
375
  });
263
376
 
264
377
  test("auto-trigger (S16): does not trim when below the anchor floor (returns undefined, no ctx.compact)", async () => {
265
- const h = harness();
266
- // A session so short that buildLiveTrimmedView's anchor floor can't hold — the
267
- // live trim skips this call (returns undefined, the next context event retries).
268
- delete process.env.MEGACOMPACT_LEGACY_DURABLE_TRIM;
269
- delete process.env.MEGACOMPACT_DURABLE_TRIM_FLOOR;
270
- const shortSession = [h.session[0], h.session[1]]; // one user + one assistant
271
- const ctx = h.ctx({ getContextUsage: () => ({ tokens: 200000, contextWindow: 200000, percent: 100 }) });
272
- const res = await h.fire("context", { type: "context", messages: shortSession }, ctx);
273
- // Either it skipped (undefined) or trimmed safely — but it must never call ctx.compact.
274
- assert.equal(h.compactCalls.length, 0, "ctx.compact() NOT called under live trim (short session)");
275
- if (res === undefined) {
276
- // skipped path is fine
277
- assert.ok(true, "below anchor floor → no trim this call (retries next event)");
278
- }
378
+ const h = harness();
379
+ // A session so short that buildLiveTrimmedView's anchor floor can't hold — the
380
+ // live trim skips this call (returns undefined, the next context event retries).
381
+ delete process.env.MEGACOMPACT_LEGACY_DURABLE_TRIM;
382
+ delete process.env.MEGACOMPACT_DURABLE_TRIM_FLOOR;
383
+ const shortSession = [h.session[0], h.session[1]]; // one user + one assistant
384
+ const ctx = h.ctx({
385
+ getContextUsage: () => ({
386
+ tokens: 200000,
387
+ contextWindow: 200000,
388
+ percent: 100,
389
+ }),
390
+ });
391
+ const res = await h.fire(
392
+ "context",
393
+ { type: "context", messages: shortSession },
394
+ ctx,
395
+ );
396
+ // Either it skipped (undefined) or trimmed safely — but it must never call ctx.compact.
397
+ assert.equal(
398
+ h.compactCalls.length,
399
+ 0,
400
+ "ctx.compact() NOT called under live trim (short session)",
401
+ );
402
+ if (res === undefined) {
403
+ // skipped path is fine
404
+ assert.ok(
405
+ true,
406
+ "below anchor floor → no trim this call (retries next event)",
407
+ );
408
+ }
279
409
  });
280
410
 
281
411
  test("auto-trigger (S16): sendUserMessage resume nudge fires only when idle + queued + not already nudged", async () => {
282
- const h = harness();
283
- // No queued messages → the nudge must NOT fire (the guard prevents busy-loops).
284
- // We assert the extension did not throw and did not push a spurious resume.
285
- const ctx = h.ctx({ isIdle: () => true, hasPendingMessages: () => false });
286
- await h.fire("agent_end", { type: "agent_end", messages: [] }, ctx);
287
- // No throw + no spurious nudge side-effect is the contract; appended stays
288
- // free of any auto "continue" marker when there is no queued work.
289
- assert.equal(h.appended.some((a) => a.t && /continue/i.test(String(a.d ?? ""))), false, "no spurious continue when no queued work");
412
+ const h = harness();
413
+ // No queued messages → the nudge must NOT fire (the guard prevents busy-loops).
414
+ // We assert the extension did not throw and did not push a spurious resume.
415
+ const ctx = h.ctx({ isIdle: () => true, hasPendingMessages: () => false });
416
+ await h.fire("agent_end", { type: "agent_end", messages: [] }, ctx);
417
+ // No throw + no spurious nudge side-effect is the contract; appended stays
418
+ // free of any auto "continue" marker when there is no queued work.
419
+ assert.equal(
420
+ h.appended.some((a) => a.t && /continue/i.test(String(a.d ?? ""))),
421
+ false,
422
+ "no spurious continue when no queued work",
423
+ );
290
424
  });
291
425
 
292
426
  test("auto-trigger (S16): durable trim still happens via pi native auto-compaction (session_before_compact)", async () => {
293
- const h = harness();
294
- // pi's native auto-compaction fires at agent-end with reason "threshold" (the
295
- // CONTINUING path). Our session_before_compact handler must still supply the
296
- // durable trim summary — independent of the live context-event trim.
297
- const prep = {
298
- firstKeptEntryId: "e2",
299
- messagesToSummarize: h.session.slice(0, 4),
300
- tokensBefore: 500,
301
- };
302
- const res = await h.fire("session_before_compact", {
303
- type: "session_before_compact", reason: "threshold", willRetry: false,
304
- signal: undefined, preparation: prep,
305
- } as any, h.ctx());
306
- assert.ok(res?.compaction, "we supply a durable compaction result to pi's native path");
307
- assert.ok(res.compaction.firstKeptEntryId === "e2", "reuses pi's boundary (PREVENT-PI-002)");
308
- assert.ok(res.compaction.summary.length > 0, "summary is non-empty");
427
+ const h = harness();
428
+ // pi's native auto-compaction fires at agent-end with reason "threshold" (the
429
+ // CONTINUING path). Our session_before_compact handler must still supply the
430
+ // durable trim summary — independent of the live context-event trim.
431
+ const prep = {
432
+ firstKeptEntryId: "e2",
433
+ messagesToSummarize: h.session.slice(0, 4),
434
+ tokensBefore: 500,
435
+ };
436
+ const res = await h.fire(
437
+ "session_before_compact",
438
+ {
439
+ type: "session_before_compact",
440
+ reason: "threshold",
441
+ willRetry: false,
442
+ signal: undefined,
443
+ preparation: prep,
444
+ } as any,
445
+ h.ctx(),
446
+ );
447
+ assert.ok(
448
+ res?.compaction,
449
+ "we supply a durable compaction result to pi's native path",
450
+ );
451
+ assert.ok(
452
+ res.compaction.firstKeptEntryId === "e2",
453
+ "reuses pi's boundary (PREVENT-PI-002)",
454
+ );
455
+ assert.ok(res.compaction.summary.length > 0, "summary is non-empty");
309
456
  });
310
457
 
311
458
  test("session_before_compact supplies our durable trim (not pi's summary)", async () => {
312
- const h = harness();
313
- // pi fires session_before_compact with its own computed preparation.
314
- const res = await h.fire(
315
- "session_before_compact",
316
- {
317
- type: "session_before_compact",
318
- reason: "overflow",
319
- willRetry: true,
320
- preparation: { firstKeptEntryId: "e2", messagesToSummarize: h.session.slice(0, 2), tokensBefore: 500 },
321
- signal: undefined,
322
- } as any,
323
- h.ctx(),
324
- );
325
- assert.ok(res && res.compaction, "returns a compaction result");
326
- assert.equal(res.compaction.firstKeptEntryId, "e2", "reuses pi's cut boundary (PREVENT-PI-002 safe)");
327
- assert.ok(typeof res.compaction.summary === "string" && res.compaction.summary.length > 0, "our summary supplied");
328
- assert.ok(res.compaction.tokensBefore >= 0, "tokensBefore reported");
459
+ const h = harness();
460
+ // pi fires session_before_compact with its own computed preparation.
461
+ const res = await h.fire(
462
+ "session_before_compact",
463
+ {
464
+ type: "session_before_compact",
465
+ reason: "overflow",
466
+ willRetry: true,
467
+ preparation: {
468
+ firstKeptEntryId: "e2",
469
+ messagesToSummarize: h.session.slice(0, 2),
470
+ tokensBefore: 500,
471
+ },
472
+ signal: undefined,
473
+ } as any,
474
+ h.ctx(),
475
+ );
476
+ assert.ok(res && res.compaction, "returns a compaction result");
477
+ assert.equal(
478
+ res.compaction.firstKeptEntryId,
479
+ "e2",
480
+ "reuses pi's cut boundary (PREVENT-PI-002 safe)",
481
+ );
482
+ assert.ok(
483
+ typeof res.compaction.summary === "string" &&
484
+ res.compaction.summary.length > 0,
485
+ "our summary supplied",
486
+ );
487
+ assert.ok(res.compaction.tokensBefore >= 0, "tokensBefore reported");
329
488
  });
330
489
 
331
- test("session_before_compact falls back to pi when nothing to summarize", async () => {
332
- const h = harness();
333
- // Empty preparation → no messages to summarize return {} so pi compacts natively.
334
- const res = await h.fire(
335
- "session_before_compact",
336
- {
337
- type: "session_before_compact",
338
- reason: "threshold",
339
- willRetry: false,
340
- preparation: { firstKeptEntryId: "e0", messagesToSummarize: [], tokensBefore: 0 },
341
- signal: undefined,
342
- } as any,
343
- h.ctx(),
344
- );
345
- assert.deepEqual(res, {}, "no compaction supplied → pi runs its own");
490
+ test("session_before_compact supplies a fallback summary when nothing to summarize", async () => {
491
+ const h = harness();
492
+ // Empty preparation → no messages to summarize (anchor floor protects
493
+ // everything). We MUST still supply a compaction (never {}), otherwise pi
494
+ // runs its own compact() which throws "Nothing to compact (session too
495
+ // small)" and leaves the session stuck with no resume context. The fallback
496
+ // records a minimal resume summary so the session always resumes.
497
+ const res = await h.fire(
498
+ "session_before_compact",
499
+ {
500
+ type: "session_before_compact",
501
+ reason: "threshold",
502
+ willRetry: false,
503
+ preparation: {
504
+ firstKeptEntryId: "e0",
505
+ messagesToSummarize: [],
506
+ tokensBefore: 0,
507
+ },
508
+ signal: undefined,
509
+ } as any,
510
+ h.ctx(),
511
+ );
512
+ assert.ok(
513
+ res && (res as any).compaction,
514
+ "fallback compaction supplied (never {})",
515
+ );
516
+ assert.ok(
517
+ (res as any).compaction.summary.includes("context compacted"),
518
+ "fallback summary injected so the session resumes",
519
+ );
520
+ assert.equal(
521
+ (res as any).compaction.firstKeptEntryId,
522
+ "e0",
523
+ "keeps pi's cut point",
524
+ );
346
525
  });
347
526
 
348
527
  test("resume auto-inline stages recall into the system prompt", async () => {
349
- const h = harness();
350
- // Seed a checkpoint first (simulate a prior session that compacted).
351
- await h.fire("context", { type: "context", messages: h.session }, h.ctx({ getContextUsage: () => ({ tokens: 200000, contextWindow: 200000, percent: 100 }) }));
352
- // Fresh resume: session_start with reason "resume".
353
- const ctx = h.ctx();
354
- await h.fire("session_start", { type: "session_start", reason: "resume", previousSessionFile: undefined } as any, ctx);
355
- // The next before_agent_start must prepend the recalled block.
356
- const res = await h.fire("before_agent_start", { type: "before_agent_start", prompt: "base system", images: undefined, systemPrompt: "base system", systemPromptOptions: {} } as any, ctx);
357
- assert.ok(res && typeof res.systemPrompt === "string", "before_agent_start returns a systemPrompt");
358
- assert.ok(res.systemPrompt.includes("Recalled context"), "recalled block injected into system prompt");
528
+ const h = harness();
529
+ // Seed a checkpoint first (simulate a prior session that compacted).
530
+ await h.fire(
531
+ "context",
532
+ { type: "context", messages: h.session },
533
+ h.ctx({
534
+ getContextUsage: () => ({
535
+ tokens: 200000,
536
+ contextWindow: 200000,
537
+ percent: 100,
538
+ }),
539
+ }),
540
+ );
541
+ // Fresh resume: session_start with reason "resume".
542
+ const ctx = h.ctx();
543
+ await h.fire(
544
+ "session_start",
545
+ {
546
+ type: "session_start",
547
+ reason: "resume",
548
+ previousSessionFile: undefined,
549
+ } as any,
550
+ ctx,
551
+ );
552
+ // The next before_agent_start must prepend the recalled block.
553
+ const res = await h.fire(
554
+ "before_agent_start",
555
+ {
556
+ type: "before_agent_start",
557
+ prompt: "base system",
558
+ images: undefined,
559
+ systemPrompt: "base system",
560
+ systemPromptOptions: {},
561
+ } as any,
562
+ ctx,
563
+ );
564
+ assert.ok(
565
+ res && typeof res.systemPrompt === "string",
566
+ "before_agent_start returns a systemPrompt",
567
+ );
568
+ assert.ok(
569
+ res.systemPrompt.includes("Recalled context"),
570
+ "recalled block injected into system prompt",
571
+ );
359
572
  });
360
573
 
361
574
  test("/recall-context reports and stages the top checkpoint", async () => {
362
- const h = harness();
363
- await h.fire("context", { type: "context", messages: h.session }, h.ctx({ getContextUsage: () => ({ tokens: 200000, contextWindow: 200000, percent: 100 }) }));
364
- const ctx = h.ctx();
365
- await h.commands["mega-recall"].handler("dedupe bug store.ts", ctx);
366
- assert.ok(h.notifies.some((n) => n.includes("recall staged")), "command reports staged checkpoints");
367
- assert.ok(h.notifies.some((n) => n.includes("chkpt_")), "command names the checkpoint");
575
+ const h = harness();
576
+ await h.fire(
577
+ "context",
578
+ { type: "context", messages: h.session },
579
+ h.ctx({
580
+ getContextUsage: () => ({
581
+ tokens: 200000,
582
+ contextWindow: 200000,
583
+ percent: 100,
584
+ }),
585
+ }),
586
+ );
587
+ const ctx = h.ctx();
588
+ await h.commands["mega-recall"].handler("dedupe bug store.ts", ctx);
589
+ assert.ok(
590
+ h.notifies.some((n) => n.includes("recall staged")),
591
+ "command reports staged checkpoints",
592
+ );
593
+ assert.ok(
594
+ h.notifies.some((n) => n.includes("chkpt_")),
595
+ "command names the checkpoint",
596
+ );
368
597
  });
369
598
 
370
599
  test("/megacompact-status reports live store stats", async () => {
371
- const h = harness();
372
- await h.fire("context", { type: "context", messages: h.session }, h.ctx({ getContextUsage: () => ({ tokens: 200000, contextWindow: 200000, percent: 100 }) }));
373
- const ctx = h.ctx({ getContextUsage: () => ({ tokens: 50000, contextWindow: 200000, percent: 25 }) });
374
- await h.commands["mega-status"].handler("", ctx);
375
- assert.ok(h.notifies.some((n) => n.includes("store:") && n.includes("chkpt")), "status shows checkpoint count");
600
+ const h = harness();
601
+ await h.fire(
602
+ "context",
603
+ { type: "context", messages: h.session },
604
+ h.ctx({
605
+ getContextUsage: () => ({
606
+ tokens: 200000,
607
+ contextWindow: 200000,
608
+ percent: 100,
609
+ }),
610
+ }),
611
+ );
612
+ const ctx = h.ctx({
613
+ getContextUsage: () => ({
614
+ tokens: 50000,
615
+ contextWindow: 200000,
616
+ percent: 25,
617
+ }),
618
+ });
619
+ await h.commands["mega-status"].handler("", ctx);
620
+ assert.ok(
621
+ h.notifies.some((n) => n.includes("store:") && n.includes("chkpt")),
622
+ "status shows checkpoint count",
623
+ );
376
624
  });
377
625
 
378
626
  // ---- Model/provider capture (Phase 5b model_snapshots) ----------------------
379
627
  test("model_select captures model + provider into SQL", async () => {
380
- const h = harness();
381
- const modelCtx = h.ctx({
382
- model: { id: "claude-opus-4-8", name: "Claude Opus 4.8", provider: "anthropic", contextWindow: 200000, maxTokens: 32000, reasoning: false, cost: { input: 0.000015, output: 0.000075 } },
383
- modelRegistry: { getProviderDisplayName: (p: string) => (p === "anthropic" ? "Anthropic" : p) },
384
- });
385
- await h.fire("model_select", {}, modelCtx);
386
- const { latestModelSnapshot } = await import("../src/store/sqlite.js");
387
- const snap = latestModelSnapshot(h.stateDir);
388
- assert.ok(snap, "model_snapshots row persisted");
389
- assert.equal(snap!.modelId, "claude-opus-4-8", "correct model id captured");
390
- assert.equal(snap!.provider, "anthropic", "correct provider captured");
391
- assert.equal(snap!.providerName, "Anthropic", "provider display name resolved");
392
- assert.equal(snap!.inputRate, 0.000015, "input rate captured");
628
+ const h = harness();
629
+ const modelCtx = h.ctx({
630
+ model: {
631
+ id: "claude-opus-4-8",
632
+ name: "Claude Opus 4.8",
633
+ provider: "anthropic",
634
+ contextWindow: 200000,
635
+ maxTokens: 32000,
636
+ reasoning: false,
637
+ cost: { input: 0.000015, output: 0.000075 },
638
+ },
639
+ modelRegistry: {
640
+ getProviderDisplayName: (p: string) =>
641
+ p === "anthropic" ? "Anthropic" : p,
642
+ },
643
+ });
644
+ await h.fire("model_select", {}, modelCtx);
645
+ const { latestModelSnapshot } = await import("../src/store/sqlite.js");
646
+ const snap = latestModelSnapshot(h.stateDir);
647
+ assert.ok(snap, "model_snapshots row persisted");
648
+ assert.equal(snap!.modelId, "claude-opus-4-8", "correct model id captured");
649
+ assert.equal(snap!.provider, "anthropic", "correct provider captured");
650
+ assert.equal(
651
+ snap!.providerName,
652
+ "Anthropic",
653
+ "provider display name resolved",
654
+ );
655
+ assert.equal(snap!.inputRate, 0.000015, "input rate captured");
393
656
  });
394
657
 
395
658
  test("/mega-status surfaces the captured model + provider", async () => {
396
- const h = harness();
397
- const modelCtx = h.ctx({
398
- model: { id: "claude-opus-4-8", name: "Claude Opus 4.8", provider: "anthropic", contextWindow: 200000, maxTokens: 32000, reasoning: false, cost: { input: 0.000015, output: 0.000075 } },
399
- modelRegistry: { getProviderDisplayName: () => "Anthropic" },
400
- });
401
- await h.fire("model_select", {}, modelCtx);
402
- await h.fire("context", { type: "context", messages: h.session }, h.ctx({ getContextUsage: () => ({ tokens: 200000, contextWindow: 200000, percent: 100 }) }));
403
- const ctx = h.ctx({ getContextUsage: () => ({ tokens: 50000, contextWindow: 200000, percent: 25 }) });
404
- await h.commands["mega-status"].handler("", ctx);
405
- assert.ok(h.notifies.some((n) => n.includes("🤖 model:") && n.includes("Claude Opus 4.8") && n.includes("Anthropic")), "status surfaces captured model + provider");
659
+ const h = harness();
660
+ const modelCtx = h.ctx({
661
+ model: {
662
+ id: "claude-opus-4-8",
663
+ name: "Claude Opus 4.8",
664
+ provider: "anthropic",
665
+ contextWindow: 200000,
666
+ maxTokens: 32000,
667
+ reasoning: false,
668
+ cost: { input: 0.000015, output: 0.000075 },
669
+ },
670
+ modelRegistry: { getProviderDisplayName: () => "Anthropic" },
671
+ });
672
+ await h.fire("model_select", {}, modelCtx);
673
+ await h.fire(
674
+ "context",
675
+ { type: "context", messages: h.session },
676
+ h.ctx({
677
+ getContextUsage: () => ({
678
+ tokens: 200000,
679
+ contextWindow: 200000,
680
+ percent: 100,
681
+ }),
682
+ }),
683
+ );
684
+ const ctx = h.ctx({
685
+ getContextUsage: () => ({
686
+ tokens: 50000,
687
+ contextWindow: 200000,
688
+ percent: 25,
689
+ }),
690
+ });
691
+ await h.commands["mega-status"].handler("", ctx);
692
+ assert.ok(
693
+ h.notifies.some(
694
+ (n) =>
695
+ n.includes("🤖 model:") &&
696
+ n.includes("Claude Opus 4.8") &&
697
+ n.includes("Anthropic"),
698
+ ),
699
+ "status surfaces captured model + provider",
700
+ );
406
701
  });
407
702
 
408
703
  // ---- Named compaction tiers -------------------------------------------------
409
704
  // low=50k, medium=100k, high=200k, ultra=1M, mega=10M. Driven through the REAL
410
705
  // loadConfig()/status path by setting MEGACOMPACT_TIER before loading the ext.
706
+ // Percentage-based thresholds: tierPct × the model context window. The harness
707
+ // getContextUsage below reports contextWindow=2_000_000, so each tier resolves to
708
+ // tierPct × 2_000_000 — which fires BELOW pi's native ~80% auto-compaction for
709
+ // ANY model size (200k or 1M). Driven through the REAL loadConfig()/status path
710
+ // by setting MEGACOMPACT_TIER before loading the ext.
411
711
  const TIER_CASES: Array<[string, number]> = [
412
- ["low", 50_000],
413
- ["medium", 100_000],
414
- ["high", 200_000],
415
- ["ultra", 1_000_000],
416
- ["mega", 10_000_000],
712
+ ["low", 1_000_000], // 0.50 × 2_000_000
713
+ ["medium", 1_200_000], // 0.60 × 2_000_000
714
+ ["high", 1_400_000], // 0.70 × 2_000_000
715
+ ["ultra", 1_400_000], // 0.70 × 2_000_000
716
+ ["mega", 1_500_000], // 0.75 × 2_000_000
417
717
  ];
418
718
  for (const [tier, threshold] of TIER_CASES) {
419
- test(`tier "${tier}" resolves to a ${threshold}-token threshold (preset; live band shown separately)`, async () => {
420
- // Keep tier + keep threshold UNSET so the tier (not an explicit number)
421
- // drives the threshold. harness() would otherwise reset the threshold.
422
- delete process.env.MEGACOMPACT_THRESHOLD_TOKENS;
423
- process.env.MEGACOMPACT_TIER = tier;
424
- const h = harness({ keepTier: true, keepThreshold: true });
425
- // tokens=1 against a 2M window → near-zero pressure → live band "low".
426
- const ctx = h.ctx({ getContextUsage: () => ({ tokens: 1, contextWindow: 2_000_000, percent: 0.01 }) });
427
- await h.commands["mega-status"].handler("", ctx);
428
- delete process.env.MEGACOMPACT_TIER;
429
- assert.ok(
430
- h.notifies.some((n) => n.includes(`preset=${tier}`) && n.includes(`threshold=${threshold}`)),
431
- `status should report preset=${tier} threshold=${threshold}`,
432
- );
433
- // S24: the headline tier is the LIVE pressure band, shown as "tier=low (live)".
434
- assert.ok(h.notifies.some((n) => n.includes("tier=low (live)")), "live band reported (low at near-zero pressure)");
435
- });
719
+ test(`tier "${tier}" resolves to a ${threshold.toLocaleString()}-token threshold (tierPct × 2M window; live band shown separately)`, async () => {
720
+ // Keep tier + keep threshold UNSET so the tier (not an explicit number)
721
+ // drives the threshold. harness() would otherwise reset the threshold.
722
+ delete process.env.MEGACOMPACT_THRESHOLD_TOKENS;
723
+ process.env.MEGACOMPACT_TIER = tier;
724
+ const h = harness({ keepTier: true, keepThreshold: true });
725
+ // tokens=1 against a 2M window → near-zero pressure → live band "low".
726
+ const ctx = h.ctx({
727
+ getContextUsage: () => ({
728
+ tokens: 1,
729
+ contextWindow: 2_000_000,
730
+ percent: 0.01,
731
+ }),
732
+ });
733
+ await h.commands["mega-status"].handler("", ctx);
734
+ delete process.env.MEGACOMPACT_TIER;
735
+ // /mega-status renders threshold with toLocaleString() (thousands commas).
736
+ assert.ok(
737
+ h.notifies.some(
738
+ (n) =>
739
+ n.includes(`preset=${tier}`) && n.includes(`threshold=${threshold.toLocaleString()}`),
740
+ ),
741
+ `status should report preset=${tier} threshold=${threshold.toLocaleString()} (tierPct × 2M window)`,
742
+ );
743
+ // S24: the headline tier is the LIVE pressure band, shown as "tier=low (live)".
744
+ assert.ok(
745
+ h.notifies.some((n) => n.includes("tier=low (live)")),
746
+ "live band reported (low at near-zero pressure)",
747
+ );
748
+ });
436
749
  }
437
750
 
438
751
  test("explicit MEGACOMPACT_THRESHOLD_TOKENS overrides the tier", async () => {
439
- process.env.MEGACOMPACT_TIER = "mega";
440
- process.env.MEGACOMPACT_THRESHOLD_TOKENS = "777";
441
- const h = harness({ keepTier: true, keepThreshold: true });
442
- const ctx = h.ctx({ getContextUsage: () => ({ tokens: 1, contextWindow: 2_000_000, percent: 0.01 }) });
443
- await h.commands["mega-status"].handler("", ctx);
444
- delete process.env.MEGACOMPACT_TIER;
445
- assert.ok(
446
- h.notifies.some((n) => n.includes("preset=custom") && n.includes("threshold=777")),
447
- "explicit threshold wins over tier (preset=custom)",
448
- );
752
+ process.env.MEGACOMPACT_TIER = "mega";
753
+ process.env.MEGACOMPACT_THRESHOLD_TOKENS = "777";
754
+ const h = harness({ keepTier: true, keepThreshold: true });
755
+ const ctx = h.ctx({
756
+ getContextUsage: () => ({
757
+ tokens: 1,
758
+ contextWindow: 2_000_000,
759
+ percent: 0.01,
760
+ }),
761
+ });
762
+ await h.commands["mega-status"].handler("", ctx);
763
+ delete process.env.MEGACOMPACT_TIER;
764
+ assert.ok(
765
+ h.notifies.some(
766
+ (n) => n.includes("preset=custom") && n.includes("threshold=777"),
767
+ ),
768
+ "explicit threshold wins over tier (preset=custom)",
769
+ );
449
770
  });
450
771
 
451
772
  // ---- S24: memory review tied to pressure / compaction -----------------------
@@ -453,182 +774,292 @@ test("explicit MEGACOMPACT_THRESHOLD_TOKENS overrides the tier", async () => {
453
774
  // non-deduped) compaction. Each user turn contains a decision phrase
454
775
  // (/\bactually\b/i, /\bwe (?:use|decided)\b/i) so reviewConversation yields ops.
455
776
  function decisionSession(): AgentMessage[] {
456
- const out: AgentMessage[] = [];
457
- for (let i = 0; i < 14; i++) {
458
- out.push({ role: "user", content: `actually we decided to use approach ${i} for module ${i}`, timestamp: i } as unknown as AgentMessage);
459
- out.push({ role: "assistant", content: [{ type: "toolCall", name: "Edit", id: `c${i}`, arguments: {} }], api: "anthropic-messages", provider: "anthropic", model: "m", usage: { inputTokens: 1, outputTokens: 1, cacheReadTokens: 0, cacheWriteTokens: 0 }, stopReason: "tool_use", timestamp: i } as unknown as AgentMessage);
460
- out.push({ role: "toolResult", content: [{ type: "text", text: `edited module ${i}` }], toolCallId: `c${i}`, toolName: "Edit", isError: false, timestamp: i } as unknown as AgentMessage);
461
- }
462
- return out;
777
+ const out: AgentMessage[] = [];
778
+ for (let i = 0; i < 14; i++) {
779
+ out.push({
780
+ role: "user",
781
+ content: `actually we decided to use approach ${i} for module ${i}`,
782
+ timestamp: i,
783
+ } as unknown as AgentMessage);
784
+ out.push({
785
+ role: "assistant",
786
+ content: [{ type: "toolCall", name: "Edit", id: `c${i}`, arguments: {} }],
787
+ api: "anthropic-messages",
788
+ provider: "anthropic",
789
+ model: "m",
790
+ usage: {
791
+ inputTokens: 1,
792
+ outputTokens: 1,
793
+ cacheReadTokens: 0,
794
+ cacheWriteTokens: 0,
795
+ },
796
+ stopReason: "tool_use",
797
+ timestamp: i,
798
+ } as unknown as AgentMessage);
799
+ out.push({
800
+ role: "toolResult",
801
+ content: [{ type: "text", text: `edited module ${i}` }],
802
+ toolCallId: `c${i}`,
803
+ toolName: "Edit",
804
+ isError: false,
805
+ timestamp: i,
806
+ } as unknown as AgentMessage);
807
+ }
808
+ return out;
463
809
  }
464
810
 
465
811
  test("S24: high pressure triggers a memory review on compaction", async () => {
466
- const h = harness();
467
- // Force a real (non-legacy) compaction at full pressure → pressureBand "mega",
468
- // which must fire the shared runMemoryReview on compact (review-on-compact).
469
- process.env.MEGACOMPACT_LEGACY_DURABLE_TRIM = "false";
470
- try {
471
- const messages = decisionSession();
472
- const ctx = h.ctx({ getContextUsage: () => ({ tokens: 200000, contextWindow: 200000, percent: 100 }) });
473
- await h.fire("context", { type: "context", messages }, ctx);
474
- // review-on-compact runs as a fire-and-forget async (doCompact is sync), so
475
- // let the microtask/macrotask queue drain before asserting the side effect.
476
- await new Promise((r) => setTimeout(r, 20));
477
- const { listMemories, listCheckpoints } = await import("../src/store/sqlite.js");
478
- // A checkpoint must have been persisted (proves compaction ran, not skipped).
479
- assert.ok(listCheckpoints("sess_ext_001", h.stateDir).length > 0, "checkpoint persisted to local vector db");
480
- // The just-compacted region is worth remembering, so durable memories must
481
- // have been written to the SQLite store (review-on-compact path).
482
- const mem = listMemories(null, 50, h.stateDir);
483
- assert.ok(mem.length > 0, "memory review wrote durable memories on compact");
484
- } finally {
485
- delete process.env.MEGACOMPACT_LEGACY_DURABLE_TRIM;
486
- }
812
+ const h = harness();
813
+ // Force a real (non-legacy) compaction at full pressure → pressureBand "mega",
814
+ // which must fire the shared runMemoryReview on compact (review-on-compact).
815
+ process.env.MEGACOMPACT_LEGACY_DURABLE_TRIM = "false";
816
+ try {
817
+ const messages = decisionSession();
818
+ const ctx = h.ctx({
819
+ getContextUsage: () => ({
820
+ tokens: 200000,
821
+ contextWindow: 200000,
822
+ percent: 100,
823
+ }),
824
+ });
825
+ await h.fire("context", { type: "context", messages }, ctx);
826
+ // review-on-compact runs as a fire-and-forget async (doCompact is sync), so
827
+ // let the microtask/macrotask queue drain before asserting the side effect.
828
+ await new Promise((r) => setTimeout(r, 20));
829
+ const { listMemories, listCheckpoints } = await import(
830
+ "../src/store/sqlite.js"
831
+ );
832
+ // A checkpoint must have been persisted (proves compaction ran, not skipped).
833
+ assert.ok(
834
+ listCheckpoints("sess_ext_001", h.stateDir).length > 0,
835
+ "checkpoint persisted to local vector db",
836
+ );
837
+ // The just-compacted region is worth remembering, so durable memories must
838
+ // have been written to the SQLite store (review-on-compact path).
839
+ const mem = listMemories(null, 50, h.stateDir);
840
+ assert.ok(
841
+ mem.length > 0,
842
+ "memory review wrote durable memories on compact",
843
+ );
844
+ } finally {
845
+ delete process.env.MEGACOMPACT_LEGACY_DURABLE_TRIM;
846
+ }
487
847
  });
488
848
 
489
849
  test("S24: /mega-status reports the live pressure band + %", async () => {
490
- const h = harness();
491
- // Populate the runtime's live context first (a context event sets
492
- // lastCtxTokens/lastCtxPercent), then read /mega-status. At 100% usage the live
493
- // band must read "mega" and pressure must report 100%.
494
- const ctx = h.ctx({ getContextUsage: () => ({ tokens: 200000, contextWindow: 200000, percent: 100 }) });
495
- await h.fire("context", { type: "context", messages: h.session }, ctx);
496
- await h.commands["mega-status"].handler("", ctx);
497
- assert.ok(h.notifies.some((n) => n.includes("tier=mega (live)")), "live band reported as mega at 100% pressure");
498
- assert.ok(h.notifies.some((n) => n.includes("pressure=100%")), "live pressure % reported");
850
+ const h = harness();
851
+ // Populate the runtime's live context first (a context event sets
852
+ // lastCtxTokens/lastCtxPercent), then read /mega-status. At 100% usage the live
853
+ // band must read "mega" and pressure must report 100%.
854
+ const ctx = h.ctx({
855
+ getContextUsage: () => ({
856
+ tokens: 200000,
857
+ contextWindow: 200000,
858
+ percent: 100,
859
+ }),
860
+ });
861
+ await h.fire("context", { type: "context", messages: h.session }, ctx);
862
+ await h.commands["mega-status"].handler("", ctx);
863
+ assert.ok(
864
+ h.notifies.some((n) => n.includes("tier=mega (live)")),
865
+ "live band reported as mega at 100% pressure",
866
+ );
867
+ assert.ok(
868
+ h.notifies.some((n) => n.includes("pressure=100%")),
869
+ "live pressure % reported",
870
+ );
499
871
  });
500
872
 
501
873
  // ---- /dashboard commands ----------------------------------------------------
502
874
  test("/dashboard-status reports no server when pid file missing", async () => {
503
- // Private base so this asserts "no server" on a range nothing else uses,
504
- // not the machine-global 9320 family (which may hold a leftover/production server).
505
- process.env.MEGACOMPACT_DASHBOARD_PORT = "49320";
506
- try {
507
- const h = harness();
508
- const ctx = h.ctx();
509
- await h.commands["mega-dashboard-status"].handler("", ctx);
510
- assert.ok(h.notifies.some((n) => n.includes("not running")), "reports no server running");
511
- } finally {
512
- delete process.env.MEGACOMPACT_DASHBOARD_PORT;
513
- }
875
+ // Private base so this asserts "no server" on a range nothing else uses,
876
+ // not the machine-global 9320 family (which may hold a leftover/production server).
877
+ process.env.MEGACOMPACT_DASHBOARD_PORT = "49320";
878
+ try {
879
+ const h = harness();
880
+ const ctx = h.ctx();
881
+ await h.commands["mega-dashboard-status"].handler("", ctx);
882
+ assert.ok(
883
+ h.notifies.some((n) => n.includes("not running")),
884
+ "reports no server running",
885
+ );
886
+ } finally {
887
+ delete process.env.MEGACOMPACT_DASHBOARD_PORT;
888
+ }
514
889
  });
515
890
 
516
891
  test("/dashboard-stop reports no server when pid file missing", async () => {
517
- const h = harness();
518
- const ctx = h.ctx();
519
- await h.commands["mega-dashboard-stop"].handler("", ctx);
520
- assert.ok(h.notifies.some((n) => n.includes("no dashboard server running")), "reports no server");
892
+ const h = harness();
893
+ const ctx = h.ctx();
894
+ await h.commands["mega-dashboard-stop"].handler("", ctx);
895
+ assert.ok(
896
+ h.notifies.some((n) => n.includes("no dashboard server running")),
897
+ "reports no server",
898
+ );
521
899
  });
522
900
 
523
901
  test("/dashboard skips server spawn when already running", async () => {
524
- // Use a private dashboard port base for THIS test's harness + fake server so
525
- // it never races the (parallel, hard-coded-9320) dashboard-server.test.js or
526
- // a leftover production server. Set BEFORE harness() so registerDashboardCommands
527
- // reads our base for findLivePort().
528
- process.env.MEGACOMPACT_DASHBOARD_PORT = "29320";
529
- const h = harness();
530
- const confirms: boolean[] = [];
531
- const livPort = 29320; // inside the harness's private scan range (29320–29329)
532
- const { createServer } = await import("node:http");
533
- const server = createServer((_req, res) => {
534
- res.writeHead(200, { "Content-Type": "application/json" });
535
- res.end(JSON.stringify({ updatedAt: new Date().toISOString(), tier: "test", version: 1, config: {}, session: {}, context: {}, trigger: {}, store: {} }));
536
- });
537
- await new Promise<void>((r) => server.listen(livPort, "127.0.0.1", r));
538
- const { join: j } = await import("node:path");
539
- const { writeFileSync: wf } = await import("node:fs");
540
- wf(j(h.stateDir, "port.pid"), JSON.stringify({ port: livPort, pid: process.pid }));
541
-
542
- const ctx = h.ctx({
543
- ui: {
544
- setStatus: () => {},
545
- notify: (s: string) => { h.notifies.push(s); },
546
- select: () => {},
547
- confirm: async () => { confirms.push(true); return true; },
548
- input: async () => "",
549
- },
550
- });
551
-
552
- await h.commands["mega-dashboard"].handler("", ctx);
553
- assert.ok(h.notifies.some((n) => n.includes("already running")), "reports already running");
554
- assert.ok(confirms.length > 0, "confirm dialog was shown");
555
-
556
- await new Promise<void>((r) => server.close(() => r()));
557
- delete process.env.MEGACOMPACT_DASHBOARD_PORT;
902
+ // Use a private dashboard port base for THIS test's harness + fake server so
903
+ // it never races the (parallel, hard-coded-9320) dashboard-server.test.js or
904
+ // a leftover production server. Set BEFORE harness() so registerDashboardCommands
905
+ // reads our base for findLivePort().
906
+ process.env.MEGACOMPACT_DASHBOARD_PORT = "29320";
907
+ const h = harness();
908
+ const confirms: boolean[] = [];
909
+ const livPort = 29320; // inside the harness's private scan range (29320–29329)
910
+ const { createServer } = await import("node:http");
911
+ const server = createServer((_req, res) => {
912
+ res.writeHead(200, { "Content-Type": "application/json" });
913
+ res.end(
914
+ JSON.stringify({
915
+ updatedAt: new Date().toISOString(),
916
+ tier: "test",
917
+ version: 1,
918
+ config: {},
919
+ session: {},
920
+ context: {},
921
+ trigger: {},
922
+ store: {},
923
+ }),
924
+ );
925
+ });
926
+ await new Promise<void>((r) => server.listen(livPort, "127.0.0.1", r));
927
+ const { join: j } = await import("node:path");
928
+ const { writeFileSync: wf } = await import("node:fs");
929
+ wf(
930
+ j(h.stateDir, "port.pid"),
931
+ JSON.stringify({ port: livPort, pid: process.pid }),
932
+ );
933
+
934
+ const ctx = h.ctx({
935
+ ui: {
936
+ setStatus: () => {},
937
+ notify: (s: string) => {
938
+ h.notifies.push(s);
939
+ },
940
+ select: () => {},
941
+ confirm: async () => {
942
+ confirms.push(true);
943
+ return true;
944
+ },
945
+ input: async () => "",
946
+ },
947
+ });
948
+
949
+ await h.commands["mega-dashboard"].handler("", ctx);
950
+ assert.ok(
951
+ h.notifies.some((n) => n.includes("already running")),
952
+ "reports already running",
953
+ );
954
+ assert.ok(confirms.length > 0, "confirm dialog was shown");
955
+
956
+ await new Promise<void>((r) => server.close(() => r()));
957
+ delete process.env.MEGACOMPACT_DASHBOARD_PORT;
558
958
  });
559
959
 
560
960
  test("/dashboard-status reports running after dashboard start", async () => {
561
- // Private dashboard port base for this harness — never collides with the
562
- // parallel dashboard-server.test.js (9320 family) or a leftover server.
563
- process.env.MEGACOMPACT_DASHBOARD_PORT = "39320";
564
- const h = harness();
565
- const livPort = 39320;
566
- const { createServer } = await import("node:http");
567
- const { join: j } = await import("node:path");
568
- const { writeFileSync: wf } = await import("node:fs");
569
- const server = createServer((_req, res) => {
570
- res.writeHead(200, { "Content-Type": "application/json" });
571
- res.end(JSON.stringify({ updatedAt: new Date().toISOString(), tier: "test" }));
572
- });
573
- await new Promise<void>((r) => server.listen(livPort, "127.0.0.1", r));
574
- wf(j(h.stateDir, "port.pid"), JSON.stringify({ port: livPort, pid: process.pid }));
575
-
576
- const ctx = h.ctx();
577
- await h.commands["mega-dashboard-status"].handler("", ctx);
578
- assert.ok(h.notifies.some((n) => n.includes("running") && n.includes(String(livPort))), "reports running with port");
579
-
580
- await new Promise<void>((r) => server.close(() => r()));
581
- delete process.env.MEGACOMPACT_DASHBOARD_PORT;
961
+ // Private dashboard port base for this harness — never collides with the
962
+ // parallel dashboard-server.test.js (9320 family) or a leftover server.
963
+ process.env.MEGACOMPACT_DASHBOARD_PORT = "39320";
964
+ const h = harness();
965
+ const livPort = 39320;
966
+ const { createServer } = await import("node:http");
967
+ const { join: j } = await import("node:path");
968
+ const { writeFileSync: wf } = await import("node:fs");
969
+ const server = createServer((_req, res) => {
970
+ res.writeHead(200, { "Content-Type": "application/json" });
971
+ res.end(
972
+ JSON.stringify({ updatedAt: new Date().toISOString(), tier: "test" }),
973
+ );
974
+ });
975
+ await new Promise<void>((r) => server.listen(livPort, "127.0.0.1", r));
976
+ wf(
977
+ j(h.stateDir, "port.pid"),
978
+ JSON.stringify({ port: livPort, pid: process.pid }),
979
+ );
980
+
981
+ const ctx = h.ctx();
982
+ await h.commands["mega-dashboard-status"].handler("", ctx);
983
+ assert.ok(
984
+ h.notifies.some(
985
+ (n) => n.includes("running") && n.includes(String(livPort)),
986
+ ),
987
+ "reports running with port",
988
+ );
989
+
990
+ await new Promise<void>((r) => server.close(() => r()));
991
+ delete process.env.MEGACOMPACT_DASHBOARD_PORT;
582
992
  });
583
993
 
584
994
  test("state snapshot writes dashboard.json after compaction", async () => {
585
- const h = harness();
586
- const ctx = h.ctx({ getContextUsage: () => ({ tokens: 200000, contextWindow: 200000, percent: 100 }) });
587
- // Fire auto-trigger compaction (context event above 80% threshold)
588
- await h.fire("context", { type: "context", messages: h.session }, ctx);
589
- const { existsSync: ex, readFileSync: rf } = await import("node:fs");
590
- const { join: j } = await import("node:path");
591
- const snapPath = j(h.stateDir, "dashboard.json");
592
- assert.ok(ex(snapPath), "dashboard.json written after compaction");
593
- const snap = JSON.parse(rf(snapPath, "utf-8"));
594
- // Item B: the honest token model is wired — the original dropped region was
595
- // captured (originalTokens > 0), and the saved amount never exceeds the
596
- // original (saved = max(0, original stored) ≤ original). For this tiny
597
- // harness session the summary can be ≥ the region, so saved may be 0; the
598
- // positive "saved > 0" case with a large region is covered by the
599
- // vectorStore unit tests.
600
- assert.ok(snap.store.originalTokens > 0, "snapshot.store.originalTokens captured after compaction");
601
- assert.ok(
602
- snap.store.originalTokens >= snap.store.tokensSaved,
603
- "model invariant: original region >= tokens saved",
604
- );
605
- // Item A: crew (live agent) block is present in the dashboard snapshot.
606
- assert.ok(snap.crew && typeof snap.crew.activeAgents === "number", "snapshot.crew.activeAgents present");
995
+ const h = harness();
996
+ const ctx = h.ctx({
997
+ getContextUsage: () => ({
998
+ tokens: 200000,
999
+ contextWindow: 200000,
1000
+ percent: 100,
1001
+ }),
1002
+ });
1003
+ // Fire auto-trigger compaction (context event above 80% threshold)
1004
+ await h.fire("context", { type: "context", messages: h.session }, ctx);
1005
+ const { existsSync: ex, readFileSync: rf } = await import("node:fs");
1006
+ const { join: j } = await import("node:path");
1007
+ const snapPath = j(h.stateDir, "dashboard.json");
1008
+ assert.ok(ex(snapPath), "dashboard.json written after compaction");
1009
+ const snap = JSON.parse(rf(snapPath, "utf-8"));
1010
+ // Item B: the honest token model is wired — the original dropped region was
1011
+ // captured (originalTokens > 0), and the saved amount never exceeds the
1012
+ // original (saved = max(0, original − stored) ≤ original). For this tiny
1013
+ // harness session the summary can be ≥ the region, so saved may be 0; the
1014
+ // positive "saved > 0" case with a large region is covered by the
1015
+ // vectorStore unit tests.
1016
+ assert.ok(
1017
+ snap.store.originalTokens > 0,
1018
+ "snapshot.store.originalTokens captured after compaction",
1019
+ );
1020
+ assert.ok(
1021
+ snap.store.originalTokens >= snap.store.tokensSaved,
1022
+ "model invariant: original region >= tokens saved",
1023
+ );
1024
+ // Item A: crew (live agent) block is present in the dashboard snapshot.
1025
+ assert.ok(
1026
+ snap.crew && typeof snap.crew.activeAgents === "number",
1027
+ "snapshot.crew.activeAgents present",
1028
+ );
607
1029
  });
608
1030
 
609
1031
  test("events.log receives compaction events", async () => {
610
- const h = harness();
611
- const ctx = h.ctx({ getContextUsage: () => ({ tokens: 200000, contextWindow: 200000, percent: 100 }) });
612
- // Fire auto-trigger compaction twice (first fires compaction, second also fires)
613
- await h.fire("context", { type: "context", messages: h.session }, ctx);
614
- const { readFileSync: rf, existsSync: ex } = await import("node:fs");
615
- const { join: j } = await import("node:path");
616
- const logPath = j(h.stateDir, "events.log");
617
- if (ex(logPath)) {
618
- const content = rf(logPath, "utf-8").trim();
619
- // At minimum, we expect at least one event logged
620
- assert.ok(content.length > 0, "events.log is non-empty after compaction");
621
- } else {
622
- // events.log may not exist if the DashboardEmitter path differs from stateDir;
623
- // verify dashboard.json was written (proves the post-compact path executed)
624
- assert.ok(ex(j(h.stateDir, "dashboard.json")), "dashboard.json proves post-compact ran");
625
- }
1032
+ const h = harness();
1033
+ const ctx = h.ctx({
1034
+ getContextUsage: () => ({
1035
+ tokens: 200000,
1036
+ contextWindow: 200000,
1037
+ percent: 100,
1038
+ }),
1039
+ });
1040
+ // Fire auto-trigger compaction twice (first fires compaction, second also fires)
1041
+ await h.fire("context", { type: "context", messages: h.session }, ctx);
1042
+ const { readFileSync: rf, existsSync: ex } = await import("node:fs");
1043
+ const { join: j } = await import("node:path");
1044
+ const logPath = j(h.stateDir, "events.log");
1045
+ if (ex(logPath)) {
1046
+ const content = rf(logPath, "utf-8").trim();
1047
+ // At minimum, we expect at least one event logged
1048
+ assert.ok(content.length > 0, "events.log is non-empty after compaction");
1049
+ } else {
1050
+ // events.log may not exist if the DashboardEmitter path differs from stateDir;
1051
+ // verify dashboard.json was written (proves the post-compact path executed)
1052
+ assert.ok(
1053
+ ex(j(h.stateDir, "dashboard.json")),
1054
+ "dashboard.json proves post-compact ran",
1055
+ );
1056
+ }
626
1057
  });
627
1058
 
628
1059
  test("cleanup", async () => {
629
- // Terminate the global PGlite cross-repo index (WASM worker thread) so the
630
- // test process can exit. Without this, node --test never returns even though
631
- // every test passed — the leaked worker keeps the event loop alive.
632
- await closeVectorIndex();
633
- rmSync(baseTmp, { recursive: true, force: true });
1060
+ // Terminate the global PGlite cross-repo index (WASM worker thread) so the
1061
+ // test process can exit. Without this, node --test never returns even though
1062
+ // every test passed — the leaked worker keeps the event loop alive.
1063
+ await closeVectorIndex();
1064
+ rmSync(baseTmp, { recursive: true, force: true });
634
1065
  });