@monotykamary/pi-vcc 0.3.16

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 (76) hide show
  1. package/README.md +688 -0
  2. package/__tests__/helpers.ts +135 -0
  3. package/__tests__/root-causes.test.ts +858 -0
  4. package/demo.gif +0 -0
  5. package/index.ts +21 -0
  6. package/knip.json +4 -0
  7. package/npm-shrinkwrap.json +3001 -0
  8. package/package.json +36 -0
  9. package/scripts/audit-sessions.ts +88 -0
  10. package/scripts/benchmark-real-sessions.ts +25 -0
  11. package/scripts/compare-before-after.ts +36 -0
  12. package/scripts/dump-branch-output.ts +20 -0
  13. package/src/commands/pi-vcc.ts +36 -0
  14. package/src/commands/vcc-recall.ts +65 -0
  15. package/src/core/brief.ts +497 -0
  16. package/src/core/build-sections.ts +431 -0
  17. package/src/core/content.ts +71 -0
  18. package/src/core/filter-noise.ts +42 -0
  19. package/src/core/format-recall.ts +177 -0
  20. package/src/core/format.ts +98 -0
  21. package/src/core/invisible-continue.ts +172 -0
  22. package/src/core/lineage.ts +26 -0
  23. package/src/core/load-messages.ts +40 -0
  24. package/src/core/normalize.ts +73 -0
  25. package/src/core/recall-scope.ts +49 -0
  26. package/src/core/render-entries.ts +58 -0
  27. package/src/core/report.ts +237 -0
  28. package/src/core/sanitize.ts +5 -0
  29. package/src/core/search-entries.ts +375 -0
  30. package/src/core/settings.ts +190 -0
  31. package/src/core/skill-collapse.ts +35 -0
  32. package/src/core/summarize.ts +179 -0
  33. package/src/core/tool-args.ts +14 -0
  34. package/src/details.ts +19 -0
  35. package/src/extract/commits.ts +69 -0
  36. package/src/extract/goals.ts +79 -0
  37. package/src/extract/preferences.ts +55 -0
  38. package/src/extract/shared-symbols.ts +357 -0
  39. package/src/hooks/before-compact.ts +564 -0
  40. package/src/hooks/proactive-threshold.ts +131 -0
  41. package/src/sections.ts +23 -0
  42. package/src/tools/recall.ts +204 -0
  43. package/src/types.ts +20 -0
  44. package/tests/australian-government.test.ts +479 -0
  45. package/tests/before-compact-hook.test.ts +185 -0
  46. package/tests/before-compact.test.ts +227 -0
  47. package/tests/brief.test.ts +215 -0
  48. package/tests/build-sections.test.ts +59 -0
  49. package/tests/compile.test.ts +89 -0
  50. package/tests/content.test.ts +66 -0
  51. package/tests/deep-extraction.test.ts +313 -0
  52. package/tests/extract-goals.test.ts +86 -0
  53. package/tests/extract-preferences.test.ts +30 -0
  54. package/tests/filter-noise.test.ts +61 -0
  55. package/tests/fixtures.ts +61 -0
  56. package/tests/format-recall.test.ts +87 -0
  57. package/tests/format.test.ts +73 -0
  58. package/tests/government-domain.test.ts +542 -0
  59. package/tests/lineage.test.ts +33 -0
  60. package/tests/load-messages.test.ts +49 -0
  61. package/tests/model-threshold-hook.test.ts +377 -0
  62. package/tests/model-threshold.test.ts +223 -0
  63. package/tests/normalize.test.ts +104 -0
  64. package/tests/proactive-threshold.test.ts +490 -0
  65. package/tests/real-sessions.test.ts +38 -0
  66. package/tests/recall-expand.test.ts +15 -0
  67. package/tests/recall-scope.test.ts +32 -0
  68. package/tests/recall-tool-scope.test.ts +67 -0
  69. package/tests/render-entries.test.ts +92 -0
  70. package/tests/report.test.ts +58 -0
  71. package/tests/sanitize.test.ts +24 -0
  72. package/tests/search-entries.test.ts +233 -0
  73. package/tests/support/load-session.ts +23 -0
  74. package/tests/support/real-sessions.ts +51 -0
  75. package/tests/threshold-integration.test.ts +359 -0
  76. package/tsconfig.json +20 -0
@@ -0,0 +1,104 @@
1
+ import { describe, it, expect } from "bun:test";
2
+ import { normalize } from "../src/core/normalize";
3
+ import {
4
+ userMsg,
5
+ assistantText,
6
+ assistantWithThinking,
7
+ assistantWithToolCall,
8
+ toolResult,
9
+ } from "./fixtures";
10
+
11
+ describe("normalize", () => {
12
+ it("returns empty for empty input", () => {
13
+ expect(normalize([])).toEqual([]);
14
+ });
15
+
16
+ it("normalizes user message (string content)", () => {
17
+ const blocks = normalize([userMsg("fix the bug")]);
18
+ expect(blocks).toEqual([{ kind: "user", text: "fix the bug", sourceIndex: 0 }]);
19
+ });
20
+
21
+ it("normalizes assistant text message", () => {
22
+ const blocks = normalize([assistantText("done")]);
23
+ expect(blocks).toEqual([{ kind: "assistant", text: "done", sourceIndex: 0 }]);
24
+ });
25
+
26
+ it("normalizes assistant string content", () => {
27
+ const msg = { ...assistantText("done"), content: "plain text" } as any;
28
+ expect(normalize([msg])).toEqual([{ kind: "assistant", text: "plain text", sourceIndex: 0 }]);
29
+ });
30
+
31
+ it("splits assistant thinking + text", () => {
32
+ const blocks = normalize([assistantWithThinking("result", "hmm")]);
33
+ expect(blocks).toHaveLength(2);
34
+ expect(blocks[0]).toEqual({
35
+ kind: "thinking", text: "hmm", redacted: false, sourceIndex: 0,
36
+ });
37
+ expect(blocks[1]).toEqual({ kind: "assistant", text: "result", sourceIndex: 0 });
38
+ });
39
+
40
+ it("normalizes tool call", () => {
41
+ const blocks = normalize([assistantWithToolCall("Read", { path: "a.ts" })]);
42
+ expect(blocks).toEqual([{
43
+ kind: "tool_call", name: "Read", args: { path: "a.ts" }, sourceIndex: 0,
44
+ }]);
45
+ });
46
+
47
+ it("normalizes tool result", () => {
48
+ const blocks = normalize([toolResult("Read", "file contents")]);
49
+ expect(blocks).toEqual([{
50
+ kind: "tool_result", name: "Read",
51
+ text: "file contents", isError: false, sourceIndex: 0,
52
+ }]);
53
+ });
54
+
55
+ it("normalizes error tool result", () => {
56
+ const blocks = normalize([toolResult("Edit", "not found", true)]);
57
+ expect(blocks[0]).toMatchObject({
58
+ kind: "tool_result", isError: true,
59
+ });
60
+ });
61
+
62
+ it("handles mixed message sequence", () => {
63
+ const blocks = normalize([
64
+ userMsg("fix it"),
65
+ assistantWithToolCall("Read", { path: "x.ts" }),
66
+ toolResult("Read", "code"),
67
+ assistantText("done"),
68
+ ]);
69
+ expect(blocks).toHaveLength(4);
70
+ expect(blocks.map((b) => b.kind)).toEqual([
71
+ "user", "tool_call", "tool_result", "assistant",
72
+ ]);
73
+ });
74
+
75
+ it("produces image placeholder for user image content", () => {
76
+ const msg = {
77
+ role: "user" as const,
78
+ content: [
79
+ { type: "text" as const, text: "look at this" },
80
+ { type: "image" as const, data: "abc", mimeType: "image/png" },
81
+ ],
82
+ timestamp: Date.now(),
83
+ };
84
+ const blocks = normalize([msg]);
85
+ expect(blocks).toHaveLength(2);
86
+ expect(blocks[0]).toEqual({ kind: "user", text: "look at this", sourceIndex: 0 });
87
+ expect(blocks[1]).toEqual({ kind: "user", text: "[image: image/png]", sourceIndex: 0 });
88
+ });
89
+
90
+ it("normalizes bashExecution messages", () => {
91
+ const msg = { role: "bashExecution", command: "ls -la", output: "files", exitCode: 0 } as any;
92
+ const blocks = normalize([msg]);
93
+ expect(blocks).toEqual([
94
+ { kind: "bash", command: "ls -la", output: "files", exitCode: 0, sourceIndex: 0 },
95
+ ]);
96
+ });
97
+
98
+ it("skips truly unknown message roles gracefully", () => {
99
+ const custom = { role: "custom", content: "hello" } as any;
100
+ expect(normalize([custom])).toEqual([]);
101
+ });
102
+ });
103
+
104
+
@@ -0,0 +1,490 @@
1
+ import { describe, test, expect, beforeEach, afterEach, beforeAll, afterAll } from "bun:test";
2
+ import { existsSync, unlinkSync, writeFileSync, mkdtempSync, rmSync } from "fs";
3
+ import { tmpdir } from "os";
4
+ import { join } from "path";
5
+ import { registerProactiveThresholdHook, resetProactiveState } from "../src/hooks/proactive-threshold";
6
+
7
+ let tmpDir: string;
8
+ let CONFIG_PATH: string;
9
+
10
+ beforeAll(() => {
11
+ tmpDir = mkdtempSync(join(tmpdir(), "pi-vcc-test-"));
12
+ CONFIG_PATH = join(tmpDir, "pi-vcc-config.json");
13
+ process.env.PI_VCC_CONFIG_PATH = CONFIG_PATH;
14
+ });
15
+
16
+ afterAll(() => {
17
+ delete process.env.PI_VCC_CONFIG_PATH;
18
+ rmSync(tmpDir, { recursive: true, force: true });
19
+ });
20
+
21
+ interface MockResult {
22
+ piApi: any; // the object passed to registerProactiveThresholdHook(pi)
23
+ ctx: any;
24
+ emit: (eventName: string, event?: any) => void;
25
+ captured: { method: string; args: any[] }[];
26
+ notifyCalls: { msg: string; level: string }[];
27
+ }
28
+
29
+ function createMockPi(
30
+ model?: { id: string; provider?: string; contextWindow?: number },
31
+ usage?: { tokens: number | null; contextWindow: number; percent: number | null },
32
+ ): MockResult {
33
+ const captured: { method: string; args: any[] }[] = [];
34
+ const notifyCalls: { msg: string; level: string }[] = [];
35
+ const handlers: Record<string, ((e: any, c: any) => any)[]> = {};
36
+
37
+ const ctx = {
38
+ hasUI: true,
39
+ model: model ?? undefined,
40
+ ui: {
41
+ notify: (msg: string, level: string) => {
42
+ notifyCalls.push({ msg, level });
43
+ },
44
+ },
45
+ getContextUsage: () => usage ?? { tokens: null, contextWindow: 0, percent: null },
46
+ compact: () => {
47
+ captured.push({ method: "compact", args: [] });
48
+ },
49
+ };
50
+
51
+ const piApi = {
52
+ on: (eventName: string, handler: (e: any, c: any) => any) => {
53
+ if (!handlers[eventName]) handlers[eventName] = [];
54
+ handlers[eventName].push(handler);
55
+ },
56
+ };
57
+
58
+ const emit = (eventName: string, event: any = {}) => {
59
+ const hs = handlers[eventName] ?? [];
60
+ for (const h of hs) h(event, ctx);
61
+ };
62
+
63
+ return { piApi, ctx, emit, captured, notifyCalls };
64
+ }
65
+
66
+ function setConfig(cfg: Record<string, unknown>) {
67
+ writeFileSync(CONFIG_PATH, JSON.stringify(cfg));
68
+ }
69
+
70
+ describe("proactiveThreshold: agent_end", () => {
71
+ afterEach(() => {
72
+ resetProactiveState();
73
+ if (existsSync(CONFIG_PATH)) unlinkSync(CONFIG_PATH);
74
+ });
75
+
76
+ test("triggers compact when context exceeds per-model threshold", () => {
77
+ setConfig({
78
+ debug: false,
79
+ overrideDefaultCompaction: true,
80
+ modelThresholds: {
81
+ "neuralwatt/GLM-5.1": { reserveTokens: 32768 },
82
+ },
83
+ });
84
+ const mock = createMockPi(
85
+ { id: "GLM-5.1", provider: "neuralwatt", contextWindow: 128000 },
86
+ { tokens: 110000, contextWindow: 128000, percent: 86 },
87
+ );
88
+ registerProactiveThresholdHook(mock.piApi);
89
+ mock.emit("agent_end", { type: "agent_end", messages: [] });
90
+ expect(mock.captured).toHaveLength(1);
91
+ expect(mock.captured[0].method).toBe("compact");
92
+ expect(mock.notifyCalls.length).toBeGreaterThanOrEqual(1);
93
+ expect(mock.notifyCalls[0].msg).toContain("auto");
94
+ });
95
+
96
+ test("does NOT trigger when context is below threshold", () => {
97
+ setConfig({
98
+ debug: false,
99
+ overrideDefaultCompaction: true,
100
+ modelThresholds: {
101
+ "neuralwatt/GLM-5.1": { reserveTokens: 32768 },
102
+ },
103
+ });
104
+ const mock = createMockPi(
105
+ { id: "GLM-5.1", provider: "neuralwatt", contextWindow: 128000 },
106
+ { tokens: 80000, contextWindow: 128000, percent: 63 },
107
+ );
108
+ registerProactiveThresholdHook(mock.piApi);
109
+ mock.emit("agent_end", { type: "agent_end", messages: [] });
110
+ expect(mock.captured).toHaveLength(0);
111
+ });
112
+
113
+ test("does NOT trigger when no modelThresholds configured", () => {
114
+ setConfig({ debug: false, overrideDefaultCompaction: true });
115
+ const mock = createMockPi(
116
+ { id: "GLM-5.1", provider: "neuralwatt", contextWindow: 128000 },
117
+ { tokens: 110000, contextWindow: 128000, percent: 86 },
118
+ );
119
+ registerProactiveThresholdHook(mock.piApi);
120
+ mock.emit("agent_end", { type: "agent_end", messages: [] });
121
+ expect(mock.captured).toHaveLength(0);
122
+ });
123
+
124
+ test("does NOT trigger when context usage tokens is null", () => {
125
+ setConfig({
126
+ debug: false,
127
+ overrideDefaultCompaction: true,
128
+ modelThresholds: {
129
+ "neuralwatt/GLM-5.1": { reserveTokens: 32768 },
130
+ },
131
+ });
132
+ const mock = createMockPi(
133
+ { id: "GLM-5.1", provider: "neuralwatt", contextWindow: 128000 },
134
+ { tokens: null, contextWindow: 128000, percent: null },
135
+ );
136
+ registerProactiveThresholdHook(mock.piApi);
137
+ mock.emit("agent_end", { type: "agent_end", messages: [] });
138
+ expect(mock.captured).toHaveLength(0);
139
+ });
140
+
141
+ test("does NOT trigger when contextWindow is 0", () => {
142
+ setConfig({
143
+ debug: false,
144
+ overrideDefaultCompaction: true,
145
+ modelThresholds: {
146
+ "neuralwatt/GLM-5.1": { reserveTokens: 32768 },
147
+ },
148
+ });
149
+ const mock = createMockPi(
150
+ { id: "GLM-5.1", provider: "neuralwatt", contextWindow: 0 },
151
+ { tokens: 50000, contextWindow: 0, percent: null },
152
+ );
153
+ registerProactiveThresholdHook(mock.piApi);
154
+ mock.emit("agent_end", { type: "agent_end", messages: [] });
155
+ expect(mock.captured).toHaveLength(0);
156
+ });
157
+ });
158
+
159
+ describe("proactiveThreshold: model_select", () => {
160
+ afterEach(() => {
161
+ resetProactiveState();
162
+ if (existsSync(CONFIG_PATH)) unlinkSync(CONFIG_PATH);
163
+ });
164
+
165
+ test("triggers compact on model switch when context exceeds new model's threshold", () => {
166
+ setConfig({
167
+ debug: false,
168
+ overrideDefaultCompaction: true,
169
+ modelThresholds: {
170
+ "neuralwatt/GLM-5.1": { reserveTokens: 32768 },
171
+ },
172
+ });
173
+ const mock = createMockPi(
174
+ { id: "GLM-5.1", provider: "neuralwatt", contextWindow: 128000 },
175
+ { tokens: 110000, contextWindow: 128000, percent: 86 },
176
+ );
177
+ registerProactiveThresholdHook(mock.piApi);
178
+ mock.emit("model_select", { type: "model_select" });
179
+ expect(mock.captured).toHaveLength(1);
180
+ expect(mock.notifyCalls[0].msg).toContain("model-switch");
181
+ });
182
+
183
+ test("does NOT trigger when context is below threshold after model switch", () => {
184
+ setConfig({
185
+ debug: false,
186
+ overrideDefaultCompaction: true,
187
+ modelThresholds: {
188
+ "neuralwatt/GLM-5.1": { reserveTokens: 32768 },
189
+ },
190
+ });
191
+ const mock = createMockPi(
192
+ { id: "GLM-5.1", provider: "neuralwatt", contextWindow: 128000 },
193
+ { tokens: 80000, contextWindow: 128000, percent: 63 },
194
+ );
195
+ registerProactiveThresholdHook(mock.piApi);
196
+ mock.emit("model_select", { type: "model_select" });
197
+ expect(mock.captured).toHaveLength(0);
198
+ });
199
+
200
+ test("uses globalThreshold when model does not match modelThresholds", () => {
201
+ setConfig({
202
+ debug: false,
203
+ overrideDefaultCompaction: true,
204
+ globalThreshold: { reserveTokens: 16384 },
205
+ });
206
+ const mock = createMockPi(
207
+ { id: "unknown-model", provider: "other", contextWindow: 128000 },
208
+ { tokens: 120000, contextWindow: 128000, percent: 94 },
209
+ );
210
+ registerProactiveThresholdHook(mock.piApi);
211
+ mock.emit("model_select", { type: "model_select" });
212
+ expect(mock.captured).toHaveLength(1);
213
+ });
214
+
215
+ test("uses globalThreshold with compactPercent", () => {
216
+ // compactPercent: 65 → reserve = 128000 * 0.35 = 44800 → threshold = 83200
217
+ setConfig({
218
+ debug: false,
219
+ overrideDefaultCompaction: true,
220
+ globalThreshold: { compactPercent: 65 },
221
+ });
222
+ const mock = createMockPi(
223
+ { id: "unknown-model", provider: "other", contextWindow: 128000 },
224
+ { tokens: 90000, contextWindow: 128000, percent: 70 },
225
+ );
226
+ registerProactiveThresholdHook(mock.piApi);
227
+ mock.emit("model_select", { type: "model_select" });
228
+ expect(mock.captured).toHaveLength(1);
229
+ });
230
+
231
+ test("uses compactPercent in modelThresholds", () => {
232
+ // compactPercent: 65 → reserve = 128000 * 0.35 = 44800 → threshold = 83200
233
+ setConfig({
234
+ debug: false,
235
+ overrideDefaultCompaction: true,
236
+ modelThresholds: {
237
+ "neuralwatt/GLM-5.1": { compactPercent: 65 },
238
+ },
239
+ });
240
+ const mock = createMockPi(
241
+ { id: "GLM-5.1", provider: "neuralwatt", contextWindow: 128000 },
242
+ { tokens: 90000, contextWindow: 128000, percent: 70 },
243
+ );
244
+ registerProactiveThresholdHook(mock.piApi);
245
+ mock.emit("agent_end", { type: "agent_end", messages: [] });
246
+ expect(mock.captured).toHaveLength(1);
247
+ });
248
+
249
+ test("does not trigger when context is below compactPercent threshold", () => {
250
+ // compactPercent: 65 → threshold = 83200
251
+ setConfig({
252
+ debug: false,
253
+ overrideDefaultCompaction: true,
254
+ modelThresholds: {
255
+ "neuralwatt/GLM-5.1": { compactPercent: 65 },
256
+ },
257
+ });
258
+ const mock = createMockPi(
259
+ { id: "GLM-5.1", provider: "neuralwatt", contextWindow: 128000 },
260
+ { tokens: 70000, contextWindow: 128000, percent: 55 },
261
+ );
262
+ registerProactiveThresholdHook(mock.piApi);
263
+ mock.emit("agent_end", { type: "agent_end", messages: [] });
264
+ expect(mock.captured).toHaveLength(0);
265
+ });
266
+
267
+ test("falls back to defaultThreshold when globalThreshold is not set", () => {
268
+ setConfig({
269
+ debug: false,
270
+ overrideDefaultCompaction: true,
271
+ defaultThreshold: { reserveTokens: 16384 },
272
+ });
273
+ const mock = createMockPi(
274
+ { id: "unknown-model", provider: "other", contextWindow: 128000 },
275
+ { tokens: 120000, contextWindow: 128000, percent: 94 },
276
+ );
277
+ registerProactiveThresholdHook(mock.piApi);
278
+ mock.emit("model_select", { type: "model_select" });
279
+ expect(mock.captured).toHaveLength(1);
280
+ });
281
+ });
282
+
283
+ describe("proactiveThreshold: cooldown", () => {
284
+ afterEach(() => {
285
+ resetProactiveState();
286
+ if (existsSync(CONFIG_PATH)) unlinkSync(CONFIG_PATH);
287
+ });
288
+
289
+ test("does NOT double-trigger within cooldown after compaction", () => {
290
+ setConfig({
291
+ debug: false,
292
+ overrideDefaultCompaction: true,
293
+ modelThresholds: {
294
+ "neuralwatt/GLM-5.1": { reserveTokens: 32768 },
295
+ },
296
+ });
297
+ const mock = createMockPi(
298
+ { id: "GLM-5.1", provider: "neuralwatt", contextWindow: 128000 },
299
+ { tokens: 110000, contextWindow: 128000, percent: 86 },
300
+ );
301
+ registerProactiveThresholdHook(mock.piApi);
302
+
303
+ // First trigger: agent_end causes compact
304
+ mock.emit("agent_end", { type: "agent_end", messages: [] });
305
+ expect(mock.captured).toHaveLength(1);
306
+
307
+ // session_compact fires (compaction completed) — sets cooldown
308
+ mock.emit("session_compact", { type: "session_compact", compactionEntry: {} });
309
+
310
+ // Second trigger: model_select within cooldown — blocked
311
+ mock.emit("model_select", { type: "model_select" });
312
+ expect(mock.captured).toHaveLength(1); // still 1, not 2
313
+ });
314
+
315
+ test("re-triggers after cooldown expires", async () => {
316
+ setConfig({
317
+ debug: false,
318
+ overrideDefaultCompaction: true,
319
+ modelThresholds: {
320
+ "neuralwatt/GLM-5.1": { reserveTokens: 32768 },
321
+ },
322
+ });
323
+ const mock = createMockPi(
324
+ { id: "GLM-5.1", provider: "neuralwatt", contextWindow: 128000 },
325
+ { tokens: 110000, contextWindow: 128000, percent: 86 },
326
+ );
327
+ registerProactiveThresholdHook(mock.piApi);
328
+
329
+ // First trigger
330
+ mock.emit("agent_end", { type: "agent_end", messages: [] });
331
+ expect(mock.captured).toHaveLength(1);
332
+
333
+ // session_compact sets cooldown
334
+ mock.emit("session_compact", { type: "session_compact", compactionEntry: {} });
335
+
336
+ // Wait for cooldown to expire (3s + buffer)
337
+ await new Promise(r => setTimeout(r, 3200));
338
+
339
+ // Second trigger should now work
340
+ mock.emit("agent_end", { type: "agent_end", messages: [] });
341
+ expect(mock.captured).toHaveLength(2);
342
+ }, 10000);
343
+ });
344
+
345
+ describe("proactiveThreshold: modelId matching", () => {
346
+ afterEach(() => {
347
+ resetProactiveState();
348
+ if (existsSync(CONFIG_PATH)) unlinkSync(CONFIG_PATH);
349
+ });
350
+
351
+ test("matches on provider/modelId", () => {
352
+ setConfig({
353
+ debug: false,
354
+ overrideDefaultCompaction: true,
355
+ modelThresholds: {
356
+ "neuralwatt/GLM-5.1": { reserveTokens: 32768 },
357
+ },
358
+ });
359
+ const mock = createMockPi(
360
+ { id: "GLM-5.1", provider: "neuralwatt", contextWindow: 128000 },
361
+ { tokens: 110000, contextWindow: 128000, percent: 86 },
362
+ );
363
+ registerProactiveThresholdHook(mock.piApi);
364
+ mock.emit("agent_end", { type: "agent_end", messages: [] });
365
+ expect(mock.captured).toHaveLength(1);
366
+ });
367
+
368
+ test("matches on modelId only when provider/modelId doesn't match", () => {
369
+ setConfig({
370
+ debug: false,
371
+ overrideDefaultCompaction: true,
372
+ modelThresholds: {
373
+ "GLM-5.1": { reserveTokens: 32768 },
374
+ },
375
+ });
376
+ const mock = createMockPi(
377
+ { id: "GLM-5.1", provider: "other-provider", contextWindow: 128000 },
378
+ { tokens: 110000, contextWindow: 128000, percent: 86 },
379
+ );
380
+ registerProactiveThresholdHook(mock.piApi);
381
+ mock.emit("agent_end", { type: "agent_end", messages: [] });
382
+ expect(mock.captured).toHaveLength(1);
383
+ });
384
+
385
+ test("does NOT match when no key matches", () => {
386
+ setConfig({
387
+ debug: false,
388
+ overrideDefaultCompaction: true,
389
+ modelThresholds: {
390
+ "neuralwatt/GLM-5.1": { reserveTokens: 32768 },
391
+ },
392
+ });
393
+ const mock = createMockPi(
394
+ { id: "Kimi-K2.6", provider: "neuralwatt", contextWindow: 128000 },
395
+ { tokens: 110000, contextWindow: 128000, percent: 86 },
396
+ );
397
+ registerProactiveThresholdHook(mock.piApi);
398
+ mock.emit("agent_end", { type: "agent_end", messages: [] });
399
+ expect(mock.captured).toHaveLength(0);
400
+ });
401
+ });
402
+
403
+ describe("proactiveThreshold: exact boundary", () => {
404
+ afterEach(() => {
405
+ resetProactiveState();
406
+ if (existsSync(CONFIG_PATH)) unlinkSync(CONFIG_PATH);
407
+ });
408
+
409
+ test("does NOT trigger when tokens exactly equal threshold", () => {
410
+ setConfig({
411
+ debug: false,
412
+ overrideDefaultCompaction: true,
413
+ modelThresholds: {
414
+ "neuralwatt/GLM-5.1": { reserveTokens: 32768 },
415
+ },
416
+ });
417
+ // 128000 - 32768 = 95232 → exactly at threshold, NOT exceeded
418
+ const mock = createMockPi(
419
+ { id: "GLM-5.1", provider: "neuralwatt", contextWindow: 128000 },
420
+ { tokens: 95232, contextWindow: 128000, percent: 74 },
421
+ );
422
+ registerProactiveThresholdHook(mock.piApi);
423
+ mock.emit("agent_end", { type: "agent_end", messages: [] });
424
+ expect(mock.captured).toHaveLength(0);
425
+ });
426
+
427
+ test("triggers when tokens exceed threshold by 1", () => {
428
+ setConfig({
429
+ debug: false,
430
+ overrideDefaultCompaction: true,
431
+ modelThresholds: {
432
+ "neuralwatt/GLM-5.1": { reserveTokens: 32768 },
433
+ },
434
+ });
435
+ // 128000 - 32768 = 95232 → just over
436
+ const mock = createMockPi(
437
+ { id: "GLM-5.1", provider: "neuralwatt", contextWindow: 128000 },
438
+ { tokens: 95233, contextWindow: 128000, percent: 74 },
439
+ );
440
+ registerProactiveThresholdHook(mock.piApi);
441
+ mock.emit("agent_end", { type: "agent_end", messages: [] });
442
+ expect(mock.captured).toHaveLength(1);
443
+ });
444
+ });
445
+
446
+ describe("proactiveThreshold: without model", () => {
447
+ afterEach(() => {
448
+ resetProactiveState();
449
+ if (existsSync(CONFIG_PATH)) unlinkSync(CONFIG_PATH);
450
+ });
451
+
452
+ test("does NOT trigger when model is undefined", () => {
453
+ setConfig({
454
+ debug: false,
455
+ overrideDefaultCompaction: true,
456
+ modelThresholds: {
457
+ "neuralwatt/GLM-5.1": { reserveTokens: 32768 },
458
+ },
459
+ });
460
+ const mock = createMockPi(undefined, { tokens: 110000, contextWindow: 128000, percent: 86 });
461
+ registerProactiveThresholdHook(mock.piApi);
462
+ mock.emit("agent_end", { type: "agent_end", messages: [] });
463
+ expect(mock.captured).toHaveLength(0);
464
+ });
465
+ });
466
+
467
+ describe("proactiveThreshold: works for pi-core compaction too", () => {
468
+ afterEach(() => {
469
+ resetProactiveState();
470
+ if (existsSync(CONFIG_PATH)) unlinkSync(CONFIG_PATH);
471
+ });
472
+
473
+ test("proactive trigger works even when overrideDefaultCompaction is false", () => {
474
+ setConfig({
475
+ debug: false,
476
+ overrideDefaultCompaction: false,
477
+ modelThresholds: {
478
+ "neuralwatt/GLM-5.1": { reserveTokens: 32768 },
479
+ },
480
+ });
481
+ const mock = createMockPi(
482
+ { id: "GLM-5.1", provider: "neuralwatt", contextWindow: 128000 },
483
+ { tokens: 110000, contextWindow: 128000, percent: 86 },
484
+ );
485
+ registerProactiveThresholdHook(mock.piApi);
486
+ mock.emit("agent_end", { type: "agent_end", messages: [] });
487
+ // Should still trigger compact — ctx.compact() will invoke pi-core's compaction
488
+ expect(mock.captured).toHaveLength(1);
489
+ });
490
+ });
@@ -0,0 +1,38 @@
1
+ import { beforeAll, describe, expect, it } from "bun:test";
2
+ import { buildCompactReport } from "../src/core/report";
3
+ import { prepareSessionSamples, readSourceStat, type SessionSample } from "./support/real-sessions";
4
+ import { loadSessionMessages } from "./support/load-session";
5
+
6
+ let samples: SessionSample[] = [];
7
+
8
+ beforeAll(async () => {
9
+ samples = await prepareSessionSamples(2);
10
+ });
11
+
12
+ describe("real session integration", () => {
13
+ it("compiles copied large sessions without mutating originals", async () => {
14
+ for (const sample of samples) {
15
+ const before = await readSourceStat(sample);
16
+ const loaded = loadSessionMessages(sample.copy);
17
+ const report = buildCompactReport({ messages: loaded.messages });
18
+ const after = await readSourceStat(sample);
19
+
20
+ expect(loaded.messageCount).toBeGreaterThan(0);
21
+ expect(loaded.skippedCount).toBeGreaterThanOrEqual(0);
22
+ expect(report.summary.length).toBeGreaterThan(0);
23
+ expect(report.summary).toContain("[");
24
+ expect(report.before.preview.length).toBeGreaterThan(0);
25
+ expect(report.after.summaryPreview.length).toBeGreaterThan(0);
26
+ expect(report.compression.charsBefore).toBeGreaterThan(0);
27
+ expect(report.recall.probes.length).toBeGreaterThan(0);
28
+ expect(after).toEqual(before);
29
+ }
30
+ });
31
+
32
+ it("uses read-only copied fixtures", () => {
33
+ for (const sample of samples) {
34
+ expect(sample.copy).not.toBe(sample.source);
35
+ expect(sample.copy.includes("pi-vcc-sessions-")).toBe(true);
36
+ }
37
+ });
38
+ });
@@ -0,0 +1,15 @@
1
+ import { describe, it, expect } from "bun:test";
2
+ import { invalidExpandIndices } from "../src/tools/recall";
3
+
4
+ describe("invalidExpandIndices", () => {
5
+ it("returns indices that are not in available lineage index set", () => {
6
+ const available = new Set([0, 2, 5]);
7
+ expect(invalidExpandIndices([0, 2], available)).toEqual([]);
8
+ expect(invalidExpandIndices([1, 2, 7], available)).toEqual([1, 7]);
9
+ });
10
+
11
+ it("rejects non-integer indices", () => {
12
+ const available = new Set([0, 1, 2]);
13
+ expect(invalidExpandIndices([1.5, 2], available)).toEqual([1.5]);
14
+ });
15
+ });
@@ -0,0 +1,32 @@
1
+ import { describe, it, expect } from "bun:test";
2
+ import { normalizeRecallScope, parseRecallScope } from "../src/core/recall-scope";
3
+
4
+ describe("normalizeRecallScope", () => {
5
+ it("defaults to active lineage", () => {
6
+ expect(normalizeRecallScope()).toBe("lineage");
7
+ expect(normalizeRecallScope("lineage")).toBe("lineage");
8
+ expect(normalizeRecallScope("unknown")).toBe("lineage");
9
+ expect(normalizeRecallScope(123)).toBe("lineage");
10
+ });
11
+
12
+ it("accepts all scope", () => {
13
+ expect(normalizeRecallScope("all")).toBe("all");
14
+ expect(normalizeRecallScope("ALL")).toBe("all");
15
+ });
16
+ });
17
+
18
+ describe("parseRecallScope", () => {
19
+ it("removes scope token from command text", () => {
20
+ expect(parseRecallScope("license scope:all page:2")).toEqual({
21
+ scope: "all",
22
+ text: "license page:2",
23
+ });
24
+ });
25
+
26
+ it("defaults to lineage when no scope token is present", () => {
27
+ expect(parseRecallScope("license page:2")).toEqual({
28
+ scope: "lineage",
29
+ text: "license page:2",
30
+ });
31
+ });
32
+ });