@pi-unipi/unipi 2.0.2 → 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.
- package/README.md +10 -3
- package/package.json +2 -2
- package/packages/ask-user/README.md +4 -0
- package/packages/ask-user/ask-ui.ts +97 -90
- package/packages/ask-user/tools.ts +12 -10
- package/packages/autocomplete/README.md +36 -0
- package/packages/compactor/README.md +290 -73
- package/packages/compactor/skills/compactor/SKILL.md +2 -3
- package/packages/compactor/skills/compactor-detail/SKILL.md +49 -64
- package/packages/compactor/skills/compactor-doctor/SKILL.md +28 -31
- package/packages/compactor/skills/compactor-stats/SKILL.md +22 -20
- package/packages/compactor/src/commands/index.ts +4 -1
- package/packages/compactor/src/compaction/auto-trigger.ts +306 -0
- package/packages/compactor/src/config/manager.ts +1 -0
- package/packages/compactor/src/config/presets.ts +26 -0
- package/packages/compactor/src/config/schema.ts +7 -0
- package/packages/compactor/src/index.ts +74 -1
- package/packages/compactor/src/tools/context-budget.ts +18 -2
- package/packages/compactor/src/tools/register.ts +19 -11
- package/packages/compactor/src/tui/settings-overlay.ts +142 -3
- package/packages/compactor/src/types.ts +17 -0
- package/packages/core/events.ts +2 -0
- package/packages/notify/README.md +2 -2
- package/packages/notify/commands.ts +9 -4
- package/packages/notify/events.ts +12 -2
- package/packages/notify/platforms/focus-win.ts +123 -0
- package/packages/notify/platforms/focus.ts +33 -0
- package/packages/notify/platforms/native.ts +33 -1
- package/packages/notify/settings.ts +1 -0
- package/packages/notify/tui/settings-overlay.ts +33 -7
- package/packages/notify/types.ts +8 -0
- package/packages/workflow/README.md +2 -0
- package/packages/workflow/commands.ts +28 -11
|
@@ -1,55 +1,53 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: compactor-doctor
|
|
3
|
-
description: Diagnostics — validate config, DB,
|
|
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` —
|
|
13
|
-
- `
|
|
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
|
|
20
|
-
| **Session DB** | SQLite connection works
|
|
21
|
-
| **
|
|
22
|
-
| **Runtime:
|
|
23
|
-
| **Runtime:
|
|
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
|
|
30
|
-
- ❌ **fail** — critical issue
|
|
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
|
-
|
|
36
|
-
-
|
|
37
|
-
-
|
|
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
|
-
|
|
45
|
-
-
|
|
46
|
-
-
|
|
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
|
-
|
|
51
|
-
-
|
|
52
|
-
-
|
|
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
|
|
64
|
+
### Check runtimes
|
|
67
65
|
```bash
|
|
68
|
-
|
|
66
|
+
node --version
|
|
67
|
+
python3 --version
|
|
68
|
+
bash --version
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
-
|
|
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,
|
|
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` —
|
|
13
|
-
- `
|
|
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** |
|
|
20
|
-
| **Compactions** | Number of
|
|
21
|
-
| **Tokens saved** | Estimated tokens freed by compaction |
|
|
22
|
-
| **
|
|
23
|
-
| **
|
|
24
|
-
| **
|
|
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 =
|
|
30
|
-
- **
|
|
31
|
-
- **Sandbox runs** =
|
|
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
|
-
|
|
47
|
-
-
|
|
48
|
-
-
|
|
49
|
-
- 7
|
|
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,
|