@pi-unipi/compactor 0.2.3 → 2.0.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.
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * ctx_stats tool — context savings dashboard
3
+ *
4
+ * Stats driven by compaction savings (DB-first) with runtime counter fallback.
3
5
  */
4
6
 
5
7
  import type { SessionDB } from "../session/db.js";
6
- import type { ContentStore } from "../store/index.js";
7
8
  import type { RuntimeCounters } from "../types.js";
8
9
 
9
10
  export interface CtxStatsResult {
@@ -11,20 +12,16 @@ export interface CtxStatsResult {
11
12
  compactions: number;
12
13
  tokensSaved: number;
13
14
  compressionRatio: string;
14
- indexedDocs: number;
15
- indexedChunks: number;
16
15
  sandboxRuns: number;
17
16
  searchQueries: number;
18
17
  }
19
18
 
20
19
  export async function ctxStats(
21
20
  sessionDB: SessionDB,
22
- contentStore: ContentStore,
23
21
  sessionId: string,
24
22
  counters?: RuntimeCounters,
25
23
  ): Promise<CtxStatsResult> {
26
24
  const sessionStats = sessionDB.getSessionStats(sessionId);
27
- const storeStats = await contentStore.getStats();
28
25
 
29
26
  // Compute tokensSaved: prefer in-memory counters (current session),
30
27
  // fall back to per-session DB stats, then all-time DB stats.
@@ -50,14 +47,50 @@ export async function ctxStats(
50
47
  compactions = allTime.allCompactions;
51
48
  }
52
49
 
50
+ // Compression ratio
51
+ let ratio = "N/A";
52
+ if (sessionStats) {
53
+ const before = (sessionStats as any).total_chars_before ?? 0;
54
+ const kept = (sessionStats as any).total_chars_kept ?? 0;
55
+ if (before > 0 && kept > 0) {
56
+ ratio = `${(before / kept).toFixed(1)}:1`;
57
+ }
58
+ }
59
+
53
60
  return {
54
61
  sessionEvents: sessionStats?.event_count ?? 0,
55
62
  compactions,
56
63
  tokensSaved,
57
- compressionRatio: "N/A",
58
- indexedDocs: storeStats.sources,
59
- indexedChunks: storeStats.chunks,
60
- sandboxRuns: counters?.sandboxRuns ?? 0,
61
- searchQueries: counters?.searchQueries ?? 0,
64
+ compressionRatio: ratio,
65
+ sandboxRuns: computeSandboxRuns(counters, sessionDB, sessionId),
66
+ searchQueries: computeSearchQueries(counters, sessionDB, sessionId),
62
67
  };
63
68
  }
69
+
70
+ /** Compute sandbox runs: prefer in-memory counter, fall back to DB. */
71
+ function computeSandboxRuns(counters: RuntimeCounters | undefined, sessionDB: SessionDB, sessionId: string): number {
72
+ let sandboxRuns = counters?.sandboxRuns ?? 0;
73
+ if (sandboxRuns === 0) {
74
+ const sessionStats = sessionDB.getSessionStats(sessionId);
75
+ sandboxRuns = (sessionStats as any)?.sandbox_runs ?? 0;
76
+ }
77
+ if (sandboxRuns === 0) {
78
+ const allTime = sessionDB.getAllTimeStats();
79
+ sandboxRuns = allTime.allSandboxRuns;
80
+ }
81
+ return sandboxRuns;
82
+ }
83
+
84
+ /** Compute search queries: prefer in-memory counter, fall back to DB. */
85
+ function computeSearchQueries(counters: RuntimeCounters | undefined, sessionDB: SessionDB, sessionId: string): number {
86
+ let searchQueries = counters?.searchQueries ?? 0;
87
+ if (searchQueries === 0) {
88
+ const sessionStats = sessionDB.getSessionStats(sessionId);
89
+ searchQueries = (sessionStats as any)?.search_queries ?? 0;
90
+ }
91
+ if (searchQueries === 0) {
92
+ const allTime = sessionDB.getAllTimeStats();
93
+ searchQueries = allTime.allSearchQueries;
94
+ }
95
+ return searchQueries;
96
+ }
@@ -4,30 +4,26 @@
4
4
  * Each tool is registered via pi.registerTool() with proper TypeBox schemas
5
5
  * so the LLM can discover and invoke them.
6
6
  *
7
- * New naming convention (v0.2.0):
7
+ * Tool names:
8
8
  * compact, session_recall, sandbox, sandbox_file, sandbox_batch,
9
- * content_index, content_search, content_fetch, compactor_stats, compactor_doctor
9
+ * compactor_stats, compactor_doctor, context_budget
10
10
  *
11
- * Old names kept as deprecated aliases for backward compatibility:
11
+ * Deprecated aliases for backward compatibility:
12
12
  * vcc_recall, ctx_execute, ctx_execute_file, ctx_batch_execute,
13
- * ctx_index, ctx_search, ctx_fetch_and_index, ctx_stats, ctx_doctor
13
+ * ctx_stats, ctx_doctor
14
14
  */
15
15
 
16
- import { Type, type Static } from "@sinclair/typebox";
16
+ import { Type } from "@sinclair/typebox";
17
17
  import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
18
18
  import { compactTool } from "./compact.js";
19
19
  import { vccRecall, type RecallInput } from "./vcc-recall.js";
20
20
  import { ctxExecute, type CtxExecuteInput } from "./ctx-execute.js";
21
21
  import { ctxExecuteFile, type CtxExecuteFileInput } from "./ctx-execute-file.js";
22
22
  import { ctxBatchExecute, type BatchItem } from "./ctx-batch-execute.js";
23
- import { ctxIndex, type CtxIndexInput } from "./ctx-index.js";
24
- import { ctxSearch, type CtxSearchInput } from "./ctx-search.js";
25
- import { ctxFetchAndIndex, type CtxFetchAndIndexInput } from "./ctx-fetch-and-index.js";
26
23
  import { ctxStats, type CtxStatsResult } from "./ctx-stats.js";
27
24
  import { ctxDoctor, type DoctorResult } from "./ctx-doctor.js";
28
25
  import { contextBudgetTool } from "./context-budget.js";
29
26
  import type { SessionDB } from "../session/db.js";
30
- import type { ContentStore } from "../store/index.js";
31
27
  import type { NormalizedBlock, RuntimeCounters } from "../types.js";
32
28
 
33
29
  // --- TypeBox Schemas for each tool ---
@@ -81,40 +77,11 @@ const SandboxBatchParams = Type.Object({
81
77
  code: Type.String(),
82
78
  timeout: Type.Optional(Type.Number()),
83
79
  }),
84
- Type.Object({
85
- type: Type.Literal("search"),
86
- query: Type.String(),
87
- limit: Type.Optional(Type.Number()),
88
- }),
89
80
  ]),
90
- { description: "Array of execute or search commands to run atomically" },
81
+ { description: "Array of execute commands to run atomically" },
91
82
  ),
92
83
  });
93
84
 
94
- const ContentIndexParams = Type.Object({
95
- label: Type.String({ description: "Label for the indexed content" }),
96
- content: Type.Optional(Type.String({ description: "Content to index (or use filePath)" })),
97
- filePath: Type.Optional(Type.String({ description: "Path to file to index" })),
98
- contentType: Type.Optional(Type.Union([
99
- Type.Literal("markdown"),
100
- Type.Literal("json"),
101
- Type.Literal("plain"),
102
- ], { description: "Content type for chunking strategy" })),
103
- chunkSize: Type.Optional(Type.Number({ description: "Chunk size in characters", minimum: 100 })),
104
- });
105
-
106
- const ContentSearchParams = Type.Object({
107
- query: Type.String({ description: "Search query against indexed content" }),
108
- limit: Type.Optional(Type.Number({ description: "Max results (default 10)", minimum: 1 })),
109
- offset: Type.Optional(Type.Number({ description: "Pagination offset", minimum: 0 })),
110
- });
111
-
112
- const ContentFetchParams = Type.Object({
113
- url: Type.String({ description: "URL to fetch, convert to markdown, and index" }),
114
- label: Type.Optional(Type.String({ description: "Label for the indexed content" })),
115
- chunkSize: Type.Optional(Type.Number({ description: "Chunk size in characters", minimum: 100 })),
116
- });
117
-
118
85
  const StatsParams = Type.Object({});
119
86
 
120
87
  const DoctorParams = Type.Object({});
@@ -147,9 +114,6 @@ const VccRecallParams = RecallParams;
147
114
  const CtxExecuteParams = SandboxParams;
148
115
  const CtxExecuteFileParams = SandboxFileParams;
149
116
  const CtxBatchExecuteParams = SandboxBatchParams;
150
- const CtxIndexParams = ContentIndexParams;
151
- const CtxSearchParams = ContentSearchParams;
152
- const CtxFetchAndIndexParams = ContentFetchParams;
153
117
  const CtxStatsParams = StatsParams;
154
118
  const CtxDoctorParams = DoctorParams;
155
119
 
@@ -157,7 +121,6 @@ const CtxDoctorParams = DoctorParams;
157
121
 
158
122
  export interface CompactorToolDeps {
159
123
  sessionDB: SessionDB;
160
- contentStore: ContentStore | null;
161
124
  getSessionId: () => string;
162
125
  getBlocks: () => NormalizedBlock[];
163
126
  getCounters?: () => RuntimeCounters;
@@ -174,7 +137,7 @@ export function registerCompactorTools(pi: ExtensionAPI, deps: CompactorToolDeps
174
137
  label: "Compact",
175
138
  description: "Trigger manual context compaction. Reduces session history while preserving continuity. Use dryRun:true to preview without compacting.",
176
139
  parameters: CompactParams,
177
- async execute(_toolCallId: string, params: any): Promise<any> {
140
+ async execute(_toolCallId: string, params: any): Promise<import("@mariozechner/pi-coding-agent").AgentToolResult<unknown>> {
178
141
  if (params.dryRun) {
179
142
  const blocks = deps.getBlocks();
180
143
  const totalMessages = blocks.length;
@@ -194,7 +157,7 @@ export function registerCompactorTools(pi: ExtensionAPI, deps: CompactorToolDeps
194
157
  } as any));
195
158
 
196
159
  // 2. session_recall (new) / vcc_recall (deprecated) — search session history
197
- const recallExec = async (_toolCallId: string, params: any): Promise<any> => {
160
+ const recallExec = async (_toolCallId: string, params: any): Promise<import("@mariozechner/pi-coding-agent").AgentToolResult<unknown>> => {
198
161
  const c = deps.getCounters?.();
199
162
  if (c) { c.recallQueries++; }
200
163
  const blocks = deps.getBlocks();
@@ -222,10 +185,11 @@ export function registerCompactorTools(pi: ExtensionAPI, deps: CompactorToolDeps
222
185
  pi.registerTool({ name: "vcc_recall", label: "Session Recall", description: "Search session history using BM25 or regex. (DEPRECATED: use session_recall instead)", parameters: VccRecallParams, async execute(tcId: string, p: any) { deprecationLog("vcc_recall", "session_recall"); return recallExec(tcId, p); } } as any);
223
186
 
224
187
  // 3. sandbox (new) / ctx_execute (deprecated) — run code in sandbox
225
- const sandboxExec = async (_toolCallId: string, params: any): Promise<any> => {
188
+ const sandboxExec = async (_toolCallId: string, params: any): Promise<import("@mariozechner/pi-coding-agent").AgentToolResult<unknown>> => {
226
189
  try {
227
190
  const c = deps.getCounters?.();
228
191
  if (c) { c.sandboxRuns++; }
192
+ deps.sessionDB.incrementSandboxRuns(deps.getSessionId());
229
193
  const result = await ctxExecute(params as CtxExecuteInput);
230
194
  const parts: string[] = [];
231
195
  if (result.stdout) parts.push(result.stdout);
@@ -241,10 +205,11 @@ export function registerCompactorTools(pi: ExtensionAPI, deps: CompactorToolDeps
241
205
  pi.registerTool({ name: "ctx_execute", label: "Sandbox", description: "Run code in sandbox. (DEPRECATED: use sandbox instead)", parameters: CtxExecuteParams, async execute(tcId: string, p: any) { deprecationLog("ctx_execute", "sandbox"); return sandboxExec(tcId, p); } } as any);
242
206
 
243
207
  // 4. sandbox_file (new) / ctx_execute_file (deprecated) — execute file
244
- const sandboxFileExec = async (_toolCallId: string, params: any): Promise<any> => {
208
+ const sandboxFileExec = async (_toolCallId: string, params: any): Promise<import("@mariozechner/pi-coding-agent").AgentToolResult<unknown>> => {
245
209
  try {
246
210
  const c = deps.getCounters?.();
247
211
  if (c) { c.sandboxRuns++; }
212
+ deps.sessionDB.incrementSandboxRuns(deps.getSessionId());
248
213
  const result = await ctxExecuteFile(params as CtxExecuteFileInput);
249
214
  const parts: string[] = [];
250
215
  if (result.stdout) parts.push(result.stdout);
@@ -258,91 +223,34 @@ export function registerCompactorTools(pi: ExtensionAPI, deps: CompactorToolDeps
258
223
  pi.registerTool({ name: "sandbox_file", label: "Sandbox File", description: "Execute a file in the sandbox. File content is injected as FILE_CONTENT variable.", parameters: SandboxFileParams, execute: sandboxFileExec } as any);
259
224
  pi.registerTool({ name: "ctx_execute_file", label: "Sandbox File", description: "Execute file in sandbox. (DEPRECATED: use sandbox_file instead)", parameters: CtxExecuteFileParams, async execute(tcId: string, p: any) { deprecationLog("ctx_execute_file", "sandbox_file"); return sandboxFileExec(tcId, p); } } as any);
260
225
 
261
- // 5. sandbox_batch (new) / ctx_batch_execute (deprecated) — atomic batch
262
- const sandboxBatchExec = async (_toolCallId: string, params: any): Promise<any> => {
226
+ // 5. sandbox_batch (new) / ctx_batch_execute (deprecated) — atomic batch (execute only)
227
+ const sandboxBatchExec = async (_toolCallId: string, params: any): Promise<import("@mariozechner/pi-coding-agent").AgentToolResult<unknown>> => {
263
228
  try {
264
229
  const c = deps.getCounters?.();
265
- if (c) { c.sandboxRuns++; c.searchQueries++; }
266
- const result = await ctxBatchExecute(deps.contentStore!, params.items as BatchItem[]);
230
+ if (c) { c.sandboxRuns++; }
231
+ deps.sessionDB.incrementSandboxRuns(deps.getSessionId());
232
+ const result = await ctxBatchExecute(params.items as BatchItem[]);
267
233
  const summaries = result.results.map((r, i) => {
268
- if (r.type === "execute") {
269
- const s = r.result.stdout?.slice(0, 200) || "(no output)";
270
- return `[${i}] execute → ${r.result.exitCode === 0 ? "ok" : "fail"}: ${s}`;
271
- }
272
- return `[${i}] search → ${r.results.length} results`;
234
+ const s = r.result.stdout?.slice(0, 200) || "(no output)";
235
+ return `[${i}] execute → ${r.result.exitCode === 0 ? "ok" : "fail"}: ${s}`;
273
236
  });
274
237
  return textResult(`Batch results (${result.results.length} items):\n${summaries.join("\n")}`, result as unknown as Record<string, unknown>);
275
238
  } catch (err) {
276
239
  return textResult(`Batch error: ${err}`, { error: true });
277
240
  }
278
241
  };
279
- pi.registerTool({ name: "sandbox_batch", label: "Sandbox Batch", description: "Run multiple code executions and searches atomically as a batch.", parameters: SandboxBatchParams, execute: sandboxBatchExec } as any);
242
+ pi.registerTool({ name: "sandbox_batch", label: "Sandbox Batch", description: "Run multiple code executions atomically as a batch.", parameters: SandboxBatchParams, execute: sandboxBatchExec } as any);
280
243
  pi.registerTool({ name: "ctx_batch_execute", label: "Sandbox Batch", description: "Run batch operations. (DEPRECATED: use sandbox_batch instead)", parameters: CtxBatchExecuteParams, async execute(tcId: string, p: any) { deprecationLog("ctx_batch_execute", "sandbox_batch"); return sandboxBatchExec(tcId, p); } } as any);
281
244
 
282
- // 6. content_index (new) / ctx_index (deprecated) — index content into FTS5
283
- const contentIndexExec = async (_toolCallId: string, params: any): Promise<any> => {
284
- try {
285
- const result = await ctxIndex(deps.contentStore!, params as CtxIndexInput);
286
- return textResult(
287
- `Indexed "${result.label}": ${result.totalChunks} chunks (${result.codeChunks} code)`,
288
- result as unknown as Record<string, unknown>,
289
- );
290
- } catch (err) {
291
- return textResult(`Index error: ${err}`, { error: true });
292
- }
293
- };
294
- pi.registerTool({ name: "content_index", label: "Content Index", description: "Chunk content or a file and index into FTS5 for fast search.", parameters: ContentIndexParams, execute: contentIndexExec } as any);
295
- pi.registerTool({ name: "ctx_index", label: "Content Index", description: "Index content into FTS5. (DEPRECATED: use content_index instead)", parameters: CtxIndexParams, async execute(tcId: string, p: any) { deprecationLog("ctx_index", "content_index"); return contentIndexExec(tcId, p); } } as any);
296
-
297
- // 7. content_search (new) / ctx_search (deprecated) — query FTS5 content store
298
- const contentSearchExec = async (_toolCallId: string, params: any): Promise<any> => {
299
- try {
300
- const c = deps.getCounters?.();
301
- if (c) { c.searchQueries++; }
302
- const results = await ctxSearch(deps.contentStore!, params as CtxSearchInput);
303
- if (results.length === 0) {
304
- return textResult(`No results for "${params.query}".`);
305
- }
306
- const lines = results.map(
307
- (r, i) =>
308
- `[${i + 1}] ${r.title} (rank: ${r.rank.toFixed(3)})\n${r.content.slice(0, 300)}`,
309
- );
310
- return textResult(
311
- `Found ${results.length} results:\n\n${lines.join("\n\n")}`,
312
- { results } as unknown as Record<string, unknown>,
313
- );
314
- } catch (err) {
315
- return textResult(`Search error: ${err}`, { error: true });
316
- }
317
- };
318
- pi.registerTool({ name: "content_search", label: "Content Search", description: "Search indexed content using FTS5 full-text search.", parameters: ContentSearchParams, execute: contentSearchExec } as any);
319
- pi.registerTool({ name: "ctx_search", label: "Content Search", description: "Search indexed content. (DEPRECATED: use content_search instead)", parameters: CtxSearchParams, async execute(tcId: string, p: any) { deprecationLog("ctx_search", "content_search"); return contentSearchExec(tcId, p); } } as any);
320
-
321
- // 8. content_fetch (new) / ctx_fetch_and_index (deprecated) — fetch URL
322
- const contentFetchExec = async (_toolCallId: string, params: any): Promise<any> => {
323
- try {
324
- const result = await ctxFetchAndIndex(deps.contentStore!, params as CtxFetchAndIndexInput);
325
- return textResult(
326
- `Fetched and indexed "${result.label}": ${result.totalChunks} chunks`,
327
- result as unknown as Record<string, unknown>,
328
- );
329
- } catch (err) {
330
- return textResult(`Fetch error: ${err}`, { error: true });
331
- }
332
- };
333
- pi.registerTool({ name: "content_fetch", label: "Content Fetch", description: "Fetch a URL, convert to markdown, and index into FTS5 content store.", parameters: ContentFetchParams, execute: contentFetchExec } as any);
334
- pi.registerTool({ name: "ctx_fetch_and_index", label: "Content Fetch", description: "Fetch URL and index. (DEPRECATED: use content_fetch instead)", parameters: CtxFetchAndIndexParams, async execute(tcId: string, p: any) { deprecationLog("ctx_fetch_and_index", "content_fetch"); return contentFetchExec(tcId, p); } } as any);
335
-
336
- // 9. compactor_stats (new) / ctx_stats (deprecated) — context savings dashboard
337
- const statsExec = async (): Promise<any> => {
245
+ // 6. compactor_stats (new) / ctx_stats (deprecated) — context savings dashboard
246
+ const statsExec = async (): Promise<import("@mariozechner/pi-coding-agent").AgentToolResult<unknown>> => {
338
247
  try {
339
- const result = await ctxStats(deps.sessionDB, deps.contentStore!, deps.getSessionId(), deps.getCounters?.());
248
+ const result = await ctxStats(deps.sessionDB, deps.getSessionId(), deps.getCounters?.());
340
249
  const lines = [
341
250
  `📊 Compactor Stats`,
342
251
  `Session events: ${result.sessionEvents}`,
343
252
  `Compactions: ${result.compactions}`,
344
253
  `Tokens saved: ${result.tokensSaved}`,
345
- `Indexed docs: ${result.indexedDocs} (${result.indexedChunks} chunks)`,
346
254
  `Sandbox runs: ${result.sandboxRuns}`,
347
255
  `Search queries: ${result.searchQueries}`,
348
256
  ];
@@ -351,13 +259,13 @@ export function registerCompactorTools(pi: ExtensionAPI, deps: CompactorToolDeps
351
259
  return textResult(`Stats error: ${err}`, { error: true });
352
260
  }
353
261
  };
354
- pi.registerTool({ name: "compactor_stats", label: "Compactor Stats", description: "Show context savings dashboard — session events, compactions, indexed content.", parameters: StatsParams, execute: statsExec } as any);
262
+ pi.registerTool({ name: "compactor_stats", label: "Compactor Stats", description: "Show context savings dashboard — session events, compactions, tool usage.", parameters: StatsParams, execute: statsExec } as any);
355
263
  pi.registerTool({ name: "ctx_stats", label: "Compactor Stats", description: "Show stats dashboard. (DEPRECATED: use compactor_stats instead)", parameters: CtxStatsParams, async execute() { deprecationLog("ctx_stats", "compactor_stats"); return statsExec(); } } as any);
356
264
 
357
- // 10. compactor_doctor (new) / ctx_doctor (deprecated) — diagnostics checklist
358
- const doctorExec = async (): Promise<any> => {
265
+ // 7. compactor_doctor (new) / ctx_doctor (deprecated) — diagnostics checklist
266
+ const doctorExec = async (): Promise<import("@mariozechner/pi-coding-agent").AgentToolResult<unknown>> => {
359
267
  try {
360
- const result = await ctxDoctor(deps.sessionDB, deps.contentStore!);
268
+ const result = await ctxDoctor(deps.sessionDB);
361
269
  const icon = (s: string) => (s === "pass" ? "✅" : s === "warn" ? "⚠️" : "❌");
362
270
  const lines = [
363
271
  result.healthy ? "🩺 All checks passed" : "🩺 Issues found",
@@ -369,16 +277,16 @@ export function registerCompactorTools(pi: ExtensionAPI, deps: CompactorToolDeps
369
277
  return textResult(`Doctor error: ${err}`, { error: true });
370
278
  }
371
279
  };
372
- pi.registerTool({ name: "compactor_doctor", label: "Compactor Doctor", description: "Run diagnostics checklist — validate config, DB, FTS5, runtimes.", parameters: DoctorParams, execute: doctorExec } as any);
280
+ pi.registerTool({ name: "compactor_doctor", label: "Compactor Doctor", description: "Run diagnostics checklist — validate config, DB, runtimes.", parameters: DoctorParams, execute: doctorExec } as any);
373
281
  pi.registerTool({ name: "ctx_doctor", label: "Compactor Doctor", description: "Run diagnostics. (DEPRECATED: use compactor_doctor instead)", parameters: CtxDoctorParams, async execute() { deprecationLog("ctx_doctor", "compactor_doctor"); return doctorExec(); } } as any);
374
282
 
375
- // 11. context_budget — estimate remaining context window
283
+ // 8. context_budget — estimate remaining context window
376
284
  pi.registerTool(({
377
285
  name: "context_budget",
378
286
  label: "Context Budget",
379
287
  description: "Estimate remaining context window (% full, tokens left) and get advice on whether to compact.",
380
288
  parameters: Type.Object({}),
381
- async execute(): Promise<any> {
289
+ async execute(): Promise<import("@mariozechner/pi-coding-agent").AgentToolResult<unknown>> {
382
290
  const blocks = deps.getBlocks();
383
291
  const estimatedTokens = blocks.reduce((sum, b) => {
384
292
  const text = b.kind === "tool_call"
@@ -7,8 +7,9 @@
7
7
  * preset preview, per-project override.
8
8
  */
9
9
 
10
- import type { Component } from "@mariozechner/pi-tui";
10
+ import type { Component, TUI } from "@mariozechner/pi-tui";
11
11
  import { truncateToWidth, visibleWidth, SettingsList, type SettingItem, type SettingsListTheme } from "@mariozechner/pi-tui";
12
+ import type { Theme, KeybindingsManager } from "@mariozechner/pi-coding-agent";
12
13
  import { loadConfig, saveConfig, projectConfigPath } from "../config/manager.js";
13
14
  import { applyPreset, detectPreset } from "../config/presets.js";
14
15
  import type { CompactorPreset } from "../types.js";
@@ -126,16 +127,6 @@ const STRATEGIES: StrategyDef[] = [
126
127
  getMode: (c) => c.sessionContinuity.mode,
127
128
  setMode: (c, v) => (c.sessionContinuity.mode = v as any),
128
129
  },
129
- {
130
- key: "fts5Index",
131
- label: "FTS5 Index",
132
- description: "Full-text search index",
133
- modes: ["auto", "manual", "off"],
134
- getEnabled: (c) => c.fts5Index.enabled,
135
- setEnabled: (c, v) => (c.fts5Index.enabled = v),
136
- getMode: (c) => c.fts5Index.mode,
137
- setMode: (c, v) => (c.fts5Index.mode = v as any),
138
- },
139
130
  {
140
131
  key: "sandboxExecution",
141
132
  label: "Sandbox Execution",
@@ -164,27 +155,27 @@ const PIPELINE_ITEMS: PipelineDef[] = [
164
155
  { key: "mmapPragma", label: "MMap Pragma", description: "Use mmap for SQLite I/O", group: "On Compaction", getValue: (c) => c.pipeline.mmapPragma, setValue: (c, v) => (c.pipeline.mmapPragma = v) },
165
156
  { key: "proximityReranking", label: "Proximity Reranking", description: "Rerank search results by proximity", group: "On Search", getValue: (c) => c.pipeline.proximityReranking, setValue: (c, v) => (c.pipeline.proximityReranking = v) },
166
157
  { key: "timelineSort", label: "Timeline Sort", description: "Sort session events chronologically", group: "On Search", getValue: (c) => c.pipeline.timelineSort, setValue: (c, v) => (c.pipeline.timelineSort = v) },
167
- { key: "progressiveThrottling", label: "Progressive Throttling", description: "Slow down indexing for large projects", group: "On Index", getValue: (c) => c.pipeline.progressiveThrottling, setValue: (c, v) => (c.pipeline.progressiveThrottling = v) },
158
+ { key: "progressiveThrottling", label: "Progressive Throttling", description: "Slow down processing for large projects", group: "On Compaction", getValue: (c) => c.pipeline.progressiveThrottling, setValue: (c, v) => (c.pipeline.progressiveThrottling = v) },
168
159
  ];
169
160
 
170
161
  const PRESETS: CompactorPreset[] = ["precise", "balanced", "thorough", "lean"];
171
162
 
172
163
  const PRESET_DESCRIPTIONS: Record<string, { summary: string; detail: string }> = {
173
164
  precise: {
174
- summary: "Code-heavy, minimal waste — compaction: full, FTS5: manual, pipeline: 2/6 on",
175
- detail: "Max token savings. Compaction: full. Display: minimal.\nFTS5: manual. Sandbox: safe-only. Pipeline: ttlCache+mmap on.",
165
+ summary: "Code-heavy, minimal waste — compaction: full, sandbox: safe-only",
166
+ detail: "Max token savings. Compaction: full. Display: minimal.\nSandbox: safe-only. Pipeline: ttlCache+mmap on.",
176
167
  },
177
168
  balanced: {
178
- summary: "Daily use (default) — all strategies moderate, pipeline: all on",
179
- detail: "Moderate all strategies. Display: balanced.\nFTS5: auto. Sandbox: all. Pipeline: all 6 on.",
169
+ summary: "Daily use (default) — all strategies moderate",
170
+ detail: "Moderate all strategies. Display: balanced.\nSandbox: all. Pipeline: all 6 on.",
180
171
  },
181
172
  thorough: {
182
- summary: "Debug/audit — everything on, full transcript, pipeline: all on",
183
- detail: "Everything enabled. Display: verbose.\nFTS5: auto. Sandbox: all. Pipeline: all 6 on.",
173
+ summary: "Debug/audit — everything on, full transcript",
174
+ detail: "Everything enabled. Display: verbose.\nSandbox: all. Pipeline: all 6 on.",
184
175
  },
185
176
  lean: {
186
- summary: "Quick fixes, short sessions — compaction only, pipeline: all off",
187
- detail: "Compaction only. Display: opencode.\nFTS5: off. Sandbox: off. Pipeline: all 6 off.",
177
+ summary: "Quick fixes, short sessions — compaction only",
178
+ detail: "Compaction only. Display: opencode.\nSandbox: off. Pipeline: all 6 off.",
188
179
  },
189
180
  };
190
181
 
@@ -502,7 +493,7 @@ export class CompactorSettingsOverlay implements Component {
502
493
  * Factory function for ctx.ui.custom() integration.
503
494
  */
504
495
  export function renderSettingsOverlay(cwd?: string) {
505
- return (_tui: any, _theme: any, _kb: any, done: (result: any) => void) => {
496
+ return (_tui: TUI, _theme: Theme, _kb: KeybindingsManager, done: (result: CompactorSettingsOverlay) => void) => {
506
497
  const overlay = new CompactorSettingsOverlay({ cwd });
507
498
  overlay.onClose = () => done(overlay);
508
499
 
package/src/types.ts CHANGED
@@ -62,7 +62,11 @@ export interface CompileInput {
62
62
  export interface CompactionStats {
63
63
  summarized: number;
64
64
  kept: number;
65
- keptTokensEst: number;
65
+ totalMessages: number;
66
+ /** Actual token count from Pi's preparation */
67
+ tokensBefore: number;
68
+ /** Estimated tokens after compaction (proportional from kept/total chars) */
69
+ tokensAfterEst: number;
66
70
  }
67
71
 
68
72
  export type OwnCutCancelReason =
@@ -71,7 +75,7 @@ export type OwnCutCancelReason =
71
75
  | "no_user_message";
72
76
 
73
77
  export type OwnCutResult =
74
- | { ok: true; messages: any[]; firstKeptEntryId: string; compactAll: boolean }
78
+ | { ok: true; messages: Message[]; firstKeptEntryId: string; compactAll: boolean }
75
79
  | { ok: false; reason: OwnCutCancelReason };
76
80
 
77
81
  // ─────────────────────────────────────────────────────────
@@ -193,32 +197,10 @@ export type Language =
193
197
  | "elixir";
194
198
 
195
199
  // ─────────────────────────────────────────────────────────
196
- // Content store (from context-mode)
200
+ // Content store — REMOVED (moved to @pi-unipi/cocoindex)
201
+ // SearchResult, IndexResult, StoreStats types no longer needed.
197
202
  // ─────────────────────────────────────────────────────────
198
203
 
199
- export interface IndexResult {
200
- sourceId: number;
201
- label: string;
202
- totalChunks: number;
203
- codeChunks: number;
204
- }
205
-
206
- export interface SearchResult {
207
- title: string;
208
- content: string;
209
- source: string;
210
- rank: number;
211
- contentType: "code" | "prose";
212
- matchLayer?: "porter" | "trigram" | "fuzzy" | "rrf" | "rrf-fuzzy";
213
- highlighted?: string;
214
- }
215
-
216
- export interface StoreStats {
217
- sources: number;
218
- chunks: number;
219
- codeChunks: number;
220
- }
221
-
222
204
  // ─────────────────────────────────────────────────────────
223
205
  // Security (from context-mode)
224
206
  // ─────────────────────────────────────────────────────────
@@ -1,126 +0,0 @@
1
- /**
2
- * Content chunking — markdown by headings, JSON recursive, plain text
3
- */
4
-
5
- export interface Chunk {
6
- title: string;
7
- content: string;
8
- hasCode: boolean;
9
- }
10
-
11
- export function chunkMarkdown(text: string, maxChunkSize: number = 4096): Chunk[] {
12
- const chunks: Chunk[] = [];
13
- const lines = text.split("\n");
14
- let currentTitle = "";
15
- let currentLines: string[] = [];
16
- let inCodeBlock = false;
17
-
18
- const flush = () => {
19
- if (currentLines.length === 0) return;
20
- const content = currentLines.join("\n");
21
- chunks.push({
22
- title: currentTitle || "Untitled",
23
- content,
24
- hasCode: content.includes("```"),
25
- });
26
- currentLines = [];
27
- };
28
-
29
- for (const line of lines) {
30
- if (line.startsWith("```")) inCodeBlock = !inCodeBlock;
31
-
32
- if (!inCodeBlock && /^#{1,6}\s/.test(line)) {
33
- flush();
34
- currentTitle = line.replace(/^#{1,6}\s*/, "").trim();
35
- continue;
36
- }
37
-
38
- currentLines.push(line);
39
-
40
- if (currentLines.join("\n").length > maxChunkSize && !inCodeBlock) {
41
- flush();
42
- currentTitle = currentTitle ? `${currentTitle} (continued)` : "Continued";
43
- }
44
- }
45
-
46
- flush();
47
- return chunks;
48
- }
49
-
50
- export function chunkJSON(text: string, maxChunkSize: number = 4096): Chunk[] {
51
- try {
52
- const obj = JSON.parse(text);
53
- return chunkObject(obj, "root", maxChunkSize);
54
- } catch {
55
- return chunkPlainText(text, maxChunkSize);
56
- }
57
- }
58
-
59
- function chunkObject(obj: any, path: string, maxChunkSize: number): Chunk[] {
60
- const chunks: Chunk[] = [];
61
-
62
- if (typeof obj !== "object" || obj === null) {
63
- const content = String(obj);
64
- if (content.length > maxChunkSize) {
65
- for (let i = 0; i < content.length; i += maxChunkSize) {
66
- chunks.push({
67
- title: `${path} [${i}-${i + maxChunkSize}]`,
68
- content: content.slice(i, i + maxChunkSize),
69
- hasCode: false,
70
- });
71
- }
72
- } else {
73
- chunks.push({ title: path, content, hasCode: false });
74
- }
75
- return chunks;
76
- }
77
-
78
- if (Array.isArray(obj)) {
79
- for (let i = 0; i < obj.length; i++) {
80
- const sub = chunkObject(obj[i], `${path}[${i}]`, maxChunkSize);
81
- chunks.push(...sub);
82
- }
83
- return chunks;
84
- }
85
-
86
- for (const [key, val] of Object.entries(obj)) {
87
- const sub = chunkObject(val, `${path}.${key}`, maxChunkSize);
88
- chunks.push(...sub);
89
- }
90
-
91
- return chunks;
92
- }
93
-
94
- export function chunkPlainText(text: string, maxChunkSize: number = 4096): Chunk[] {
95
- const chunks: Chunk[] = [];
96
- const paragraphs = text.split(/\n\s*\n/);
97
- let current: string[] = [];
98
-
99
- const flush = () => {
100
- if (current.length === 0) return;
101
- chunks.push({
102
- title: "Text",
103
- content: current.join("\n\n"),
104
- hasCode: false,
105
- });
106
- current = [];
107
- };
108
-
109
- for (const para of paragraphs) {
110
- if ((current.join("\n\n").length + para.length) > maxChunkSize) {
111
- flush();
112
- }
113
- current.push(para);
114
- }
115
-
116
- flush();
117
- return chunks;
118
- }
119
-
120
- export function autoChunk(text: string, contentType: "markdown" | "json" | "plain", maxChunkSize?: number): Chunk[] {
121
- switch (contentType) {
122
- case "markdown": return chunkMarkdown(text, maxChunkSize);
123
- case "json": return chunkJSON(text, maxChunkSize);
124
- default: return chunkPlainText(text, maxChunkSize);
125
- }
126
- }