@pi-unipi/compactor 2.6.0 → 2.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -4
- package/package.json +3 -3
- package/skills/compactor/SKILL.md +1 -1
- package/skills/compactor-detail/SKILL.md +3 -5
- package/skills/compactor-doctor/SKILL.md +1 -1
- package/skills/compactor-stats/SKILL.md +1 -1
- package/src/commands/index.ts +47 -78
- package/src/compaction/brief.ts +161 -90
- package/src/compaction/build-sections.ts +3 -4
- package/src/compaction/compact-args.ts +86 -0
- package/src/compaction/cut.ts +270 -28
- package/src/compaction/drill-down.ts +261 -0
- package/src/compaction/format-recall.ts +96 -0
- package/src/compaction/format.ts +8 -3
- package/src/compaction/hooks.ts +248 -72
- package/src/compaction/merge.ts +34 -4
- package/src/compaction/rank.ts +270 -0
- package/src/compaction/recall-scope.ts +28 -0
- package/src/compaction/search-entries.ts +333 -96
- package/src/compaction/skill-collapse.ts +35 -0
- package/src/compaction/summarize.ts +37 -6
- package/src/compaction/token-estimate.ts +104 -0
- package/src/compaction/touched-files.ts +35 -0
- package/src/config/manager.ts +2 -27
- package/src/config/presets.ts +0 -2
- package/src/config/schema.ts +2 -16
- package/src/executor/executor.ts +6 -15
- package/src/executor/runtime.ts +2 -12
- package/src/index.ts +12 -122
- package/src/info-screen.ts +3 -10
- package/src/security/evaluator.ts +0 -53
- package/src/security/policy.ts +7 -8
- package/src/session/db.ts +0 -6
- package/src/tools/ctx-execute-file.ts +0 -5
- package/src/tools/register.ts +27 -50
- package/src/tools/vcc-recall.ts +86 -48
- package/src/tui/settings-overlay.ts +20 -40
- package/src/types.ts +43 -100
- package/src/display/diff-renderer.ts +0 -281
- package/src/display/line-width-safety.ts +0 -28
- package/src/display/render-utils.ts +0 -52
- package/src/display/thinking-label.ts +0 -18
- package/src/display/tool-overrides.ts +0 -136
- package/src/tools/compact.ts +0 -20
package/src/tools/register.ts
CHANGED
|
@@ -15,8 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
import { Type } from "typebox";
|
|
17
17
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
18
|
-
import {
|
|
19
|
-
import { MAX_RECALL_RESULTS, vccRecall, type RecallInput } from "./vcc-recall.js";
|
|
18
|
+
import { vccRecall } from "./vcc-recall.js";
|
|
20
19
|
import { ctxExecute, type CtxExecuteInput } from "./ctx-execute.js";
|
|
21
20
|
import { ctxExecuteFile, type CtxExecuteFileInput } from "./ctx-execute-file.js";
|
|
22
21
|
import { ctxBatchExecute, type BatchItem } from "./ctx-batch-execute.js";
|
|
@@ -51,17 +50,15 @@ const CompactParams = Type.Object({
|
|
|
51
50
|
});
|
|
52
51
|
|
|
53
52
|
const RecallParams = Type.Object({
|
|
54
|
-
query: Type.String({ description: "
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
query: Type.Optional(Type.String({ description: "What to recall, in plain keywords (e.g. 'redis cache decision'). Multi-word queries are ranked by relevance. A regex pattern also works. #N:path drills into a file's content from an entry." })),
|
|
54
|
+
expand: Type.Optional(Type.Array(Type.Number(), { description: "Entry indices to return full untruncated content for" })),
|
|
55
|
+
page: Type.Optional(Type.Number({ description: "Page number (1-based) for paginated search results. Default: 1.", minimum: 1 })),
|
|
56
|
+
scope: Type.Optional(Type.Union([Type.Literal("lineage"), Type.Literal("all")], {
|
|
57
|
+
description: "Default 'lineage' covers the active conversation path. Use 'all' to also reach messages from other branches, such as turns that were edited or retried.",
|
|
57
58
|
})),
|
|
58
|
-
|
|
59
|
-
description:
|
|
60
|
-
minimum: 1,
|
|
61
|
-
maximum: MAX_RECALL_RESULTS,
|
|
59
|
+
mode: Type.Optional(Type.Union([Type.Literal("hybrid"), Type.Literal("touched")], {
|
|
60
|
+
description: "What to show. hybrid (default) = normal search; touched = aggregated files-by-path with entry indices.",
|
|
62
61
|
})),
|
|
63
|
-
offset: Type.Optional(Type.Number({ description: "Pagination offset", minimum: 0 })),
|
|
64
|
-
expand: Type.Optional(Type.Boolean({ description: "Return full message content for hits" })),
|
|
65
62
|
});
|
|
66
63
|
|
|
67
64
|
const SandboxParams = Type.Object({
|
|
@@ -111,20 +108,6 @@ function jsonResult(data: unknown, label?: string): any {
|
|
|
111
108
|
};
|
|
112
109
|
}
|
|
113
110
|
|
|
114
|
-
/** Log a deprecation warning when old tool names are used. */
|
|
115
|
-
function deprecationLog(_oldName: string, _newName: string): void {
|
|
116
|
-
// Deprecation logging disabled — was writing to stdout causing TUI rendering issues.
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// --- Old schema names for backward compat aliases ---
|
|
120
|
-
|
|
121
|
-
const VccRecallParams = RecallParams;
|
|
122
|
-
const CtxExecuteParams = SandboxParams;
|
|
123
|
-
const CtxExecuteFileParams = SandboxFileParams;
|
|
124
|
-
const CtxBatchExecuteParams = SandboxBatchParams;
|
|
125
|
-
const CtxStatsParams = StatsParams;
|
|
126
|
-
const CtxDoctorParams = DoctorParams;
|
|
127
|
-
|
|
128
111
|
// --- Registration ---
|
|
129
112
|
|
|
130
113
|
export interface CompactorToolDeps {
|
|
@@ -163,8 +146,7 @@ export function registerCompactorTools(pi: ExtensionAPI, deps: CompactorToolDeps
|
|
|
163
146
|
}
|
|
164
147
|
const c = deps.getCounters?.();
|
|
165
148
|
if (c) { c.compactions++; }
|
|
166
|
-
|
|
167
|
-
return jsonResult(result, "Compaction triggered");
|
|
149
|
+
return jsonResult({ success: true, message: "Compaction triggered. Stats will be available after next compact event." }, "Compaction triggered");
|
|
168
150
|
},
|
|
169
151
|
} as any));
|
|
170
152
|
|
|
@@ -175,28 +157,28 @@ export function registerCompactorTools(pi: ExtensionAPI, deps: CompactorToolDeps
|
|
|
175
157
|
const config = loadConfig(ctx?.cwd ?? process.cwd());
|
|
176
158
|
const liveBlocks = ctx ? filterNoise(recallBlocksFromContext(ctx), config.pipeline?.customNoisePatterns) : [];
|
|
177
159
|
const blocks = liveBlocks.length > 0 ? liveBlocks : deps.getBlocks();
|
|
178
|
-
const
|
|
160
|
+
const result = vccRecall(blocks, {
|
|
179
161
|
query: params.query,
|
|
162
|
+
scope: params.scope,
|
|
180
163
|
mode: params.mode,
|
|
181
|
-
|
|
182
|
-
offset: params.offset,
|
|
164
|
+
page: params.page,
|
|
183
165
|
expand: params.expand,
|
|
184
|
-
};
|
|
185
|
-
|
|
186
|
-
if (result.hits.length === 0) {
|
|
187
|
-
return textResult(`No results found for "${result.query}".`);
|
|
188
|
-
}
|
|
189
|
-
const lines = result.hits.map(
|
|
190
|
-
(h, i) =>
|
|
191
|
-
`[${i + 1}/${result.total}] score=${h.score.toFixed(2)} kind=${h.kind}\n${h.text}`,
|
|
192
|
-
);
|
|
193
|
-
return textResult(
|
|
194
|
-
`Found ${result.total} results for "${result.query}":\n\n${lines.join("\n\n")}`,
|
|
195
|
-
result as unknown as Record<string, unknown>,
|
|
196
|
-
);
|
|
166
|
+
});
|
|
167
|
+
return textResult(result.text, { query: params.query ?? null });
|
|
197
168
|
};
|
|
198
|
-
pi.registerTool({
|
|
199
|
-
|
|
169
|
+
pi.registerTool({
|
|
170
|
+
name: "session_recall",
|
|
171
|
+
label: "Session Recall",
|
|
172
|
+
description:
|
|
173
|
+
"Search session history using keyword or regex search. Find previous goals, files, commits, decisions, and context — " +
|
|
174
|
+
"including anything dropped by compaction. Reach for this before telling the user you no longer have the context. " +
|
|
175
|
+
"Plain keywords work best; a regex pattern is also accepted. Results are paged (page); pass expand with entry indices " +
|
|
176
|
+
"to read full untruncated content. Use mode:'touched' to list files worked on in this session with their entry indices, " +
|
|
177
|
+
"and #N:path to drill into a file's content from an entry (#N:path:full for all lines). Only the current session is " +
|
|
178
|
+
"searchable — earlier sessions are not.",
|
|
179
|
+
parameters: RecallParams,
|
|
180
|
+
execute: recallExec,
|
|
181
|
+
} as any);
|
|
200
182
|
|
|
201
183
|
// Sandbox tools are session-scoped and only registered when enabled.
|
|
202
184
|
if (deps.sandbox) {
|
|
@@ -226,7 +208,6 @@ export function registerCompactorTools(pi: ExtensionAPI, deps: CompactorToolDeps
|
|
|
226
208
|
}
|
|
227
209
|
};
|
|
228
210
|
pi.registerTool({ name: "sandbox", label: "Sandbox", description: "Run code in a sandboxed environment. Supports 11 languages. Only stdout enters context.", parameters: SandboxParams, execute: sandboxExec } as any);
|
|
229
|
-
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);
|
|
230
211
|
|
|
231
212
|
// 4. sandbox_file (new) / ctx_execute_file (deprecated) — execute file
|
|
232
213
|
const sandboxFileExec = async (_toolCallId: string, params: any): Promise<import("@earendil-works/pi-coding-agent").AgentToolResult<unknown>> => {
|
|
@@ -246,7 +227,6 @@ export function registerCompactorTools(pi: ExtensionAPI, deps: CompactorToolDeps
|
|
|
246
227
|
}
|
|
247
228
|
};
|
|
248
229
|
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);
|
|
249
|
-
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);
|
|
250
230
|
|
|
251
231
|
// 5. sandbox_batch (new) / ctx_batch_execute (deprecated) — atomic batch (execute only)
|
|
252
232
|
const sandboxBatchExec = async (_toolCallId: string, params: any): Promise<import("@earendil-works/pi-coding-agent").AgentToolResult<unknown>> => {
|
|
@@ -266,7 +246,6 @@ export function registerCompactorTools(pi: ExtensionAPI, deps: CompactorToolDeps
|
|
|
266
246
|
}
|
|
267
247
|
};
|
|
268
248
|
pi.registerTool({ name: "sandbox_batch", label: "Sandbox Batch", description: "Run multiple code executions atomically as a batch.", parameters: SandboxBatchParams, execute: sandboxBatchExec } as any);
|
|
269
|
-
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);
|
|
270
249
|
}
|
|
271
250
|
|
|
272
251
|
// 6. compactor_stats (new) / ctx_stats (deprecated) — context savings dashboard
|
|
@@ -287,7 +266,6 @@ export function registerCompactorTools(pi: ExtensionAPI, deps: CompactorToolDeps
|
|
|
287
266
|
}
|
|
288
267
|
};
|
|
289
268
|
pi.registerTool({ name: "compactor_stats", label: "Compactor Stats", description: "Show context savings dashboard — session events, compactions, tool usage.", parameters: StatsParams, execute: statsExec } as any);
|
|
290
|
-
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);
|
|
291
269
|
|
|
292
270
|
// 7. compactor_doctor (new) / ctx_doctor (deprecated) — diagnostics checklist
|
|
293
271
|
const doctorExec = async (): Promise<import("@earendil-works/pi-coding-agent").AgentToolResult<unknown>> => {
|
|
@@ -305,7 +283,6 @@ export function registerCompactorTools(pi: ExtensionAPI, deps: CompactorToolDeps
|
|
|
305
283
|
}
|
|
306
284
|
};
|
|
307
285
|
pi.registerTool({ name: "compactor_doctor", label: "Compactor Doctor", description: "Run diagnostics checklist — validate config, DB, runtimes.", parameters: DoctorParams, execute: doctorExec } as any);
|
|
308
|
-
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);
|
|
309
286
|
|
|
310
287
|
// 8. context_budget — estimate remaining context window
|
|
311
288
|
pi.registerTool(({
|
package/src/tools/vcc-recall.ts
CHANGED
|
@@ -1,19 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* vcc_recall
|
|
2
|
+
* session_recall / vcc_recall — session history search (pi-vcc parity).
|
|
3
|
+
*
|
|
4
|
+
* Hybrid keyword/regex search over the session branch, with:
|
|
5
|
+
* - scope: lineage (active branch, default) | all
|
|
6
|
+
* - pagination (page, 5 results/page)
|
|
7
|
+
* - mode:"touched" — files worked on + entry indices
|
|
8
|
+
* - #N:path drill-down into file content from an entry
|
|
9
|
+
* - expand: entry indices returned as full untruncated content
|
|
3
10
|
*/
|
|
4
11
|
|
|
5
12
|
import type { NormalizedBlock } from "../types.js";
|
|
6
|
-
import { searchEntries } from "../compaction/search-entries.js";
|
|
13
|
+
import { searchEntries, type RecallHit } from "../compaction/search-entries.js";
|
|
14
|
+
import { getTouchedFiles } from "../compaction/touched-files.js";
|
|
15
|
+
import { formatRecallOutput, formatTouchedOutput } from "../compaction/format-recall.js";
|
|
16
|
+
import { normalizeRecallScope, normalizeRecallMode, type RecallScope } from "../compaction/recall-scope.js";
|
|
17
|
+
import { parseDrillDown, expandEntryFile } from "../compaction/drill-down.js";
|
|
7
18
|
|
|
8
19
|
export const MAX_RECALL_RESULTS = 50;
|
|
9
20
|
export const MAX_EXPANDED_HIT_BYTES = 16 * 1024;
|
|
21
|
+
export const DEFAULT_RECENT = 25;
|
|
22
|
+
export const PAGE_SIZE = 5;
|
|
10
23
|
|
|
11
24
|
export interface RecallInput {
|
|
12
|
-
query
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
expand?:
|
|
25
|
+
query?: string;
|
|
26
|
+
scope?: string;
|
|
27
|
+
mode?: string;
|
|
28
|
+
page?: number;
|
|
29
|
+
expand?: number[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface RecallResult {
|
|
33
|
+
text: string;
|
|
17
34
|
}
|
|
18
35
|
|
|
19
36
|
function truncateExpandedHit(text: string): string {
|
|
@@ -24,56 +41,77 @@ function truncateExpandedHit(text: string): string {
|
|
|
24
41
|
return `${visible}\n… ${omitted} bytes omitted from this hit; narrow the query to inspect more specific context …`;
|
|
25
42
|
}
|
|
26
43
|
|
|
27
|
-
export
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
text: string;
|
|
32
|
-
kind: string;
|
|
33
|
-
}>;
|
|
34
|
-
total: number;
|
|
35
|
-
query: string;
|
|
36
|
-
}
|
|
44
|
+
export const invalidExpandIndices = (requested: number[], available: Set<number>): number[] =>
|
|
45
|
+
requested.filter((i) => !Number.isInteger(i) || !available.has(i));
|
|
46
|
+
|
|
47
|
+
const scopeSuffix = (scope: RecallScope): string => (scope === "all" ? " (scope: all)" : "");
|
|
37
48
|
|
|
38
49
|
export function vccRecall(
|
|
39
50
|
blocks: NormalizedBlock[],
|
|
40
51
|
input: RecallInput,
|
|
41
52
|
): RecallResult {
|
|
42
|
-
const
|
|
43
|
-
const requestedLimit = Number.isFinite(input.limit) ? Math.floor(input.limit!) : 10;
|
|
44
|
-
const requestedOffset = Number.isFinite(input.offset) ? Math.floor(input.offset!) : 0;
|
|
45
|
-
const limit = Math.min(MAX_RECALL_RESULTS, Math.max(1, requestedLimit));
|
|
46
|
-
const offset = Math.max(0, requestedOffset);
|
|
53
|
+
const scope = normalizeRecallScope(input.scope);
|
|
47
54
|
|
|
48
|
-
|
|
55
|
+
// Drill-down: #N:path resolves to file-scoped tool content. Anchored so
|
|
56
|
+
// inline mentions like "see #42:auth.ts" are never treated as drill-down.
|
|
57
|
+
const q = input.query?.trim();
|
|
58
|
+
if (q && parseDrillDown(q)) {
|
|
59
|
+
const parsed = parseDrillDown(q)!;
|
|
60
|
+
const text = expandEntryFile(blocks, parsed.index, parsed.pathPattern, parsed.full, parsed.offset, parsed.limit);
|
|
61
|
+
return { text };
|
|
62
|
+
}
|
|
49
63
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
text: expand ? truncateExpandedHit(text) : text.slice(0, 200),
|
|
69
|
-
kind: b.kind,
|
|
70
|
-
});
|
|
71
|
-
}
|
|
64
|
+
// touched mode: aggregate file operations across the searched window.
|
|
65
|
+
if (normalizeRecallMode(input.mode) === "touched") {
|
|
66
|
+
const touched = getTouchedFiles(blocks);
|
|
67
|
+
return { text: formatTouchedOutput(touched, input.page) };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const expandSet = new Set(input.expand ?? []);
|
|
71
|
+
const hasExpand = expandSet.size > 0;
|
|
72
|
+
|
|
73
|
+
// expand without query: return full untruncated content for the indices
|
|
74
|
+
if (hasExpand && !q) {
|
|
75
|
+
const byIndex = new Map(blocks.map((b, i) => [b.sourceIndex ?? i, b]));
|
|
76
|
+
const requested = [...expandSet];
|
|
77
|
+
const invalid = invalidExpandIndices(requested, new Set(byIndex.keys()));
|
|
78
|
+
if (invalid.length > 0) {
|
|
79
|
+
return {
|
|
80
|
+
text: `Cannot expand indices outside ${scope === "all" ? "session history" : "active lineage"}: ${invalid.join(", ")}`,
|
|
81
|
+
};
|
|
72
82
|
}
|
|
83
|
+
const expanded = requested
|
|
84
|
+
.sort((a, b) => a - b)
|
|
85
|
+
.map((i) => byIndex.get(i))
|
|
86
|
+
.filter((b): b is NormalizedBlock => Boolean(b));
|
|
87
|
+
const lines = expanded.map((b) => {
|
|
88
|
+
const idx = b.sourceIndex ?? 0;
|
|
89
|
+
const text = truncateExpandedHit(b.kind === "tool_call" ? `${b.name} ${JSON.stringify(b.args)}` : b.text);
|
|
90
|
+
return `#${idx} [${b.kind}] ${text}`;
|
|
91
|
+
});
|
|
92
|
+
return { text: (scope === "all" ? "Scope: all\n\n" : "") + lines.join("\n\n") };
|
|
73
93
|
}
|
|
74
94
|
|
|
75
|
-
const
|
|
76
|
-
|
|
95
|
+
const allResults = q ? searchEntries(blocks, q) : searchEntries(blocks).slice(-DEFAULT_RECENT);
|
|
96
|
+
|
|
97
|
+
if (q) {
|
|
98
|
+
const page = Math.max(1, input.page ?? 1);
|
|
99
|
+
const start = (page - 1) * PAGE_SIZE;
|
|
100
|
+
const pageResults: RecallHit[] = allResults.slice(start, start + PAGE_SIZE);
|
|
101
|
+
const totalPages = Math.ceil(allResults.length / PAGE_SIZE);
|
|
102
|
+
if (pageResults.length === 0) {
|
|
103
|
+
return { text: `No matches for "${q}"${scopeSuffix(scope)}${page > 1 ? ` on page ${page}` : ""}.` };
|
|
104
|
+
}
|
|
105
|
+
const header = totalPages > 1
|
|
106
|
+
? `Page ${page}/${totalPages} (${allResults.length} total matches${scopeSuffix(scope)})`
|
|
107
|
+
: `${allResults.length} matches${scopeSuffix(scope)}`;
|
|
108
|
+
const footer = page < totalPages
|
|
109
|
+
? `\n--- Use page:${page + 1}${scope === "all" ? " with scope:'all'" : ""} for more results ---`
|
|
110
|
+
: "";
|
|
111
|
+
return { text: formatRecallOutput(pageResults, q, header) + footer };
|
|
112
|
+
}
|
|
77
113
|
|
|
78
|
-
|
|
114
|
+
// No query: recent entries
|
|
115
|
+
const output = (scope === "all" ? "Scope: all\n\n" : "") + formatRecallOutput(allResults, q);
|
|
116
|
+
return { text: output };
|
|
79
117
|
}
|
|
@@ -8,14 +8,14 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import type { Component, TUI } from "@earendil-works/pi-tui";
|
|
11
|
-
import {
|
|
11
|
+
import { Key, matchesKey, SettingsList, type SettingItem, type SettingsListTheme } from "@earendil-works/pi-tui";
|
|
12
12
|
import type { Theme, KeybindingsManager } from "@earendil-works/pi-coding-agent";
|
|
13
13
|
import { loadConfig, saveConfig, projectConfigPath } from "../config/manager.js";
|
|
14
14
|
import { applyPreset, detectPreset } from "../config/presets.js";
|
|
15
15
|
import type { CompactorPreset } from "../types.js";
|
|
16
16
|
import type { CompactorConfig } from "../types.js";
|
|
17
17
|
import { existsSync, unlinkSync } from "node:fs";
|
|
18
|
-
import { boxInnerWidth } from "@pi-unipi/core";
|
|
18
|
+
import { boxInnerWidth, OverlayTheme } from "@pi-unipi/core";
|
|
19
19
|
|
|
20
20
|
// ─── Section types ─────────────────────────────────────────────────────
|
|
21
21
|
|
|
@@ -48,16 +48,6 @@ interface PipelineDef {
|
|
|
48
48
|
// ─── Static definitions ────────────────────────────────────────────────
|
|
49
49
|
|
|
50
50
|
const STRATEGIES: StrategyDef[] = [
|
|
51
|
-
{
|
|
52
|
-
key: "debug",
|
|
53
|
-
label: "Verbose Debug",
|
|
54
|
-
description: "Log ALL compaction events to console",
|
|
55
|
-
modes: ["on", "off"],
|
|
56
|
-
getEnabled: (c) => c.debug,
|
|
57
|
-
setEnabled: (c, v) => (c.debug = v),
|
|
58
|
-
getMode: (c) => (c.debug ? "on" : "off"),
|
|
59
|
-
setMode: (c, v) => (c.debug = v === "on"),
|
|
60
|
-
},
|
|
61
51
|
{
|
|
62
52
|
key: "sessionGoals",
|
|
63
53
|
label: "Session Goals",
|
|
@@ -144,6 +134,9 @@ const PIPELINE_ITEMS: PipelineDef[] = [
|
|
|
144
134
|
// Only expose implemented behavior. Reserved compatibility fields remain in
|
|
145
135
|
// the persisted schema so existing config files continue to load.
|
|
146
136
|
{ key: "autoInjection", label: "Auto Injection", description: "Inject behavioral state after compaction", group: "On Compaction", getValue: (c) => c.pipeline.autoInjection, setValue: (c, v) => (c.pipeline.autoInjection = v) },
|
|
137
|
+
{ key: "smartKeepTail", label: "Smart Keep Tail", description: "Grow keep:N tail to ≥5k tokens when it would be tiny", group: "Cut Behavior", getValue: (c) => c.smartKeepTail, setValue: (c, v) => (c.smartKeepTail = v) },
|
|
138
|
+
{ key: "continueAfterThresholdCompact", label: "Auto-continue", description: "Resume agent after threshold/overflow compaction", group: "On Compaction", getValue: (c) => c.continueAfterThresholdCompact, setValue: (c, v) => (c.continueAfterThresholdCompact = v) },
|
|
139
|
+
{ key: "debug", label: "Debug Output", description: "Write compaction diagnostics to /tmp/compactor-debug.json", group: "Diagnostics", getValue: (c) => c.debug, setValue: (c, v) => (c.debug = v) },
|
|
147
140
|
];
|
|
148
141
|
|
|
149
142
|
const PRESETS: CompactorPreset[] = ["precise", "balanced", "thorough", "lean"];
|
|
@@ -181,23 +174,10 @@ const THEME: SettingsListTheme = {
|
|
|
181
174
|
hint: (text) => `\x1b[2m${text}\x1b[0m`,
|
|
182
175
|
};
|
|
183
176
|
|
|
184
|
-
// ───
|
|
185
|
-
|
|
186
|
-
function frameLine(content: string, innerWidth: number): string {
|
|
187
|
-
const truncated = truncateToWidth(content, innerWidth, "");
|
|
188
|
-
const padding = Math.max(0, innerWidth - visibleWidth(truncated));
|
|
189
|
-
return `\x1b[90m│\x1b[0m${truncated}${" ".repeat(padding)}\x1b[90m│\x1b[0m`;
|
|
190
|
-
}
|
|
177
|
+
// ─── Shared box-drawing helper ────────────────────────────────────────
|
|
191
178
|
|
|
192
|
-
|
|
193
|
-
return `\x1b[90m├${"─".repeat(innerWidth)}┤\x1b[0m`;
|
|
194
|
-
}
|
|
179
|
+
const overlay = new OverlayTheme();
|
|
195
180
|
|
|
196
|
-
function borderLine(innerWidth: number, edge: "top" | "bottom"): string {
|
|
197
|
-
const left = edge === "top" ? "┌" : "└";
|
|
198
|
-
const right = edge === "top" ? "┐" : "┘";
|
|
199
|
-
return `\x1b[90m${left}${"─".repeat(innerWidth)}${right}\x1b[0m`;
|
|
200
|
-
}
|
|
201
181
|
|
|
202
182
|
function uniqueValues(values: string[]): string[] {
|
|
203
183
|
return [...new Set(values)];
|
|
@@ -538,7 +518,7 @@ export class CompactorSettingsOverlay implements Component {
|
|
|
538
518
|
|
|
539
519
|
handleInput(data: string): void {
|
|
540
520
|
// Tab switches section
|
|
541
|
-
if (data === "\t" || data
|
|
521
|
+
if (data === "\t" || matchesKey(data, Key.shift("tab"))) {
|
|
542
522
|
const idx = SECTIONS.indexOf(this.section);
|
|
543
523
|
this.section = SECTIONS[(idx + 1) % SECTIONS.length];
|
|
544
524
|
return;
|
|
@@ -569,8 +549,8 @@ export class CompactorSettingsOverlay implements Component {
|
|
|
569
549
|
const lines: string[] = [];
|
|
570
550
|
|
|
571
551
|
// Header
|
|
572
|
-
lines.push(borderLine(innerWidth, "top"));
|
|
573
|
-
lines.push(frameLine(`\x1b[1m\x1b[36m🗜️ Compactor Settings\x1b[0m`, innerWidth));
|
|
552
|
+
lines.push(overlay.borderLine(innerWidth, "top"));
|
|
553
|
+
lines.push(overlay.frameLine(`\x1b[1m\x1b[36m🗜️ Compactor Settings\x1b[0m`, innerWidth));
|
|
574
554
|
|
|
575
555
|
// Current preset indicator
|
|
576
556
|
const presetName = detectPreset(this.config);
|
|
@@ -578,8 +558,8 @@ export class CompactorSettingsOverlay implements Component {
|
|
|
578
558
|
const overrideLabel = this.perProjectOverride
|
|
579
559
|
? `\x1b[33mProject override\x1b[0m`
|
|
580
560
|
: `\x1b[2mGlobal config\x1b[0m`;
|
|
581
|
-
lines.push(frameLine(`\x1b[2mPreset: ${presetLabel} · ${overrideLabel}\x1b[0m`, innerWidth));
|
|
582
|
-
lines.push(ruleLine(innerWidth));
|
|
561
|
+
lines.push(overlay.frameLine(`\x1b[2mPreset: ${presetLabel} · ${overrideLabel}\x1b[0m`, innerWidth));
|
|
562
|
+
lines.push(overlay.ruleLine(innerWidth));
|
|
583
563
|
|
|
584
564
|
// Section tabs
|
|
585
565
|
const tabParts = SECTIONS.map((s) => {
|
|
@@ -589,28 +569,28 @@ export class CompactorSettingsOverlay implements Component {
|
|
|
589
569
|
}
|
|
590
570
|
return `\x1b[2m${label}\x1b[0m`;
|
|
591
571
|
});
|
|
592
|
-
lines.push(frameLine(` ${tabParts.join(" ")}`, innerWidth));
|
|
593
|
-
lines.push(ruleLine(innerWidth));
|
|
572
|
+
lines.push(overlay.frameLine(` ${tabParts.join(" ")}`, innerWidth));
|
|
573
|
+
lines.push(overlay.ruleLine(innerWidth));
|
|
594
574
|
|
|
595
575
|
// Section content (rendered by SettingsList)
|
|
596
576
|
const contentLines = this.currentList.render(innerWidth - 2);
|
|
597
577
|
for (const line of contentLines) {
|
|
598
|
-
lines.push(frameLine(` ${line}`, innerWidth));
|
|
578
|
+
lines.push(overlay.frameLine(` ${line}`, innerWidth));
|
|
599
579
|
}
|
|
600
580
|
|
|
601
581
|
// Saved indicator
|
|
602
582
|
if (this.saved) {
|
|
603
|
-
lines.push(ruleLine(innerWidth));
|
|
604
|
-
lines.push(frameLine(` \x1b[32m✓ Settings saved\x1b[0m`, innerWidth));
|
|
583
|
+
lines.push(overlay.ruleLine(innerWidth));
|
|
584
|
+
lines.push(overlay.frameLine(` \x1b[32m✓ Settings saved\x1b[0m`, innerWidth));
|
|
605
585
|
}
|
|
606
586
|
|
|
607
587
|
// Footer hints
|
|
608
|
-
lines.push(ruleLine(innerWidth));
|
|
588
|
+
lines.push(overlay.ruleLine(innerWidth));
|
|
609
589
|
const hints = this.section === "strategies"
|
|
610
590
|
? "↑↓ navigate · Space change · Tab switch · / search · Enter save · Esc cancel"
|
|
611
591
|
: "↑↓ navigate · Space change · Tab switch · Enter save · Esc cancel";
|
|
612
|
-
lines.push(frameLine(`\x1b[2m${hints}\x1b[0m`, innerWidth));
|
|
613
|
-
lines.push(borderLine(innerWidth, "bottom"));
|
|
592
|
+
lines.push(overlay.frameLine(`\x1b[2m${hints}\x1b[0m`, innerWidth));
|
|
593
|
+
lines.push(overlay.borderLine(innerWidth, "bottom"));
|
|
614
594
|
|
|
615
595
|
return lines;
|
|
616
596
|
}
|