@pi-unipi/compactor 2.3.0 → 2.4.1
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 +18 -22
- package/package.json +3 -3
- package/src/compaction/auto-trigger.ts +0 -0
- package/src/compaction/build-sections.ts +2 -2
- package/src/compaction/format.ts +1 -1
- package/src/config/presets.ts +19 -30
- package/src/config/schema.ts +0 -0
- package/src/index.ts +39 -56
- package/src/session/resume-inject.ts +27 -0
- package/src/tools/ctx-batch-execute.ts +4 -2
- package/src/tools/ctx-execute-file.ts +4 -2
- package/src/tools/ctx-execute.ts +4 -2
- package/src/tools/register.ts +22 -4
- package/src/tui/settings-overlay.ts +9 -22
- package/src/types.ts +24 -2
- package/src/compaction/recall-scope.ts +0 -32
- package/src/compaction/sections.ts +0 -15
- package/src/display/bash-display.ts +0 -28
- package/src/display/diff-presentation.ts +0 -25
- package/src/display/pending-diff-preview.ts +0 -51
- package/src/display/user-message-box.ts +0 -16
- package/src/session/analytics.ts +0 -205
package/README.md
CHANGED
|
@@ -72,14 +72,14 @@ Keyboard:
|
|
|
72
72
|
|
|
73
73
|
### Presets tab
|
|
74
74
|
|
|
75
|
-
Presets are profiles that set strategies,
|
|
75
|
+
Presets are profiles that set strategies, the implemented Auto Injection pipeline behavior, and sandbox mode.
|
|
76
76
|
|
|
77
77
|
| Preset | Intended use | Important effects |
|
|
78
78
|
|---|---|---|
|
|
79
|
-
| `precise` | Code-heavy work, minimal waste | Full compaction sections,
|
|
80
|
-
| `balanced` | Daily/default profile | Compact transcript,
|
|
81
|
-
| `thorough` | Debug/audit sessions | Full transcript,
|
|
82
|
-
| `lean` | Quick fixes or short sessions | Brief/minimal compaction sections, no sandbox, no session continuity,
|
|
79
|
+
| `precise` | Code-heavy work, minimal waste | Full compaction sections, sandbox enabled, Auto Injection off. |
|
|
80
|
+
| `balanced` | Daily/default profile | Compact transcript, sandbox enabled, Auto Injection on. |
|
|
81
|
+
| `thorough` | Debug/audit sessions | Full transcript, sandbox enabled, Auto Injection on. |
|
|
82
|
+
| `lean` | Quick fixes or short sessions | Brief/minimal compaction sections, no sandbox, no session continuity, Auto Injection off. |
|
|
83
83
|
|
|
84
84
|
The **Project Override** row controls where settings are saved:
|
|
85
85
|
|
|
@@ -99,11 +99,10 @@ These settings control what is extracted into compaction summaries or how relate
|
|
|
99
99
|
| `Outstanding Context` | `full`, `critical-only`, `off` | Keeps blockers, TODOs, pending decisions, and follow-ups. |
|
|
100
100
|
| `User Preferences` | `all`, `recent-only`, `off` | Preserves user preferences learned during the session. |
|
|
101
101
|
| `Brief Transcript` | `full`, `compact`, `minimal`, `off` | Keeps a rolling recent transcript after the structured sections. |
|
|
102
|
-
| `Session Continuity` | `full`, `
|
|
103
|
-
| `Sandbox Execution` | `all`, `
|
|
104
|
-
| `Tool Display` | `opencode`, `balanced`, `verbose`, `custom` | Controls built-in tool output rendering style and diff display behavior. |
|
|
102
|
+
| `Session Continuity` | `full`, `off` | Controls XML-style resume snapshots. Off prevents snapshot creation and injection; Pi’s normal compaction summary still remains. |
|
|
103
|
+
| `Sandbox Execution` | `all`, `off` | Controls sandbox tool registration. Changes require a session reload. Allowed languages and output limit are enforced from config. |
|
|
105
104
|
|
|
106
|
-
Config-only legacy field: `fts5Index` remains in the schema for compatibility, but project indexing has moved to `@pi-unipi/cocoindex`.
|
|
105
|
+
Config-only legacy field: `fts5Index` remains in the schema for compatibility, but project indexing has moved to `@pi-unipi/cocoindex`. It is hidden from Compactor settings and presets. Legacy `sessionContinuity.mode: "essential-only"`, `eventCategories`, and `sandboxExecution.mode: "safe-only"` values remain loadable; essential-only behaves as full and safe-only behaves as enabled/all until explicit filtering/security policies are designed. Legacy `toolDisplay` and `showTruncationHints` fields also remain loadable but are deprecated and ignored; their profile names were never mapped to runtime renderer modes. Narrow-terminal diff clamping remains active as independent safety behavior.
|
|
107
106
|
|
|
108
107
|
### Auto tab
|
|
109
108
|
|
|
@@ -133,20 +132,15 @@ Example config:
|
|
|
133
132
|
|
|
134
133
|
### Pipeline tab
|
|
135
134
|
|
|
136
|
-
Pipeline
|
|
135
|
+
The Pipeline tab exposes only behavior implemented by the current runtime.
|
|
137
136
|
|
|
138
137
|
| Setting | Preset behavior | Current effect |
|
|
139
138
|
|---|---|---|
|
|
140
|
-
| `
|
|
141
|
-
| `Auto Injection` | On in `balanced`, `thorough`; off in `precise`, `lean` | Active. Adds behavioral/session state to the resume snapshot after compaction. |
|
|
142
|
-
| `MMap Pragma` | On in `precise`, `balanced`, `thorough`; off in `lean` | Reserved/compatibility switch for SQLite mmap tuning. |
|
|
143
|
-
| `Proximity Reranking` | On in `balanced`, `thorough`; off in `precise`, `lean` | Reserved/compatibility switch for future recall/search ranking. |
|
|
144
|
-
| `Timeline Sort` | On in `balanced`, `thorough`; off in `precise`, `lean` | Reserved/compatibility switch for future chronological recall sorting. |
|
|
145
|
-
| `Progressive Throttling` | On in `balanced`, `thorough`; off in `precise`, `lean` | Reserved/compatibility switch for future large-project throttling. |
|
|
139
|
+
| `Auto Injection` | On in `balanced`, `thorough`; off in `precise`, `lean` | Adds behavioral/session state to the one-shot resume context after compaction. |
|
|
146
140
|
|
|
147
|
-
|
|
141
|
+
Deprecated config-only compatibility fields are still accepted so existing files load without migration failures, but they are ignored and no longer controlled or advertised by presets: `ttlCache`, `mmapPragma`, `proximityReranking`, `timelineSort`, and `progressiveThrottling`.
|
|
148
142
|
|
|
149
|
-
|
|
143
|
+
Other config-only pipeline field:
|
|
150
144
|
|
|
151
145
|
| Field | Meaning |
|
|
152
146
|
|---|---|
|
|
@@ -289,10 +283,12 @@ Practical interpretation:
|
|
|
289
283
|
|
|
290
284
|
That means preset application is not updating `config.pipeline`. The intended behavior is:
|
|
291
285
|
|
|
292
|
-
- `precise`:
|
|
293
|
-
- `balanced`:
|
|
294
|
-
- `thorough`:
|
|
295
|
-
- `lean`:
|
|
286
|
+
- `precise`: Auto Injection off.
|
|
287
|
+
- `balanced`: Auto Injection on.
|
|
288
|
+
- `thorough`: Auto Injection on.
|
|
289
|
+
- `lean`: Auto Injection off.
|
|
290
|
+
|
|
291
|
+
The five deprecated compatibility fields remain off in every preset.
|
|
296
292
|
|
|
297
293
|
Make sure you are running a version with the preset pipeline fix, select the preset, and press **Enter** to save.
|
|
298
294
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/compactor",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.1",
|
|
4
4
|
"description": "Context engine for Pi — zero-LLM compaction, session continuity, sandbox execution, and tool display optimization",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
"access": "public"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@pi-unipi/core": "2.
|
|
38
|
-
"@pi-unipi/info-screen": "2.
|
|
37
|
+
"@pi-unipi/core": "2.4.1",
|
|
38
|
+
"@pi-unipi/info-screen": "2.4.1",
|
|
39
39
|
"@earendil-works/pi-agent-core": "^0.80.0"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
File without changes
|
|
@@ -9,8 +9,8 @@ import { extractFiles } from "./extract/files.js";
|
|
|
9
9
|
import { extractPreferences, dedupPreferencesAgainstGoals } from "./extract/preferences.js";
|
|
10
10
|
import { extractCommits, formatCommits } from "./extract/commits.js";
|
|
11
11
|
import { buildBriefSections, sectionsToTranscript, stringifyBrief } from "./brief.js";
|
|
12
|
-
import type { SectionData } from "
|
|
13
|
-
export type { SectionData } from "
|
|
12
|
+
import type { SectionData } from "../types.js";
|
|
13
|
+
export type { SectionData } from "../types.js";
|
|
14
14
|
|
|
15
15
|
const BLOCKER_RE =
|
|
16
16
|
/\b(fail(ed|s|ure|ing)?|broken|cannot|can't|won't work|does not work|doesn't work|still (broken|failing|wrong)|blocked|blocker|not (fixed|resolved|working)|crash(es|ed|ing)?)\b/i;
|
package/src/compaction/format.ts
CHANGED
package/src/config/presets.ts
CHANGED
|
@@ -12,13 +12,11 @@ const pipeline = (overrides: Partial<CompactorConfig["pipeline"]> = {}): Compact
|
|
|
12
12
|
customNoisePatterns: [...(overrides.customNoisePatterns ?? DEFAULT_COMPACTOR_CONFIG.pipeline.customNoisePatterns)],
|
|
13
13
|
});
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
// Auto Injection is the only preset-controlled pipeline feature with a runtime
|
|
16
|
+
// implementation. The other persisted fields are retained as ignored legacy
|
|
17
|
+
// compatibility data, but presets must not advertise or enable them.
|
|
18
|
+
const pipelineWithAutoInjection = (): CompactorConfig["pipeline"] => pipeline({
|
|
17
19
|
autoInjection: true,
|
|
18
|
-
proximityReranking: true,
|
|
19
|
-
timelineSort: true,
|
|
20
|
-
progressiveThrottling: true,
|
|
21
|
-
mmapPragma: true,
|
|
22
20
|
});
|
|
23
21
|
|
|
24
22
|
const preset = (
|
|
@@ -33,30 +31,29 @@ const preset = (
|
|
|
33
31
|
userPreferences: { ...DEFAULT_COMPACTOR_CONFIG.userPreferences, ...(overrides.userPreferences ?? {}) },
|
|
34
32
|
briefTranscript: { ...DEFAULT_COMPACTOR_CONFIG.briefTranscript, ...(overrides.briefTranscript ?? {}) },
|
|
35
33
|
sessionContinuity: { ...DEFAULT_COMPACTOR_CONFIG.sessionContinuity, ...(overrides.sessionContinuity ?? {}) },
|
|
36
|
-
|
|
34
|
+
// Legacy compatibility data only; presets never advertise or alter it.
|
|
35
|
+
fts5Index: { ...DEFAULT_COMPACTOR_CONFIG.fts5Index },
|
|
37
36
|
sandboxExecution: { ...DEFAULT_COMPACTOR_CONFIG.sandboxExecution, ...(overrides.sandboxExecution ?? {}) },
|
|
38
|
-
|
|
37
|
+
// Legacy compatibility data only; presets never advertise or alter it.
|
|
38
|
+
toolDisplay: { ...DEFAULT_COMPACTOR_CONFIG.toolDisplay },
|
|
39
39
|
pipeline: pipeline(overrides.pipeline),
|
|
40
40
|
autoCompaction: { ...DEFAULT_COMPACTOR_CONFIG.autoCompaction, ...(overrides.autoCompaction ?? {}) },
|
|
41
41
|
});
|
|
42
42
|
|
|
43
|
-
//
|
|
44
|
-
// precise:
|
|
45
|
-
// balanced:
|
|
46
|
-
//
|
|
47
|
-
// lean: all off
|
|
43
|
+
// Active pipeline behavior per preset:
|
|
44
|
+
// precise/lean: auto-injection off
|
|
45
|
+
// balanced/thorough: auto-injection on
|
|
46
|
+
// Reserved compatibility fields remain at their inert defaults in all presets.
|
|
48
47
|
|
|
49
48
|
export const PRESET_CONFIGS: Record<CompactorPreset, CompactorConfig> = {
|
|
50
49
|
// New preset names
|
|
51
50
|
precise: preset({
|
|
52
|
-
pipeline: pipeline(
|
|
53
|
-
sandboxExecution: { ...DEFAULT_COMPACTOR_CONFIG.sandboxExecution, mode: "
|
|
54
|
-
toolDisplay: { ...DEFAULT_COMPACTOR_CONFIG.toolDisplay, mode: "opencode" },
|
|
51
|
+
pipeline: pipeline(),
|
|
52
|
+
sandboxExecution: { ...DEFAULT_COMPACTOR_CONFIG.sandboxExecution, mode: "all" },
|
|
55
53
|
}),
|
|
56
54
|
thorough: preset({
|
|
57
|
-
pipeline:
|
|
55
|
+
pipeline: pipelineWithAutoInjection(),
|
|
58
56
|
briefTranscript: { ...DEFAULT_COMPACTOR_CONFIG.briefTranscript, mode: "full" },
|
|
59
|
-
toolDisplay: { ...DEFAULT_COMPACTOR_CONFIG.toolDisplay, mode: "verbose" },
|
|
60
57
|
}),
|
|
61
58
|
lean: preset({
|
|
62
59
|
pipeline: pipeline(),
|
|
@@ -67,27 +64,21 @@ export const PRESET_CONFIGS: Record<CompactorPreset, CompactorConfig> = {
|
|
|
67
64
|
userPreferences: { ...DEFAULT_COMPACTOR_CONFIG.userPreferences, enabled: false, mode: "off" },
|
|
68
65
|
briefTranscript: { ...DEFAULT_COMPACTOR_CONFIG.briefTranscript, enabled: true, mode: "minimal" },
|
|
69
66
|
sessionContinuity: { ...DEFAULT_COMPACTOR_CONFIG.sessionContinuity, enabled: false, mode: "off" },
|
|
70
|
-
fts5Index: { ...DEFAULT_COMPACTOR_CONFIG.fts5Index, enabled: false, mode: "off" },
|
|
71
67
|
sandboxExecution: { ...DEFAULT_COMPACTOR_CONFIG.sandboxExecution, enabled: false, mode: "off" },
|
|
72
|
-
toolDisplay: { ...DEFAULT_COMPACTOR_CONFIG.toolDisplay, enabled: true, mode: "opencode" },
|
|
73
68
|
}),
|
|
74
69
|
balanced: preset({
|
|
75
|
-
pipeline:
|
|
70
|
+
pipeline: pipelineWithAutoInjection(),
|
|
76
71
|
briefTranscript: { ...DEFAULT_COMPACTOR_CONFIG.briefTranscript, mode: "compact" },
|
|
77
|
-
toolDisplay: { ...DEFAULT_COMPACTOR_CONFIG.toolDisplay, mode: "balanced" },
|
|
78
|
-
fts5Index: { ...DEFAULT_COMPACTOR_CONFIG.fts5Index, mode: "auto" },
|
|
79
72
|
}),
|
|
80
73
|
|
|
81
74
|
// Backward-compat aliases — map old names to new
|
|
82
75
|
opencode: preset({
|
|
83
|
-
pipeline: pipeline(
|
|
84
|
-
sandboxExecution: { ...DEFAULT_COMPACTOR_CONFIG.sandboxExecution, mode: "
|
|
85
|
-
toolDisplay: { ...DEFAULT_COMPACTOR_CONFIG.toolDisplay, mode: "opencode" },
|
|
76
|
+
pipeline: pipeline(),
|
|
77
|
+
sandboxExecution: { ...DEFAULT_COMPACTOR_CONFIG.sandboxExecution, mode: "all" },
|
|
86
78
|
}),
|
|
87
79
|
verbose: preset({
|
|
88
|
-
pipeline:
|
|
80
|
+
pipeline: pipelineWithAutoInjection(),
|
|
89
81
|
briefTranscript: { ...DEFAULT_COMPACTOR_CONFIG.briefTranscript, mode: "full" },
|
|
90
|
-
toolDisplay: { ...DEFAULT_COMPACTOR_CONFIG.toolDisplay, mode: "verbose" },
|
|
91
82
|
}),
|
|
92
83
|
minimal: preset({
|
|
93
84
|
pipeline: pipeline(),
|
|
@@ -98,9 +89,7 @@ export const PRESET_CONFIGS: Record<CompactorPreset, CompactorConfig> = {
|
|
|
98
89
|
userPreferences: { ...DEFAULT_COMPACTOR_CONFIG.userPreferences, enabled: false, mode: "off" },
|
|
99
90
|
briefTranscript: { ...DEFAULT_COMPACTOR_CONFIG.briefTranscript, enabled: true, mode: "minimal" },
|
|
100
91
|
sessionContinuity: { ...DEFAULT_COMPACTOR_CONFIG.sessionContinuity, enabled: false, mode: "off" },
|
|
101
|
-
fts5Index: { ...DEFAULT_COMPACTOR_CONFIG.fts5Index, enabled: false, mode: "off" },
|
|
102
92
|
sandboxExecution: { ...DEFAULT_COMPACTOR_CONFIG.sandboxExecution, enabled: false, mode: "off" },
|
|
103
|
-
toolDisplay: { ...DEFAULT_COMPACTOR_CONFIG.toolDisplay, enabled: true, mode: "opencode" },
|
|
104
93
|
}),
|
|
105
94
|
custom: structuredClone(DEFAULT_COMPACTOR_CONFIG),
|
|
106
95
|
};
|
package/src/config/schema.ts
CHANGED
|
File without changes
|
package/src/index.ts
CHANGED
|
@@ -15,15 +15,14 @@ import {
|
|
|
15
15
|
} from "./compaction/auto-trigger.js";
|
|
16
16
|
import { SessionDB, getWorktreeSuffix } from "./session/db.js";
|
|
17
17
|
import { extractEventsFromToolResult } from "./session/extract.js";
|
|
18
|
-
import {
|
|
18
|
+
import { buildResumeContextMessage, isSessionContinuityEnabled } from "./session/resume-inject.js";
|
|
19
19
|
import { PolyglotExecutor } from "./executor/executor.js";
|
|
20
20
|
import { registerCommands } from "./commands/index.js";
|
|
21
21
|
import { registerCompactorTools } from "./tools/register.js";
|
|
22
22
|
import { normalizeMessages } from "./compaction/normalize.js";
|
|
23
23
|
import { filterNoise } from "./compaction/filter-noise.js";
|
|
24
24
|
import { recallBlocksFromContext } from "./session/recall-blocks.js";
|
|
25
|
-
import type { NormalizedBlock, CompactorStrategyConfig, RuntimeCounters } from "./types.js";
|
|
26
|
-
import type { RuntimeStats } from "./session/analytics.js";
|
|
25
|
+
import type { NormalizedBlock, CompactorStrategyConfig, RuntimeCounters, RuntimeStats } from "./types.js";
|
|
27
26
|
|
|
28
27
|
/** Debug logger — only logs when config.debug === true */
|
|
29
28
|
function createDebugLogger(getConfig: () => { debug: boolean }) {
|
|
@@ -63,11 +62,6 @@ function isSandboxTool(name: string): boolean {
|
|
|
63
62
|
return name === "bash" || name === "Bash";
|
|
64
63
|
}
|
|
65
64
|
|
|
66
|
-
/** Check if a tool is an index tool (content goes to FTS5, not context). Future-proofing. */
|
|
67
|
-
function isIndexTool(_name: string): boolean {
|
|
68
|
-
return false;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
65
|
export default function compactorExtension(pi: ExtensionAPI): void {
|
|
72
66
|
let sessionDB: SessionDB | null = null;
|
|
73
67
|
let executor: PolyglotExecutor | null = null;
|
|
@@ -86,7 +80,6 @@ export default function compactorExtension(pi: ExtensionAPI): void {
|
|
|
86
80
|
|
|
87
81
|
const runtimeStats: RuntimeStats = {
|
|
88
82
|
bytesReturned: {},
|
|
89
|
-
bytesIndexed: 0,
|
|
90
83
|
bytesSandboxed: 0,
|
|
91
84
|
calls: {},
|
|
92
85
|
sessionStart: Date.now(),
|
|
@@ -114,7 +107,7 @@ export default function compactorExtension(pi: ExtensionAPI): void {
|
|
|
114
107
|
sessionDB = null;
|
|
115
108
|
}
|
|
116
109
|
|
|
117
|
-
executor =
|
|
110
|
+
executor = null;
|
|
118
111
|
};
|
|
119
112
|
|
|
120
113
|
// Register compaction hooks with lazy deps — sessionDB/sessionId may not be
|
|
@@ -156,7 +149,6 @@ export default function compactorExtension(pi: ExtensionAPI): void {
|
|
|
156
149
|
|
|
157
150
|
// Reset runtime stats for new session
|
|
158
151
|
runtimeStats.bytesReturned = {};
|
|
159
|
-
runtimeStats.bytesIndexed = 0;
|
|
160
152
|
runtimeStats.bytesSandboxed = 0;
|
|
161
153
|
runtimeStats.calls = {};
|
|
162
154
|
runtimeStats.sessionStart = Date.now();
|
|
@@ -168,11 +160,19 @@ export default function compactorExtension(pi: ExtensionAPI): void {
|
|
|
168
160
|
|
|
169
161
|
// Register all compactor tools with Pi (deps now have live sessionDB)
|
|
170
162
|
if (sessionDB) {
|
|
163
|
+
const sandboxEnabled = config.sandboxExecution.enabled && config.sandboxExecution.mode !== "off";
|
|
164
|
+
executor = sandboxEnabled
|
|
165
|
+
? new PolyglotExecutor({ hardCapBytes: config.sandboxExecution.outputLimit, projectRoot: projectDir })
|
|
166
|
+
: null;
|
|
171
167
|
registerCompactorTools(pi, {
|
|
172
168
|
sessionDB,
|
|
173
169
|
getSessionId: () => currentSessionId,
|
|
174
170
|
getBlocks: () => cachedBlocks,
|
|
175
171
|
getCounters,
|
|
172
|
+
sandbox: executor ? {
|
|
173
|
+
executor,
|
|
174
|
+
allowedLanguages: config.sandboxExecution.allowedLanguages,
|
|
175
|
+
} : undefined,
|
|
176
176
|
});
|
|
177
177
|
}
|
|
178
178
|
|
|
@@ -277,25 +277,11 @@ export default function compactorExtension(pi: ExtensionAPI): void {
|
|
|
277
277
|
// Non-fatal: recall will work on empty blocks
|
|
278
278
|
}
|
|
279
279
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
if (snapshot && sessionDB) {
|
|
286
|
-
try {
|
|
287
|
-
const { buildAutoInjection } = await import("./session/auto-inject.js");
|
|
288
|
-
const events = sessionDB.getEvents(currentSessionId, { limit: 100 });
|
|
289
|
-
const autoInjection = buildAutoInjection(events);
|
|
290
|
-
if (autoInjection) {
|
|
291
|
-
debug("auto_injection", { tokens: autoInjection.tokens, length: autoInjection.text.length });
|
|
292
|
-
// Note: auto-injection is included in the resume snapshot context
|
|
293
|
-
// The model receives it as part of the session state restoration
|
|
294
|
-
}
|
|
295
|
-
} catch (err) {
|
|
296
|
-
debug("auto_injection_error", { error: String(err) });
|
|
297
|
-
}
|
|
298
|
-
}
|
|
280
|
+
const continuityEnabled = isSessionContinuityEnabled(config);
|
|
281
|
+
if (sessionDB && continuityEnabled) {
|
|
282
|
+
const resumeContext = await buildResumeContextMessage(sessionDB, currentSessionId);
|
|
283
|
+
debug("resume_snapshot", { injected: !!resumeContext });
|
|
284
|
+
return resumeContext;
|
|
299
285
|
}
|
|
300
286
|
});
|
|
301
287
|
|
|
@@ -358,7 +344,8 @@ export default function compactorExtension(pi: ExtensionAPI): void {
|
|
|
358
344
|
});
|
|
359
345
|
|
|
360
346
|
pi.on("session_before_compact", async (event, _ctx) => {
|
|
361
|
-
|
|
347
|
+
const continuityEnabled = isSessionContinuityEnabled(config);
|
|
348
|
+
if (sessionDB && continuityEnabled) {
|
|
362
349
|
// Use closure currentSessionId — Pi's session_before_compact event
|
|
363
350
|
// does not include sessionId at the top level.
|
|
364
351
|
const sessionId = currentSessionId;
|
|
@@ -386,14 +373,21 @@ export default function compactorExtension(pi: ExtensionAPI): void {
|
|
|
386
373
|
const compactionEntry = (event as any).compactionEntry;
|
|
387
374
|
const tokensBefore = compactionEntry?.tokensBefore ?? 0;
|
|
388
375
|
|
|
376
|
+
let summarized = 0;
|
|
377
|
+
let kept = 0;
|
|
378
|
+
let tokensSaved = 0;
|
|
379
|
+
|
|
389
380
|
if (tokensBefore > 0) {
|
|
390
381
|
// Use actual token count from Pi's compactionEntry.
|
|
391
382
|
// Compaction typically keeps ~10-15% of original context.
|
|
392
383
|
const charsBefore = tokensBefore * 4;
|
|
393
|
-
const tokensAfter =
|
|
384
|
+
const tokensAfter = Math.round(tokensBefore * 0.12);
|
|
385
|
+
kept = tokensAfter;
|
|
386
|
+
tokensSaved = tokensBefore - tokensAfter;
|
|
394
387
|
const charsKept = tokensAfter * 4;
|
|
395
388
|
const messagesSummarized = Math.max(1, Math.round(tokensBefore / 500));
|
|
396
|
-
|
|
389
|
+
summarized = messagesSummarized;
|
|
390
|
+
counters.totalTokensCompacted += tokensSaved;
|
|
397
391
|
sessionDB.addCompactionStats(sessionId, charsBefore, charsKept, messagesSummarized);
|
|
398
392
|
} else {
|
|
399
393
|
// tokensBefore unavailable — use session event count as a rough heuristic.
|
|
@@ -407,13 +401,24 @@ export default function compactorExtension(pi: ExtensionAPI): void {
|
|
|
407
401
|
const estTokensAfter = Math.round(estTokensBefore * 0.12);
|
|
408
402
|
const charsBefore = estTokensBefore * 4;
|
|
409
403
|
const charsKept = estTokensAfter * 4;
|
|
410
|
-
|
|
404
|
+
summarized = eventCount;
|
|
405
|
+
kept = estTokensAfter;
|
|
406
|
+
tokensSaved = estTokensBefore - estTokensAfter;
|
|
407
|
+
counters.totalTokensCompacted += tokensSaved;
|
|
411
408
|
sessionDB.addCompactionStats(sessionId, charsBefore, charsKept, eventCount);
|
|
412
409
|
}
|
|
413
410
|
} catch {
|
|
414
411
|
// Non-fatal: heuristic estimation failed
|
|
415
412
|
}
|
|
416
413
|
}
|
|
414
|
+
const totalEstimated = tokensSaved + kept;
|
|
415
|
+
emitEvent(pi, UNIPI_EVENTS.COMPACTOR_COMPACTED, {
|
|
416
|
+
sessionId,
|
|
417
|
+
summarized,
|
|
418
|
+
kept,
|
|
419
|
+
tokensSaved,
|
|
420
|
+
compressionRatio: kept > 0 ? `${Math.round(totalEstimated / kept)}:1` : "0:1",
|
|
421
|
+
});
|
|
417
422
|
debug("session_compact", { sessionId, tokensBefore, hasCompactionEntry: !!compactionEntry });
|
|
418
423
|
}
|
|
419
424
|
});
|
|
@@ -531,34 +536,12 @@ export default function compactorExtension(pi: ExtensionAPI): void {
|
|
|
531
536
|
if (isSandboxTool(tName)) {
|
|
532
537
|
runtimeStats.bytesSandboxed += responseBytes;
|
|
533
538
|
}
|
|
534
|
-
if (isIndexTool(tName)) {
|
|
535
|
-
runtimeStats.bytesIndexed += responseBytes;
|
|
536
|
-
}
|
|
537
539
|
}
|
|
538
540
|
} catch {
|
|
539
541
|
// Non-blocking: byte tracking errors silently skipped
|
|
540
542
|
}
|
|
541
543
|
|
|
542
|
-
// Apply display overrides for built-in tools
|
|
543
544
|
const toolName = (event as any).toolName ?? "";
|
|
544
|
-
const td = config.toolDisplay;
|
|
545
|
-
const toolConfig = {
|
|
546
|
-
readOutputMode: td?.mode as any,
|
|
547
|
-
searchOutputMode: td?.mode as any,
|
|
548
|
-
bashOutputMode: td?.mode as any,
|
|
549
|
-
previewLines: 20,
|
|
550
|
-
bashCollapsedLines: 5,
|
|
551
|
-
showTruncationHints: true,
|
|
552
|
-
};
|
|
553
|
-
try {
|
|
554
|
-
const { applyToolDisplayOverride } = await import("./display/tool-overrides.js");
|
|
555
|
-
const override = applyToolDisplayOverride(toolName, event as any, toolConfig);
|
|
556
|
-
if (override !== undefined) {
|
|
557
|
-
return override as any;
|
|
558
|
-
}
|
|
559
|
-
} catch {
|
|
560
|
-
// Non-fatal: display override failed
|
|
561
|
-
}
|
|
562
545
|
|
|
563
546
|
// Width-safe diff truncation for edit/write tool results.
|
|
564
547
|
// Pi's renderDiff() does not truncate lines to terminal width,
|
|
@@ -2,10 +2,16 @@
|
|
|
2
2
|
* Resume injection — inject snapshot into session context post-compaction
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import type { BeforeAgentStartEventResult } from "@earendil-works/pi-coding-agent";
|
|
5
6
|
import type { SessionDB } from "./db.js";
|
|
6
7
|
import { buildResumeSnapshot } from "./snapshot.js";
|
|
7
8
|
import { buildAutoInjection } from "./auto-inject.js";
|
|
8
9
|
import { loadConfig } from "../config/manager.js";
|
|
10
|
+
import type { CompactorConfig } from "../types.js";
|
|
11
|
+
|
|
12
|
+
export function isSessionContinuityEnabled(config: CompactorConfig): boolean {
|
|
13
|
+
return config.sessionContinuity.enabled && config.sessionContinuity.mode !== "off";
|
|
14
|
+
}
|
|
9
15
|
|
|
10
16
|
export async function injectResumeSnapshot(
|
|
11
17
|
db: SessionDB,
|
|
@@ -35,3 +41,24 @@ export async function injectResumeSnapshot(
|
|
|
35
41
|
db.markResumeConsumed(sessionId);
|
|
36
42
|
return fullSnapshot;
|
|
37
43
|
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Build the one-shot hidden context message used on the first turn after
|
|
47
|
+
* compaction. Keeping this out of the system prompt preserves Pi's stable
|
|
48
|
+
* prompt/history prefix for provider cache reuse.
|
|
49
|
+
*/
|
|
50
|
+
export async function buildResumeContextMessage(
|
|
51
|
+
db: SessionDB,
|
|
52
|
+
sessionId: string,
|
|
53
|
+
): Promise<BeforeAgentStartEventResult | undefined> {
|
|
54
|
+
const snapshot = await injectResumeSnapshot(db, sessionId);
|
|
55
|
+
if (!snapshot) return undefined;
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
message: {
|
|
59
|
+
customType: "unipi-compactor-resume",
|
|
60
|
+
content: snapshot,
|
|
61
|
+
display: false,
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -20,9 +20,11 @@ export interface BatchResult {
|
|
|
20
20
|
>;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
export async function ctxBatchExecute(
|
|
23
|
+
export async function ctxBatchExecute(
|
|
24
|
+
items: BatchItem[],
|
|
25
|
+
executor = new PolyglotExecutor(),
|
|
26
|
+
): Promise<BatchResult> {
|
|
24
27
|
const results: BatchResult["results"] = [];
|
|
25
|
-
const executor = new PolyglotExecutor();
|
|
26
28
|
|
|
27
29
|
for (const item of items) {
|
|
28
30
|
if (item.type === "execute") {
|
|
@@ -12,11 +12,13 @@ export interface CtxExecuteFileInput {
|
|
|
12
12
|
timeout?: number;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
export async function ctxExecuteFile(
|
|
15
|
+
export async function ctxExecuteFile(
|
|
16
|
+
input: CtxExecuteFileInput,
|
|
17
|
+
executor = new PolyglotExecutor(),
|
|
18
|
+
): Promise<ExecResult> {
|
|
16
19
|
const content = readFileSync(input.path, "utf-8");
|
|
17
20
|
const code = `const FILE_CONTENT = ${JSON.stringify(content)};\n// User script follows:\n`;
|
|
18
21
|
|
|
19
|
-
const executor = new PolyglotExecutor();
|
|
20
22
|
return executor.executeFile({
|
|
21
23
|
language: input.language,
|
|
22
24
|
path: input.path,
|
package/src/tools/ctx-execute.ts
CHANGED
|
@@ -11,8 +11,10 @@ export interface CtxExecuteInput {
|
|
|
11
11
|
timeout?: number;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
export async function ctxExecute(
|
|
15
|
-
|
|
14
|
+
export async function ctxExecute(
|
|
15
|
+
input: CtxExecuteInput,
|
|
16
|
+
executor = new PolyglotExecutor(),
|
|
17
|
+
): Promise<ExecResult> {
|
|
16
18
|
return executor.execute({
|
|
17
19
|
language: input.language,
|
|
18
20
|
code: input.code,
|
package/src/tools/register.ts
CHANGED
|
@@ -27,7 +27,8 @@ import { recallBlocksFromContext } from "../session/recall-blocks.js";
|
|
|
27
27
|
import { filterNoise } from "../compaction/filter-noise.js";
|
|
28
28
|
import { loadConfig } from "../config/manager.js";
|
|
29
29
|
import type { SessionDB } from "../session/db.js";
|
|
30
|
-
import type { NormalizedBlock, RuntimeCounters } from "../types.js";
|
|
30
|
+
import type { Language, NormalizedBlock, RuntimeCounters } from "../types.js";
|
|
31
|
+
import type { PolyglotExecutor } from "../executor/executor.js";
|
|
31
32
|
|
|
32
33
|
// --- TypeBox Schemas for each tool ---
|
|
33
34
|
|
|
@@ -127,6 +128,10 @@ export interface CompactorToolDeps {
|
|
|
127
128
|
getSessionId: () => string;
|
|
128
129
|
getBlocks: () => NormalizedBlock[];
|
|
129
130
|
getCounters?: () => RuntimeCounters;
|
|
131
|
+
sandbox?: {
|
|
132
|
+
executor: PolyglotExecutor;
|
|
133
|
+
allowedLanguages: Language[];
|
|
134
|
+
};
|
|
130
135
|
}
|
|
131
136
|
|
|
132
137
|
/**
|
|
@@ -189,13 +194,23 @@ export function registerCompactorTools(pi: ExtensionAPI, deps: CompactorToolDeps
|
|
|
189
194
|
pi.registerTool({ name: "session_recall", label: "Session Recall", description: "Search session history using BM25 or regex. Find previous goals, files, commits, and context.", parameters: RecallParams, execute: recallExec } as any);
|
|
190
195
|
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);
|
|
191
196
|
|
|
197
|
+
// Sandbox tools are session-scoped and only registered when enabled.
|
|
198
|
+
if (deps.sandbox) {
|
|
199
|
+
const { executor, allowedLanguages } = deps.sandbox;
|
|
200
|
+
const assertAllowedLanguage = (language: Language): void => {
|
|
201
|
+
if (!allowedLanguages.includes(language)) {
|
|
202
|
+
throw new Error(`Language "${language}" is disabled by Compactor sandbox settings. Allowed: ${allowedLanguages.join(", ") || "none"}`);
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
|
|
192
206
|
// 3. sandbox (new) / ctx_execute (deprecated) — run code in sandbox
|
|
193
207
|
const sandboxExec = async (_toolCallId: string, params: any): Promise<import("@earendil-works/pi-coding-agent").AgentToolResult<unknown>> => {
|
|
194
208
|
try {
|
|
195
209
|
const c = deps.getCounters?.();
|
|
196
210
|
if (c) { c.sandboxRuns++; }
|
|
197
211
|
deps.sessionDB.incrementSandboxRuns(deps.getSessionId());
|
|
198
|
-
|
|
212
|
+
assertAllowedLanguage(params.language as Language);
|
|
213
|
+
const result = await ctxExecute(params as CtxExecuteInput, executor);
|
|
199
214
|
const parts: string[] = [];
|
|
200
215
|
if (result.stdout) parts.push(result.stdout);
|
|
201
216
|
if (result.stderr) parts.push(`[stderr] ${result.stderr}`);
|
|
@@ -215,7 +230,8 @@ export function registerCompactorTools(pi: ExtensionAPI, deps: CompactorToolDeps
|
|
|
215
230
|
const c = deps.getCounters?.();
|
|
216
231
|
if (c) { c.sandboxRuns++; }
|
|
217
232
|
deps.sessionDB.incrementSandboxRuns(deps.getSessionId());
|
|
218
|
-
|
|
233
|
+
assertAllowedLanguage(params.language as Language);
|
|
234
|
+
const result = await ctxExecuteFile(params as CtxExecuteFileInput, executor);
|
|
219
235
|
const parts: string[] = [];
|
|
220
236
|
if (result.stdout) parts.push(result.stdout);
|
|
221
237
|
if (result.stderr) parts.push(`[stderr] ${result.stderr}`);
|
|
@@ -234,7 +250,8 @@ export function registerCompactorTools(pi: ExtensionAPI, deps: CompactorToolDeps
|
|
|
234
250
|
const c = deps.getCounters?.();
|
|
235
251
|
if (c) { c.sandboxRuns++; }
|
|
236
252
|
deps.sessionDB.incrementSandboxRuns(deps.getSessionId());
|
|
237
|
-
const
|
|
253
|
+
for (const item of params.items as BatchItem[]) assertAllowedLanguage(item.language);
|
|
254
|
+
const result = await ctxBatchExecute(params.items as BatchItem[], executor);
|
|
238
255
|
const summaries = result.results.map((r, i) => {
|
|
239
256
|
const s = r.result.stdout?.slice(0, 200) || "(no output)";
|
|
240
257
|
return `[${i}] execute → ${r.result.exitCode === 0 ? "ok" : "fail"}: ${s}`;
|
|
@@ -246,6 +263,7 @@ export function registerCompactorTools(pi: ExtensionAPI, deps: CompactorToolDeps
|
|
|
246
263
|
};
|
|
247
264
|
pi.registerTool({ name: "sandbox_batch", label: "Sandbox Batch", description: "Run multiple code executions atomically as a batch.", parameters: SandboxBatchParams, execute: sandboxBatchExec } as any);
|
|
248
265
|
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);
|
|
266
|
+
}
|
|
249
267
|
|
|
250
268
|
// 6. compactor_stats (new) / ctx_stats (deprecated) — context savings dashboard
|
|
251
269
|
const statsExec = async (): Promise<import("@earendil-works/pi-coding-agent").AgentToolResult<unknown>> => {
|
|
@@ -122,7 +122,7 @@ const STRATEGIES: StrategyDef[] = [
|
|
|
122
122
|
key: "sessionContinuity",
|
|
123
123
|
label: "Session Continuity",
|
|
124
124
|
description: "XML resume snapshot for compaction survival",
|
|
125
|
-
modes: ["full", "
|
|
125
|
+
modes: ["full", "off"],
|
|
126
126
|
getEnabled: (c) => c.sessionContinuity.enabled,
|
|
127
127
|
setEnabled: (c, v) => (c.sessionContinuity.enabled = v),
|
|
128
128
|
getMode: (c) => c.sessionContinuity.mode,
|
|
@@ -132,31 +132,18 @@ const STRATEGIES: StrategyDef[] = [
|
|
|
132
132
|
key: "sandboxExecution",
|
|
133
133
|
label: "Sandbox Execution",
|
|
134
134
|
description: "Polyglot code execution",
|
|
135
|
-
modes: ["all", "
|
|
135
|
+
modes: ["all", "off"],
|
|
136
136
|
getEnabled: (c) => c.sandboxExecution.enabled,
|
|
137
137
|
setEnabled: (c, v) => (c.sandboxExecution.enabled = v),
|
|
138
138
|
getMode: (c) => c.sandboxExecution.mode,
|
|
139
139
|
setMode: (c, v) => (c.sandboxExecution.mode = v as any),
|
|
140
140
|
},
|
|
141
|
-
{
|
|
142
|
-
key: "toolDisplay",
|
|
143
|
-
label: "Tool Display",
|
|
144
|
-
description: "Override tool output rendering",
|
|
145
|
-
modes: ["opencode", "balanced", "verbose", "custom"],
|
|
146
|
-
getEnabled: (c) => c.toolDisplay.enabled,
|
|
147
|
-
setEnabled: (c, v) => (c.toolDisplay.enabled = v),
|
|
148
|
-
getMode: (c) => c.toolDisplay.mode,
|
|
149
|
-
setMode: (c, v) => (c.toolDisplay.mode = v as any),
|
|
150
|
-
},
|
|
151
141
|
];
|
|
152
142
|
|
|
153
143
|
const PIPELINE_ITEMS: PipelineDef[] = [
|
|
154
|
-
|
|
144
|
+
// Only expose implemented behavior. Reserved compatibility fields remain in
|
|
145
|
+
// the persisted schema so existing config files continue to load.
|
|
155
146
|
{ 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) },
|
|
156
|
-
{ 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) },
|
|
157
|
-
{ 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) },
|
|
158
|
-
{ 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) },
|
|
159
|
-
{ 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) },
|
|
160
147
|
];
|
|
161
148
|
|
|
162
149
|
const PRESETS: CompactorPreset[] = ["precise", "balanced", "thorough", "lean"];
|
|
@@ -167,20 +154,20 @@ const REPEAT_GROWTH_VALUES = ["0", "1k", "4k", "8k", "16k", "32k"];
|
|
|
167
154
|
|
|
168
155
|
const PRESET_DESCRIPTIONS: Record<string, { summary: string; detail: string }> = {
|
|
169
156
|
precise: {
|
|
170
|
-
summary: "Code-heavy, minimal waste — compaction: full, sandbox:
|
|
171
|
-
detail: "Max token savings. Compaction: full
|
|
157
|
+
summary: "Code-heavy, minimal waste — compaction: full, sandbox: all",
|
|
158
|
+
detail: "Max token savings. Compaction: full.\nSandbox: all. Auto injection: off.",
|
|
172
159
|
},
|
|
173
160
|
balanced: {
|
|
174
161
|
summary: "Daily use (default) — all strategies moderate",
|
|
175
|
-
detail: "Moderate all strategies
|
|
162
|
+
detail: "Moderate all strategies.\nSandbox: all. Auto injection: on.",
|
|
176
163
|
},
|
|
177
164
|
thorough: {
|
|
178
165
|
summary: "Debug/audit — everything on, full transcript",
|
|
179
|
-
detail: "Everything enabled
|
|
166
|
+
detail: "Everything enabled.\nSandbox: all. Auto injection: on.",
|
|
180
167
|
},
|
|
181
168
|
lean: {
|
|
182
169
|
summary: "Quick fixes, short sessions — compaction only",
|
|
183
|
-
detail: "Compaction only
|
|
170
|
+
detail: "Compaction only.\nSandbox: off. Auto injection: off.",
|
|
184
171
|
},
|
|
185
172
|
};
|
|
186
173
|
|
package/src/types.ts
CHANGED
|
@@ -49,6 +49,16 @@ export interface BriefLine {
|
|
|
49
49
|
lines: string[];
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
/** Runtime stats tracked during a live session. */
|
|
53
|
+
export interface RuntimeStats {
|
|
54
|
+
bytesReturned: Record<string, number>;
|
|
55
|
+
bytesSandboxed: number;
|
|
56
|
+
calls: Record<string, number>;
|
|
57
|
+
sessionStart: number;
|
|
58
|
+
cacheHits: number;
|
|
59
|
+
cacheBytesSaved: number;
|
|
60
|
+
}
|
|
61
|
+
|
|
52
62
|
// ─────────────────────────────────────────────────────────
|
|
53
63
|
// Compaction input / output
|
|
54
64
|
// ─────────────────────────────────────────────────────────
|
|
@@ -110,18 +120,29 @@ export interface CompactorConfig {
|
|
|
110
120
|
outstandingContext: CompactorStrategyConfig & { mode: "full" | "critical-only" | "off"; maxItems: number };
|
|
111
121
|
userPreferences: CompactorStrategyConfig & { mode: "all" | "recent-only" | "off"; maxPreferences: number };
|
|
112
122
|
briefTranscript: CompactorStrategyConfig & { mode: "full" | "compact" | "minimal" | "off"; userTokenLimit: number; assistantTokenLimit: number; toolCallLimit: number };
|
|
113
|
-
sessionContinuity: CompactorStrategyConfig & {
|
|
123
|
+
sessionContinuity: CompactorStrategyConfig & {
|
|
124
|
+
mode: "full" | "essential-only" | "off";
|
|
125
|
+
/** @deprecated Category filtering was never implemented; ignored by runtime. */
|
|
126
|
+
eventCategories: string[];
|
|
127
|
+
};
|
|
128
|
+
/** @deprecated Project indexing moved to @pi-unipi/cocoindex; retained for config compatibility. */
|
|
114
129
|
fts5Index: CompactorStrategyConfig & { mode: "auto" | "manual" | "off"; chunkSize: number; cacheTtlHours: number };
|
|
115
|
-
sandboxExecution: CompactorStrategyConfig & { mode: "all" | "safe-only" | "off"; allowedLanguages:
|
|
130
|
+
sandboxExecution: CompactorStrategyConfig & { mode: "all" | "safe-only" | "off"; allowedLanguages: Language[]; outputLimit: number };
|
|
131
|
+
/** @deprecated Display profiles were never connected to runtime; retained for config compatibility. */
|
|
116
132
|
toolDisplay: CompactorStrategyConfig & { mode: "opencode" | "balanced" | "verbose" | "custom"; diffLayout: "auto" | "split" | "unified"; diffIndicator: "bars" | "classic" | "none"; showThinkingLabels: boolean; showUserMessageBox: boolean; showBashSpinner: boolean; showPendingPreviews: boolean };
|
|
117
133
|
|
|
118
134
|
// Pipeline features
|
|
119
135
|
pipeline: {
|
|
136
|
+
/** @deprecated Reserved compatibility field; ignored by runtime. */
|
|
120
137
|
ttlCache: boolean;
|
|
121
138
|
autoInjection: boolean;
|
|
139
|
+
/** @deprecated Reserved compatibility field; ignored by runtime. */
|
|
122
140
|
proximityReranking: boolean;
|
|
141
|
+
/** @deprecated Reserved compatibility field; ignored by runtime. */
|
|
123
142
|
timelineSort: boolean;
|
|
143
|
+
/** @deprecated Reserved compatibility field; ignored by runtime. */
|
|
124
144
|
progressiveThrottling: boolean;
|
|
145
|
+
/** @deprecated Reserved compatibility field; ignored by runtime. */
|
|
125
146
|
mmapPragma: boolean;
|
|
126
147
|
customNoisePatterns: string[];
|
|
127
148
|
};
|
|
@@ -132,6 +153,7 @@ export interface CompactorConfig {
|
|
|
132
153
|
// Global settings
|
|
133
154
|
overrideDefaultCompaction: boolean;
|
|
134
155
|
debug: boolean;
|
|
156
|
+
/** @deprecated Truncation hints were never connected to runtime; retained for config compatibility. */
|
|
135
157
|
showTruncationHints: boolean;
|
|
136
158
|
}
|
|
137
159
|
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Recall scope — lineage filtering for vcc_recall
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export interface LineageRange {
|
|
6
|
-
startIndex: number;
|
|
7
|
-
endIndex: number;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Determine the valid message index range for recall based on
|
|
12
|
-
* the most recent compaction boundary.
|
|
13
|
-
*/
|
|
14
|
-
export function getRecallScope(
|
|
15
|
-
branchEntries: Array<{ type: string; [key: string]: unknown }>,
|
|
16
|
-
opts?: { expand?: boolean },
|
|
17
|
-
): LineageRange {
|
|
18
|
-
let lastCompactionIdx = -1;
|
|
19
|
-
for (let i = branchEntries.length - 1; i >= 0; i--) {
|
|
20
|
-
if (branchEntries[i].type === "compaction") {
|
|
21
|
-
lastCompactionIdx = i;
|
|
22
|
-
break;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// If expand is true, include everything; otherwise only post-compaction
|
|
27
|
-
const startIndex = opts?.expand ? 0 : lastCompactionIdx + 1;
|
|
28
|
-
return {
|
|
29
|
-
startIndex: Math.max(0, startIndex),
|
|
30
|
-
endIndex: branchEntries.length,
|
|
31
|
-
};
|
|
32
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Section data type for compaction pipeline
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import type { TranscriptEntry } from "../types.js";
|
|
6
|
-
|
|
7
|
-
export interface SectionData {
|
|
8
|
-
sessionGoal: string[];
|
|
9
|
-
filesAndChanges: string[];
|
|
10
|
-
commits: string[];
|
|
11
|
-
outstandingContext: string[];
|
|
12
|
-
userPreferences: string[];
|
|
13
|
-
briefTranscript: string;
|
|
14
|
-
transcriptEntries: TranscriptEntry[];
|
|
15
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Bash display — spinner + elapsed time
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
6
|
-
|
|
7
|
-
export function getSpinnerFrame(index: number): string {
|
|
8
|
-
return SPINNER_FRAMES[index % SPINNER_FRAMES.length];
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function formatElapsedTime(ms: number): string {
|
|
12
|
-
if (ms < 1000) return `${ms}ms`;
|
|
13
|
-
if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
|
|
14
|
-
const mins = Math.floor(ms / 60000);
|
|
15
|
-
const secs = ((ms % 60000) / 1000).toFixed(0);
|
|
16
|
-
return `${mins}m${secs}s`;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function renderBashCall(command: string, opts?: { collapsed?: boolean }): string {
|
|
20
|
-
if (opts?.collapsed) {
|
|
21
|
-
const firstLine = command.split("\n")[0]?.trim() ?? command;
|
|
22
|
-
if (firstLine.length > 80) {
|
|
23
|
-
return firstLine.slice(0, 77) + "...";
|
|
24
|
-
}
|
|
25
|
-
return firstLine;
|
|
26
|
-
}
|
|
27
|
-
return command;
|
|
28
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Diff presentation — layout selection based on terminal width
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import type { DiffLayout } from "../types.js";
|
|
6
|
-
import { visibleWidth, truncateToWidth } from "@earendil-works/pi-tui";
|
|
7
|
-
|
|
8
|
-
export function selectDiffLayout(
|
|
9
|
-
terminalWidth: number,
|
|
10
|
-
preferred: DiffLayout = "auto",
|
|
11
|
-
): "split" | "unified" {
|
|
12
|
-
if (preferred !== "auto") return preferred;
|
|
13
|
-
return terminalWidth >= 100 ? "split" : "unified";
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/** Clamp text to maxWidth visible columns, ANSI-aware */
|
|
17
|
-
export function clampWidth(text: string, maxWidth: number): string {
|
|
18
|
-
return text
|
|
19
|
-
.split("\n")
|
|
20
|
-
.map((line) => {
|
|
21
|
-
if (visibleWidth(line) <= maxWidth) return line;
|
|
22
|
-
return truncateToWidth(line, maxWidth, "…");
|
|
23
|
-
})
|
|
24
|
-
.join("\n");
|
|
25
|
-
}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pending diff previews during streaming
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export interface PendingDiffPreviewData {
|
|
6
|
-
type: "edit" | "write" | "create";
|
|
7
|
-
filePath: string;
|
|
8
|
-
previousContent?: string;
|
|
9
|
-
newContent: string;
|
|
10
|
-
confidence: number;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function buildPendingEditPreviewData(
|
|
14
|
-
filePath: string,
|
|
15
|
-
oldText: string,
|
|
16
|
-
newText: string,
|
|
17
|
-
): PendingDiffPreviewData {
|
|
18
|
-
return {
|
|
19
|
-
type: "edit",
|
|
20
|
-
filePath,
|
|
21
|
-
previousContent: oldText,
|
|
22
|
-
newContent: newText,
|
|
23
|
-
confidence: 1.0,
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function buildPendingWritePreviewData(
|
|
28
|
-
filePath: string,
|
|
29
|
-
content: string,
|
|
30
|
-
fileExisted: boolean,
|
|
31
|
-
): PendingDiffPreviewData {
|
|
32
|
-
return {
|
|
33
|
-
type: fileExisted ? "write" : "create",
|
|
34
|
-
filePath,
|
|
35
|
-
newContent: content,
|
|
36
|
-
confidence: 1.0,
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export function renderPendingDiffPreview(data: PendingDiffPreviewData, opts?: { maxLines?: number }): string {
|
|
41
|
-
const maxLines = opts?.maxLines ?? 20;
|
|
42
|
-
const lines = data.newContent.split("\n").slice(0, maxLines);
|
|
43
|
-
const header = data.type === "edit" ? `✏️ Edit: ${data.filePath}`
|
|
44
|
-
: data.type === "write" ? `📝 Write: ${data.filePath}`
|
|
45
|
-
: `📄 Create: ${data.filePath}`;
|
|
46
|
-
|
|
47
|
-
const omitted = data.newContent.split("\n").length - lines.length;
|
|
48
|
-
const hint = omitted > 0 ? `\n...(${omitted} more lines)...` : "";
|
|
49
|
-
|
|
50
|
-
return `${header}\n${"─".repeat(header.length)}\n${lines.join("\n")}${hint}`;
|
|
51
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* User message box — bordered display for user messages
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export function renderUserMessageBox(text: string, opts?: { maxWidth?: number }): string {
|
|
6
|
-
const maxWidth = opts?.maxWidth ?? 80;
|
|
7
|
-
const lines = text.split("\n");
|
|
8
|
-
const clamped = lines.map((l) => (l.length > maxWidth - 4 ? l.slice(0, maxWidth - 7) + "..." : l));
|
|
9
|
-
const width = Math.min(maxWidth, Math.max(...clamped.map((l) => l.length), 10) + 4);
|
|
10
|
-
|
|
11
|
-
const top = `╭${"─".repeat(width - 2)}╮`;
|
|
12
|
-
const bottom = `╰${"─".repeat(width - 2)}╯`;
|
|
13
|
-
const middle = clamped.map((l) => `│ ${l.padEnd(width - 4)} │`);
|
|
14
|
-
|
|
15
|
-
return [top, ...middle, bottom].join("\n");
|
|
16
|
-
}
|
package/src/session/analytics.ts
DELETED
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AnalyticsEngine — Runtime savings + session continuity reporting.
|
|
3
|
-
*
|
|
4
|
-
* Ported from context-mode's AnalyticsEngine, trimmed to budget-focused stats.
|
|
5
|
-
* Omits: formatReport(), categoryLabels, categoryHints, ThinkInCodeComparison,
|
|
6
|
-
* SandboxIO, dataBar(), visual formatting helpers.
|
|
7
|
-
*
|
|
8
|
-
* Usage:
|
|
9
|
-
* const engine = new AnalyticsEngine(sessionDb);
|
|
10
|
-
* const report = engine.queryAll(runtimeStats);
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
// ─────────────────────────────────────────────────────────
|
|
14
|
-
// Types
|
|
15
|
-
// ─────────────────────────────────────────────────────────
|
|
16
|
-
|
|
17
|
-
/** Database adapter — anything with a prepare() method (better-sqlite3, bun:sqlite, etc.) */
|
|
18
|
-
export interface DatabaseAdapter {
|
|
19
|
-
prepare(sql: string): {
|
|
20
|
-
run(...params: unknown[]): unknown;
|
|
21
|
-
get(...params: unknown[]): unknown;
|
|
22
|
-
all(...params: unknown[]): unknown[];
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/** Context savings result */
|
|
27
|
-
export interface ContextSavings {
|
|
28
|
-
rawBytes: number;
|
|
29
|
-
contextBytes: number;
|
|
30
|
-
savedBytes: number;
|
|
31
|
-
savedPercent: number;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/** Runtime stats tracked during a live session. */
|
|
35
|
-
export interface RuntimeStats {
|
|
36
|
-
bytesReturned: Record<string, number>;
|
|
37
|
-
bytesIndexed: number;
|
|
38
|
-
bytesSandboxed: number;
|
|
39
|
-
calls: Record<string, number>;
|
|
40
|
-
sessionStart: number;
|
|
41
|
-
cacheHits: number;
|
|
42
|
-
cacheBytesSaved: number;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/** Unified report combining runtime stats, DB analytics, and continuity data. */
|
|
46
|
-
export interface FullReport {
|
|
47
|
-
/** Runtime context savings */
|
|
48
|
-
savings: {
|
|
49
|
-
processed_kb: number;
|
|
50
|
-
entered_kb: number;
|
|
51
|
-
saved_kb: number;
|
|
52
|
-
pct: number;
|
|
53
|
-
savings_ratio: number;
|
|
54
|
-
by_tool: Array<{ tool: string; calls: number; context_kb: number; tokens: number }>;
|
|
55
|
-
total_calls: number;
|
|
56
|
-
total_bytes_returned: number;
|
|
57
|
-
kept_out: number;
|
|
58
|
-
total_processed: number;
|
|
59
|
-
};
|
|
60
|
-
/** Session metadata from SessionDB */
|
|
61
|
-
session: {
|
|
62
|
-
id: string;
|
|
63
|
-
uptime_min: string;
|
|
64
|
-
};
|
|
65
|
-
/** Session continuity data */
|
|
66
|
-
continuity: {
|
|
67
|
-
total_events: number;
|
|
68
|
-
compact_count: number;
|
|
69
|
-
all_time_compact_count: number;
|
|
70
|
-
resume_ready: boolean;
|
|
71
|
-
};
|
|
72
|
-
/** Persistent project memory — all events across all sessions */
|
|
73
|
-
projectMemory: {
|
|
74
|
-
total_events: number;
|
|
75
|
-
session_count: number;
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// ─────────────────────────────────────────────────────────
|
|
80
|
-
// AnalyticsEngine
|
|
81
|
-
// ─────────────────────────────────────────────────────────
|
|
82
|
-
|
|
83
|
-
export class AnalyticsEngine {
|
|
84
|
-
private readonly db: DatabaseAdapter;
|
|
85
|
-
|
|
86
|
-
constructor(db: DatabaseAdapter) {
|
|
87
|
-
this.db = db;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Build a FullReport by merging runtime stats (passed in)
|
|
92
|
-
* with continuity data from the DB.
|
|
93
|
-
*/
|
|
94
|
-
queryAll(runtimeStats: RuntimeStats): FullReport {
|
|
95
|
-
// ── Resolve latest session ID ──
|
|
96
|
-
const latestSession = this.db.prepare(
|
|
97
|
-
"SELECT session_id FROM session_meta ORDER BY started_at DESC LIMIT 1",
|
|
98
|
-
).get() as { session_id: string } | undefined;
|
|
99
|
-
const sid = latestSession?.session_id ?? "";
|
|
100
|
-
|
|
101
|
-
// ── Runtime savings ──
|
|
102
|
-
const totalBytesReturned = Object.values(runtimeStats.bytesReturned).reduce(
|
|
103
|
-
(sum, b) => sum + b, 0,
|
|
104
|
-
);
|
|
105
|
-
const totalCalls = Object.values(runtimeStats.calls).reduce(
|
|
106
|
-
(sum, c) => sum + c, 0,
|
|
107
|
-
);
|
|
108
|
-
const keptOut = runtimeStats.bytesIndexed + runtimeStats.bytesSandboxed;
|
|
109
|
-
const totalProcessed = keptOut + totalBytesReturned;
|
|
110
|
-
const savingsRatio = totalProcessed / Math.max(totalBytesReturned, 1);
|
|
111
|
-
const reductionPct = totalProcessed > 0
|
|
112
|
-
? Math.round((1 - totalBytesReturned / totalProcessed) * 100)
|
|
113
|
-
: 0;
|
|
114
|
-
|
|
115
|
-
const toolNames = new Set([
|
|
116
|
-
...Object.keys(runtimeStats.calls),
|
|
117
|
-
...Object.keys(runtimeStats.bytesReturned),
|
|
118
|
-
]);
|
|
119
|
-
const byTool = Array.from(toolNames).sort().map((tool) => ({
|
|
120
|
-
tool,
|
|
121
|
-
calls: runtimeStats.calls[tool] || 0,
|
|
122
|
-
context_kb: Math.round((runtimeStats.bytesReturned[tool] || 0) / 1024 * 10) / 10,
|
|
123
|
-
tokens: Math.round((runtimeStats.bytesReturned[tool] || 0) / 4),
|
|
124
|
-
}));
|
|
125
|
-
|
|
126
|
-
const uptimeMs = Date.now() - runtimeStats.sessionStart;
|
|
127
|
-
const uptimeMin = (uptimeMs / 60_000).toFixed(1);
|
|
128
|
-
|
|
129
|
-
// ── Continuity data (scoped to current session) ──
|
|
130
|
-
const eventTotal = (this.db.prepare(
|
|
131
|
-
"SELECT COUNT(*) as cnt FROM session_events WHERE session_id = ?",
|
|
132
|
-
).get(sid) as { cnt: number }).cnt;
|
|
133
|
-
|
|
134
|
-
const meta = this.db.prepare(
|
|
135
|
-
"SELECT compact_count FROM session_meta WHERE session_id = ?",
|
|
136
|
-
).get(sid) as { compact_count: number } | undefined;
|
|
137
|
-
const compactCount = meta?.compact_count ?? 0;
|
|
138
|
-
|
|
139
|
-
// ── All-time compaction count across all sessions ──
|
|
140
|
-
const allTimeCompactions = (this.db.prepare(
|
|
141
|
-
"SELECT COALESCE(SUM(compact_count), 0) as total FROM session_meta",
|
|
142
|
-
).get() as { total: number }).total;
|
|
143
|
-
|
|
144
|
-
const resume = this.db.prepare(
|
|
145
|
-
"SELECT event_count, consumed FROM session_resume WHERE session_id = ? ORDER BY created_at DESC LIMIT 1",
|
|
146
|
-
).get(sid) as { event_count: number; consumed: number } | undefined;
|
|
147
|
-
const resumeReady = resume ? !resume.consumed : false;
|
|
148
|
-
|
|
149
|
-
// ── Project-wide persistent memory (all sessions, no session_id filter) ──
|
|
150
|
-
const projectTotals = this.db.prepare(
|
|
151
|
-
"SELECT COUNT(*) as cnt, COUNT(DISTINCT session_id) as sessions FROM session_events",
|
|
152
|
-
).get() as { cnt: number; sessions: number };
|
|
153
|
-
|
|
154
|
-
return {
|
|
155
|
-
savings: {
|
|
156
|
-
processed_kb: Math.round(totalProcessed / 1024 * 10) / 10,
|
|
157
|
-
entered_kb: Math.round(totalBytesReturned / 1024 * 10) / 10,
|
|
158
|
-
saved_kb: Math.round(keptOut / 1024 * 10) / 10,
|
|
159
|
-
pct: reductionPct,
|
|
160
|
-
savings_ratio: Math.round(savingsRatio * 10) / 10,
|
|
161
|
-
by_tool: byTool,
|
|
162
|
-
total_calls: totalCalls,
|
|
163
|
-
total_bytes_returned: totalBytesReturned,
|
|
164
|
-
kept_out: keptOut,
|
|
165
|
-
total_processed: totalProcessed,
|
|
166
|
-
},
|
|
167
|
-
session: {
|
|
168
|
-
id: sid,
|
|
169
|
-
uptime_min: uptimeMin,
|
|
170
|
-
},
|
|
171
|
-
continuity: {
|
|
172
|
-
total_events: eventTotal,
|
|
173
|
-
compact_count: compactCount,
|
|
174
|
-
all_time_compact_count: allTimeCompactions,
|
|
175
|
-
resume_ready: resumeReady,
|
|
176
|
-
},
|
|
177
|
-
projectMemory: {
|
|
178
|
-
total_events: projectTotals.cnt,
|
|
179
|
-
session_count: projectTotals.sessions,
|
|
180
|
-
},
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// ─────────────────────────────────────────────────────────
|
|
186
|
-
// createMinimalDb — in-memory SQLite fallback
|
|
187
|
-
// ─────────────────────────────────────────────────────────
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* Create a minimal in-memory DatabaseAdapter for when SessionDB is unavailable.
|
|
191
|
-
* Returns zeroed/empty results for all queries.
|
|
192
|
-
*/
|
|
193
|
-
export function createMinimalDb(): DatabaseAdapter {
|
|
194
|
-
// Use an in-memory SQLite database with the expected schema
|
|
195
|
-
// so AnalyticsEngine queries don't fail.
|
|
196
|
-
const emptyStmt = {
|
|
197
|
-
run: (..._params: unknown[]) => {},
|
|
198
|
-
get: (..._params: unknown[]) => ({ cnt: 0, sessions: 0, compact_count: 0, total: 0, session_id: "", event_count: 0, consumed: 1 }),
|
|
199
|
-
all: (..._params: unknown[]) => [] as unknown[],
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
return {
|
|
203
|
-
prepare: (_sql: string) => emptyStmt,
|
|
204
|
-
};
|
|
205
|
-
}
|