@pi-unipi/unipi 2.0.3 → 2.0.4

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,55 +1,53 @@
1
1
  ---
2
2
  name: compactor-doctor
3
- description: Diagnostics — validate config, DB, FTS5, runtimes, troubleshoot issues.
3
+ description: Diagnostics — validate config, session DB, runtimes, and troubleshoot compactor issues.
4
4
  ---
5
5
 
6
6
  # Compactor Doctor
7
7
 
8
8
  Run diagnostics and troubleshoot compactor issues.
9
9
 
10
- ## Commands
10
+ ## Commands and Tools
11
11
 
12
- - `/unipi:compact-doctor` — run all checks
13
- - `ctx_doctor` tool — agent-callable diagnostics
12
+ - `/unipi:compact-doctor` — user-facing diagnostics
13
+ - `compactor_doctor` tool — agent-callable diagnostics
14
+ - `ctx_doctor` tool — deprecated alias
14
15
 
15
16
  ## Checks Performed
16
17
 
17
18
  | Check | What It Validates |
18
19
  |-------|-------------------|
19
- | **Config file** | `~/.unipi/config/compactor/config.json` exists and is valid |
20
- | **Session DB** | SQLite connection works, schema correct |
21
- | **Content Store** | FTS5 index accessible, tables exist |
22
- | **Runtime: node** | Node.js available for sandbox |
23
- | **Runtime: python3** | Python 3 available for sandbox |
24
- | **Runtime: bash** | Bash available for sandbox |
20
+ | **Config file** | `~/.unipi/config/compactor/config.json` exists or defaults can be used |
21
+ | **Session DB** | SQLite connection works and session schema is usable |
22
+ | **Runtime: node** | Node.js available for sandbox/runtime helpers |
23
+ | **Runtime: python3** | Python 3 available for Python sandbox execution |
24
+ | **Runtime: bash** | Bash available for shell sandbox execution |
25
25
 
26
26
  ## Status Icons
27
27
 
28
28
  - ✅ **pass** — check succeeded
29
- - ⚠️ **warn** — non-critical issue (e.g., optional runtime missing)
30
- - ❌ **fail** — critical issue, feature may not work
29
+ - ⚠️ **warn** — non-critical issue, such as an optional runtime missing
30
+ - ❌ **fail** — critical issue; a feature may not work
31
31
 
32
32
  ## Common Issues
33
33
 
34
34
  ### "Config file: Using defaults"
35
- - Normal on first run
36
- - Config auto-created on next settings save
37
- - Fix: `/unipi:compact-settings` save
35
+
36
+ - Normal on first run.
37
+ - Config is created by the extension or on settings save.
38
+ - Fix: open `/unipi:compact-settings`, adjust if desired, then save.
38
39
 
39
40
  ### "Session DB: Connection failed"
40
- - SQLite not available
41
- - Check if `better-sqlite3` is installed
42
- - Fix: `npm install better-sqlite3`
43
41
 
44
- ### "Content Store: FTS5 error"
45
- - SQLite FTS5 extension not available
46
- - Requires SQLite 3.9+ with FTS5
47
- - Fix: Update system SQLite
42
+ - SQLite or `better-sqlite3` may be unavailable.
43
+ - Compaction can still degrade gracefully, but stats/recall may be limited.
44
+ - Fix: verify dependencies and run package install/build steps.
48
45
 
49
46
  ### "Runtime: python3 Not found"
50
- - Python not installed or not in PATH
51
- - Only needed for Python sandbox execution
52
- - Fix: Install Python 3 or ignore if not needed
47
+
48
+ - Python is not installed or not in `PATH`.
49
+ - Only needed for Python sandbox execution.
50
+ - Fix: install Python 3 or ignore if not needed.
53
51
 
54
52
  ## Manual Diagnostics
55
53
 
@@ -63,12 +61,11 @@ cat ~/.unipi/config/compactor/config.json
63
61
  ls -la ~/.unipi/db/compactor/
64
62
  ```
65
63
 
66
- ### Check SQLite version
64
+ ### Check runtimes
67
65
  ```bash
68
- sqlite3 --version
66
+ node --version
67
+ python3 --version
68
+ bash --version
69
69
  ```
70
70
 
71
- ### Test FTS5
72
- ```bash
73
- sqlite3 ':memory:' "CREATE VIRTUAL TABLE t USING fts5(x); SELECT 1;"
74
- ```
71
+ Project content indexing diagnostics live in the CocoIndex package, not compactor.
@@ -1,49 +1,51 @@
1
1
  ---
2
2
  name: compactor-stats
3
- description: Stats display — context savings, session metrics, index health.
3
+ description: Stats display — context savings, session metrics, compactions, sandbox and recall/search counters.
4
4
  ---
5
5
 
6
6
  # Compactor Stats
7
7
 
8
8
  Display and interpret compactor statistics.
9
9
 
10
- ## Commands
10
+ ## Commands and Tools
11
11
 
12
- - `/unipi:compact-stats` — quick dashboard
13
- - `ctx_stats` tool — detailed stats (agent-callable)
12
+ - `/unipi:compact-stats` — user-facing dashboard
13
+ - `compactor_stats` tool — agent-callable stats
14
+ - `ctx_stats` tool — deprecated alias
14
15
 
15
16
  ## Metrics Explained
16
17
 
17
18
  | Metric | Description |
18
19
  |--------|-------------|
19
- | **Session events** | Total events tracked in current session |
20
- | **Compactions** | Number of times context was compacted |
21
- | **Tokens saved** | Estimated tokens freed by compaction |
22
- | **Indexed docs** | Number of files/sources in FTS5 index |
23
- | **Indexed chunks** | Total searchable chunks |
24
- | **Sandbox runs** | Code executions in sandbox |
25
- | **Search queries** | FTS5 searches performed |
20
+ | **Session events** | Events tracked for the current session in the compactor session DB |
21
+ | **Compactions** | Number of compaction events recorded |
22
+ | **Tokens saved** | Estimated tokens freed by compaction, using Pi token counts when available and DB fallbacks otherwise |
23
+ | **Compression ratio** | Approximate before/kept ratio from stored compaction stats |
24
+ | **Sandbox runs** | Agent sandbox executions recorded in runtime counters/DB |
25
+ | **Search queries** | Session recall/search queries recorded in runtime counters/DB |
26
26
 
27
27
  ## Health Indicators
28
28
 
29
- - **Compactions > 0** on long sessions = working correctly
30
- - **Indexed chunks > 0** = search available
31
- - **Sandbox runs** = code execution active
29
+ - **Compactions > 0** on long sessions = compaction is happening.
30
+ - **Tokens saved > 0** after compaction = savings were recorded.
31
+ - **Sandbox runs** increasing = sandbox tool usage is tracked.
32
+ - **Search queries** increasing = recall/search activity is tracked.
32
33
 
33
34
  ## Reading Stats
34
35
 
35
- ```
36
+ ```text
36
37
  📊 Compactor Stats
37
38
  Session events: 42
38
39
  Compactions: 3
39
40
  Tokens saved: 15000
40
- Indexed docs: 12 (48 chunks)
41
41
  Sandbox runs: 7
42
42
  Search queries: 15
43
43
  ```
44
44
 
45
45
  This means:
46
- - 42 tool calls/events tracked
47
- - 3 compactions saved ~15K tokens
48
- - 12 files indexed into 48 searchable chunks
49
- - 7 code executions, 15 searches performed
46
+
47
+ - 42 events are tracked for the session.
48
+ - 3 compactions saved roughly 15K tokens.
49
+ - 7 sandbox executions and 15 recall/search queries were recorded.
50
+
51
+ Project content indexing stats are not owned by compactor anymore; use the CocoIndex package for indexed file search/status.
@@ -229,9 +229,12 @@ export function registerCommands(pi: ExtensionAPI, deps?: CommandDeps): void {
229
229
  " /unipi:session-recall <query> — search session history\n" +
230
230
  " /unipi:compact-stats — view stats\n" +
231
231
  " /unipi:compact-doctor — run diagnostics\n" +
232
- " /unipi:compact-settings — TUI settings\n" +
232
+ " /unipi:compact-settings — TUI settings, including optional % auto-compaction\n" +
233
233
  " /unipi:compact-preset <name> — apply preset\n" +
234
234
  "\n" +
235
+ "Percentage trigger:\n" +
236
+ " Disabled by default. Enable in /unipi:compact-settings to compact at a context % before Pi's reserve-token limit.\n" +
237
+ "\n" +
235
238
  "Content indexing has moved to @pi-unipi/cocoindex:\n" +
236
239
  " /unipi:cocoindex-init — initialize pipeline\n" +
237
240
  " /unipi:cocoindex-update — index project files\n" +
@@ -0,0 +1,306 @@
1
+ /**
2
+ * Pure percentage auto-compaction trigger decisions.
3
+ *
4
+ * The runtime wiring owns Pi ctx access; this module only decides whether a
5
+ * known context-usage sample should trigger UniPi's zero-LLM compaction.
6
+ */
7
+
8
+ import type { AutoCompactionConfig } from "../types.js";
9
+
10
+ export interface AutoCompactionUsage {
11
+ tokens?: number | null;
12
+ percent?: number | null;
13
+ contextWindow?: number | null;
14
+ }
15
+
16
+ export interface KnownAutoCompactionUsage {
17
+ tokens: number;
18
+ percent: number;
19
+ contextWindow?: number;
20
+ }
21
+
22
+ export interface AutoCompactionState {
23
+ /** Previous known context percentage sample. Null before first known usage. */
24
+ previousPercent: number | null;
25
+ /** Previous known context token sample. Null before first known usage. */
26
+ previousTokens: number | null;
27
+ /** True after UniPi calls ctx.compact() and before Pi reports completion/error. */
28
+ inFlight: boolean;
29
+ /** Timestamp of the last UniPi-triggered compaction attempt. */
30
+ lastTriggerAt: number | null;
31
+ /** Context tokens reported at the last UniPi-triggered compaction attempt. */
32
+ lastTriggerTokens: number | null;
33
+ /** Baseline used to require meaningful token growth for repeat high-usage triggers. */
34
+ repeatBaselineTokens: number | null;
35
+ /** After a successful compaction, consume one known usage sample as a fresh baseline. */
36
+ awaitingPostCompactionSample: boolean;
37
+ }
38
+
39
+ export type AutoCompactionDecisionReason =
40
+ | "disabled"
41
+ | "unknown_usage"
42
+ | "in_flight"
43
+ | "below_threshold"
44
+ | "post_compaction_baseline"
45
+ | "threshold_reached"
46
+ | "threshold_crossed"
47
+ | "cooldown_active"
48
+ | "repeat_growth_needed"
49
+ | "repeat_growth_reached";
50
+
51
+ export interface AutoCompactionDecision {
52
+ shouldTrigger: boolean;
53
+ reason: AutoCompactionDecisionReason;
54
+ state: AutoCompactionState;
55
+ thresholdPercent: number;
56
+ usage?: KnownAutoCompactionUsage;
57
+ cooldownRemainingMs?: number;
58
+ tokenGrowth?: number;
59
+ tokensUntilRepeat?: number;
60
+ }
61
+
62
+ export interface AutoCompactionDecisionInput {
63
+ config?: Partial<AutoCompactionConfig> | null;
64
+ usage?: AutoCompactionUsage | null;
65
+ state: AutoCompactionState;
66
+ nowMs?: number;
67
+ }
68
+
69
+ export const AUTO_COMPACTION_DEFAULTS: AutoCompactionConfig = {
70
+ enabled: false,
71
+ thresholdPercent: 80,
72
+ cooldownMs: 60_000,
73
+ repeatMinGrowthTokens: 4_000,
74
+ notify: true,
75
+ };
76
+
77
+ const clamp = (value: unknown, fallback: number, min: number, max: number): number => {
78
+ if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
79
+ return Math.min(max, Math.max(min, value));
80
+ };
81
+
82
+ export function normalizeAutoCompactionConfig(config?: Partial<AutoCompactionConfig> | null): AutoCompactionConfig {
83
+ return {
84
+ enabled: config?.enabled ?? AUTO_COMPACTION_DEFAULTS.enabled,
85
+ thresholdPercent: clamp(
86
+ config?.thresholdPercent,
87
+ AUTO_COMPACTION_DEFAULTS.thresholdPercent,
88
+ 1,
89
+ 99,
90
+ ),
91
+ cooldownMs: Math.round(clamp(
92
+ config?.cooldownMs,
93
+ AUTO_COMPACTION_DEFAULTS.cooldownMs,
94
+ 0,
95
+ 24 * 60 * 60 * 1000,
96
+ )),
97
+ repeatMinGrowthTokens: Math.round(clamp(
98
+ config?.repeatMinGrowthTokens,
99
+ AUTO_COMPACTION_DEFAULTS.repeatMinGrowthTokens,
100
+ 0,
101
+ 10_000_000,
102
+ )),
103
+ notify: config?.notify ?? AUTO_COMPACTION_DEFAULTS.notify,
104
+ };
105
+ }
106
+
107
+ export function createAutoCompactionState(): AutoCompactionState {
108
+ return {
109
+ previousPercent: null,
110
+ previousTokens: null,
111
+ inFlight: false,
112
+ lastTriggerAt: null,
113
+ lastTriggerTokens: null,
114
+ repeatBaselineTokens: null,
115
+ awaitingPostCompactionSample: false,
116
+ };
117
+ }
118
+
119
+ function knownUsage(usage?: AutoCompactionUsage | null): KnownAutoCompactionUsage | null {
120
+ if (!usage) return null;
121
+ const { tokens, percent, contextWindow } = usage;
122
+ if (typeof tokens !== "number" || !Number.isFinite(tokens) || tokens < 0) return null;
123
+ if (typeof percent !== "number" || !Number.isFinite(percent) || percent < 0) return null;
124
+ const known: KnownAutoCompactionUsage = { tokens, percent };
125
+ if (typeof contextWindow === "number" && Number.isFinite(contextWindow) && contextWindow > 0) {
126
+ known.contextWindow = contextWindow;
127
+ }
128
+ return known;
129
+ }
130
+
131
+ function updatePrevious(state: AutoCompactionState, usage: KnownAutoCompactionUsage): AutoCompactionState {
132
+ return {
133
+ ...state,
134
+ previousPercent: usage.percent,
135
+ previousTokens: usage.tokens,
136
+ };
137
+ }
138
+
139
+ function trigger(
140
+ reason: Extract<AutoCompactionDecisionReason, "threshold_reached" | "threshold_crossed" | "repeat_growth_reached">,
141
+ state: AutoCompactionState,
142
+ usage: KnownAutoCompactionUsage,
143
+ thresholdPercent: number,
144
+ nowMs: number,
145
+ tokenGrowth?: number,
146
+ ): AutoCompactionDecision {
147
+ return {
148
+ shouldTrigger: true,
149
+ reason,
150
+ thresholdPercent,
151
+ usage,
152
+ tokenGrowth,
153
+ state: {
154
+ ...updatePrevious(state, usage),
155
+ inFlight: true,
156
+ lastTriggerAt: nowMs,
157
+ lastTriggerTokens: usage.tokens,
158
+ repeatBaselineTokens: usage.tokens,
159
+ awaitingPostCompactionSample: false,
160
+ },
161
+ };
162
+ }
163
+
164
+ /**
165
+ * Decide whether the current usage sample should trigger auto-compaction.
166
+ *
167
+ * Loop safeguards:
168
+ * - unknown/null usage never triggers and does not erase the previous baseline;
169
+ * - in-flight compactions suppress further triggers;
170
+ * - every repeat trigger after a prior attempt observes cooldown;
171
+ * - if usage remains above threshold after compaction, the first known sample is
172
+ * only a baseline, and a repeat requires sufficient token growth.
173
+ */
174
+ export function decideAutoCompaction(input: AutoCompactionDecisionInput): AutoCompactionDecision {
175
+ const config = normalizeAutoCompactionConfig(input.config);
176
+ const nowMs = input.nowMs ?? Date.now();
177
+
178
+ if (!config.enabled) {
179
+ return {
180
+ shouldTrigger: false,
181
+ reason: "disabled",
182
+ thresholdPercent: config.thresholdPercent,
183
+ state: input.state,
184
+ };
185
+ }
186
+
187
+ if (input.state.inFlight) {
188
+ return {
189
+ shouldTrigger: false,
190
+ reason: "in_flight",
191
+ thresholdPercent: config.thresholdPercent,
192
+ state: input.state,
193
+ };
194
+ }
195
+
196
+ const usage = knownUsage(input.usage);
197
+ if (!usage) {
198
+ return {
199
+ shouldTrigger: false,
200
+ reason: "unknown_usage",
201
+ thresholdPercent: config.thresholdPercent,
202
+ state: input.state,
203
+ };
204
+ }
205
+
206
+ let nextState = updatePrevious(input.state, usage);
207
+
208
+ if (input.state.awaitingPostCompactionSample) {
209
+ nextState = {
210
+ ...nextState,
211
+ awaitingPostCompactionSample: false,
212
+ repeatBaselineTokens: usage.percent >= config.thresholdPercent ? usage.tokens : null,
213
+ };
214
+ return {
215
+ shouldTrigger: false,
216
+ reason: usage.percent >= config.thresholdPercent ? "post_compaction_baseline" : "below_threshold",
217
+ thresholdPercent: config.thresholdPercent,
218
+ usage,
219
+ state: nextState,
220
+ };
221
+ }
222
+
223
+ if (usage.percent < config.thresholdPercent) {
224
+ return {
225
+ shouldTrigger: false,
226
+ reason: "below_threshold",
227
+ thresholdPercent: config.thresholdPercent,
228
+ usage,
229
+ state: {
230
+ ...nextState,
231
+ repeatBaselineTokens: null,
232
+ },
233
+ };
234
+ }
235
+
236
+ if (input.state.lastTriggerAt !== null) {
237
+ const elapsedMs = Math.max(0, nowMs - input.state.lastTriggerAt);
238
+ if (elapsedMs < config.cooldownMs) {
239
+ return {
240
+ shouldTrigger: false,
241
+ reason: "cooldown_active",
242
+ thresholdPercent: config.thresholdPercent,
243
+ usage,
244
+ cooldownRemainingMs: config.cooldownMs - elapsedMs,
245
+ state: nextState,
246
+ };
247
+ }
248
+ }
249
+
250
+ const previousPercent = input.state.previousPercent;
251
+ if (previousPercent === null) {
252
+ return trigger("threshold_reached", input.state, usage, config.thresholdPercent, nowMs);
253
+ }
254
+ if (previousPercent < config.thresholdPercent) {
255
+ return trigger("threshold_crossed", input.state, usage, config.thresholdPercent, nowMs);
256
+ }
257
+
258
+ // We were already above threshold. Re-trigger only after enough growth.
259
+ if (input.state.lastTriggerAt === null) {
260
+ return trigger("threshold_reached", input.state, usage, config.thresholdPercent, nowMs);
261
+ }
262
+
263
+ const baselineTokens = input.state.repeatBaselineTokens
264
+ ?? input.state.lastTriggerTokens
265
+ ?? input.state.previousTokens
266
+ ?? usage.tokens;
267
+ const tokenGrowth = Math.max(0, usage.tokens - baselineTokens);
268
+
269
+ if (tokenGrowth < config.repeatMinGrowthTokens) {
270
+ return {
271
+ shouldTrigger: false,
272
+ reason: "repeat_growth_needed",
273
+ thresholdPercent: config.thresholdPercent,
274
+ usage,
275
+ tokenGrowth,
276
+ tokensUntilRepeat: config.repeatMinGrowthTokens - tokenGrowth,
277
+ state: nextState,
278
+ };
279
+ }
280
+
281
+ return trigger(
282
+ "repeat_growth_reached",
283
+ input.state,
284
+ usage,
285
+ config.thresholdPercent,
286
+ nowMs,
287
+ tokenGrowth,
288
+ );
289
+ }
290
+
291
+ export function markAutoCompactionComplete(state: AutoCompactionState): AutoCompactionState {
292
+ return {
293
+ ...state,
294
+ inFlight: false,
295
+ awaitingPostCompactionSample: true,
296
+ };
297
+ }
298
+
299
+ export function markAutoCompactionError(state: AutoCompactionState, nowMs?: number): AutoCompactionState {
300
+ return {
301
+ ...state,
302
+ inFlight: false,
303
+ awaitingPostCompactionSample: false,
304
+ lastTriggerAt: state.lastTriggerAt ?? nowMs ?? null,
305
+ };
306
+ }
@@ -116,6 +116,7 @@ export function migrateConfig(partial: Partial<CompactorConfig>): CompactorConfi
116
116
  sandboxExecution: mergeStrategy("sandboxExecution", defaults.sandboxExecution, partial.sandboxExecution),
117
117
  toolDisplay: mergeStrategy("toolDisplay", defaults.toolDisplay, partial.toolDisplay),
118
118
  pipeline: mergeStrategy("pipeline", defaults.pipeline, (partial as any).pipeline) as any,
119
+ autoCompaction: mergeStrategy("autoCompaction", defaults.autoCompaction, partial.autoCompaction),
119
120
  overrideDefaultCompaction: partial.overrideDefaultCompaction ?? defaults.overrideDefaultCompaction,
120
121
  debug: partial.debug ?? defaults.debug,
121
122
  showTruncationHints: partial.showTruncationHints ?? defaults.showTruncationHints,
@@ -6,6 +6,21 @@ import { createHash } from "node:crypto";
6
6
  import type { CompactorConfig, CompactorPreset } from "../types.js";
7
7
  import { DEFAULT_COMPACTOR_CONFIG } from "./schema.js";
8
8
 
9
+ const pipeline = (overrides: Partial<CompactorConfig["pipeline"]> = {}): CompactorConfig["pipeline"] => ({
10
+ ...DEFAULT_COMPACTOR_CONFIG.pipeline,
11
+ ...overrides,
12
+ customNoisePatterns: [...(overrides.customNoisePatterns ?? DEFAULT_COMPACTOR_CONFIG.pipeline.customNoisePatterns)],
13
+ });
14
+
15
+ const pipelineAllOn = (): CompactorConfig["pipeline"] => pipeline({
16
+ ttlCache: true,
17
+ autoInjection: true,
18
+ proximityReranking: true,
19
+ timelineSort: true,
20
+ progressiveThrottling: true,
21
+ mmapPragma: true,
22
+ });
23
+
9
24
  const preset = (
10
25
  overrides: Partial<CompactorConfig>,
11
26
  ): CompactorConfig => ({
@@ -21,6 +36,8 @@ const preset = (
21
36
  fts5Index: { ...DEFAULT_COMPACTOR_CONFIG.fts5Index, ...(overrides.fts5Index ?? {}) },
22
37
  sandboxExecution: { ...DEFAULT_COMPACTOR_CONFIG.sandboxExecution, ...(overrides.sandboxExecution ?? {}) },
23
38
  toolDisplay: { ...DEFAULT_COMPACTOR_CONFIG.toolDisplay, ...(overrides.toolDisplay ?? {}) },
39
+ pipeline: pipeline(overrides.pipeline),
40
+ autoCompaction: { ...DEFAULT_COMPACTOR_CONFIG.autoCompaction, ...(overrides.autoCompaction ?? {}) },
24
41
  });
25
42
 
26
43
  // Pipeline feature defaults per preset:
@@ -32,13 +49,17 @@ const preset = (
32
49
  export const PRESET_CONFIGS: Record<CompactorPreset, CompactorConfig> = {
33
50
  // New preset names
34
51
  precise: preset({
52
+ pipeline: pipeline({ ttlCache: true, mmapPragma: true }),
53
+ sandboxExecution: { ...DEFAULT_COMPACTOR_CONFIG.sandboxExecution, mode: "safe-only" },
35
54
  toolDisplay: { ...DEFAULT_COMPACTOR_CONFIG.toolDisplay, mode: "opencode" },
36
55
  }),
37
56
  thorough: preset({
57
+ pipeline: pipelineAllOn(),
38
58
  briefTranscript: { ...DEFAULT_COMPACTOR_CONFIG.briefTranscript, mode: "full" },
39
59
  toolDisplay: { ...DEFAULT_COMPACTOR_CONFIG.toolDisplay, mode: "verbose" },
40
60
  }),
41
61
  lean: preset({
62
+ pipeline: pipeline(),
42
63
  sessionGoals: { ...DEFAULT_COMPACTOR_CONFIG.sessionGoals, enabled: true, mode: "brief" },
43
64
  filesAndChanges: { ...DEFAULT_COMPACTOR_CONFIG.filesAndChanges, enabled: true, mode: "modified-only" },
44
65
  commits: { ...DEFAULT_COMPACTOR_CONFIG.commits, enabled: false, mode: "off" },
@@ -51,6 +72,7 @@ export const PRESET_CONFIGS: Record<CompactorPreset, CompactorConfig> = {
51
72
  toolDisplay: { ...DEFAULT_COMPACTOR_CONFIG.toolDisplay, enabled: true, mode: "opencode" },
52
73
  }),
53
74
  balanced: preset({
75
+ pipeline: pipelineAllOn(),
54
76
  briefTranscript: { ...DEFAULT_COMPACTOR_CONFIG.briefTranscript, mode: "compact" },
55
77
  toolDisplay: { ...DEFAULT_COMPACTOR_CONFIG.toolDisplay, mode: "balanced" },
56
78
  fts5Index: { ...DEFAULT_COMPACTOR_CONFIG.fts5Index, mode: "auto" },
@@ -58,13 +80,17 @@ export const PRESET_CONFIGS: Record<CompactorPreset, CompactorConfig> = {
58
80
 
59
81
  // Backward-compat aliases — map old names to new
60
82
  opencode: preset({
83
+ pipeline: pipeline({ ttlCache: true, mmapPragma: true }),
84
+ sandboxExecution: { ...DEFAULT_COMPACTOR_CONFIG.sandboxExecution, mode: "safe-only" },
61
85
  toolDisplay: { ...DEFAULT_COMPACTOR_CONFIG.toolDisplay, mode: "opencode" },
62
86
  }),
63
87
  verbose: preset({
88
+ pipeline: pipelineAllOn(),
64
89
  briefTranscript: { ...DEFAULT_COMPACTOR_CONFIG.briefTranscript, mode: "full" },
65
90
  toolDisplay: { ...DEFAULT_COMPACTOR_CONFIG.toolDisplay, mode: "verbose" },
66
91
  }),
67
92
  minimal: preset({
93
+ pipeline: pipeline(),
68
94
  sessionGoals: { ...DEFAULT_COMPACTOR_CONFIG.sessionGoals, enabled: true, mode: "brief" },
69
95
  filesAndChanges: { ...DEFAULT_COMPACTOR_CONFIG.filesAndChanges, enabled: true, mode: "modified-only" },
70
96
  commits: { ...DEFAULT_COMPACTOR_CONFIG.commits, enabled: false, mode: "off" },
@@ -58,6 +58,13 @@ export const DEFAULT_COMPACTOR_CONFIG: CompactorConfig = {
58
58
  mmapPragma: false,
59
59
  customNoisePatterns: [],
60
60
  },
61
+ autoCompaction: {
62
+ enabled: false,
63
+ thresholdPercent: 80,
64
+ cooldownMs: 60_000,
65
+ repeatMinGrowthTokens: 4_000,
66
+ notify: true,
67
+ },
61
68
  overrideDefaultCompaction: true,
62
69
  debug: false,
63
70
  showTruncationHints: true,