@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.
- package/README.md +3 -1
- package/package.json +2 -2
- package/src/commands/index.ts +79 -169
- package/src/compaction/content.ts +2 -2
- package/src/compaction/cut.ts +10 -6
- package/src/compaction/hooks.ts +82 -52
- package/src/compaction/recall-scope.ts +1 -1
- package/src/config/manager.ts +0 -0
- package/src/config/presets.ts +10 -10
- package/src/executor/executor.ts +4 -4
- package/src/index.ts +34 -45
- package/src/info-screen.ts +97 -40
- package/src/session/db.ts +40 -11
- package/src/session/extract.ts +37 -0
- package/src/tools/ctx-batch-execute.ts +5 -16
- package/src/tools/ctx-doctor.ts +0 -18
- package/src/tools/ctx-stats.ts +43 -10
- package/src/tools/register.ts +30 -122
- package/src/tui/settings-overlay.ts +12 -21
- package/src/types.ts +8 -26
- package/src/store/chunking.ts +0 -126
- package/src/store/db-base.ts +0 -87
- package/src/store/index.ts +0 -513
- package/src/store/unified.ts +0 -109
- package/src/tools/ctx-fetch-and-index.ts +0 -32
- package/src/tools/ctx-index.ts +0 -36
- package/src/tools/ctx-search.ts +0 -19
package/src/tools/ctx-stats.ts
CHANGED
|
@@ -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:
|
|
58
|
-
|
|
59
|
-
|
|
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
|
+
}
|
package/src/tools/register.ts
CHANGED
|
@@ -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
|
-
*
|
|
7
|
+
* Tool names:
|
|
8
8
|
* compact, session_recall, sandbox, sandbox_file, sandbox_batch,
|
|
9
|
-
*
|
|
9
|
+
* compactor_stats, compactor_doctor, context_budget
|
|
10
10
|
*
|
|
11
|
-
*
|
|
11
|
+
* Deprecated aliases for backward compatibility:
|
|
12
12
|
* vcc_recall, ctx_execute, ctx_execute_file, ctx_batch_execute,
|
|
13
|
-
*
|
|
13
|
+
* ctx_stats, ctx_doctor
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import { Type
|
|
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
|
|
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<
|
|
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<
|
|
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<
|
|
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<
|
|
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<
|
|
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++;
|
|
266
|
-
|
|
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
|
-
|
|
269
|
-
|
|
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
|
|
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.
|
|
283
|
-
const
|
|
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.
|
|
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,
|
|
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
|
-
//
|
|
358
|
-
const doctorExec = async (): Promise<
|
|
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
|
|
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,
|
|
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
|
-
//
|
|
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<
|
|
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
|
|
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,
|
|
175
|
-
detail: "Max token savings. Compaction: full. Display: minimal.\
|
|
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
|
|
179
|
-
detail: "Moderate all strategies. Display: balanced.\
|
|
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
|
|
183
|
-
detail: "Everything enabled. Display: verbose.\
|
|
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
|
|
187
|
-
detail: "Compaction only. Display: opencode.\
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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 (
|
|
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
|
// ─────────────────────────────────────────────────────────
|
package/src/store/chunking.ts
DELETED
|
@@ -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
|
-
}
|