@chendpoc/pi-memory 0.1.11 → 0.1.13
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 +88 -16
- package/dist/cache/memoryCaches.d.ts +32 -0
- package/dist/cache/memoryCaches.d.ts.map +1 -0
- package/dist/cache/memoryCaches.js +59 -0
- package/dist/cache/memoryCaches.js.map +1 -0
- package/dist/cli.js +123 -0
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +28 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +37 -1
- package/dist/config.js.map +1 -1
- package/dist/consolidation/enqueue.d.ts +33 -0
- package/dist/consolidation/enqueue.d.ts.map +1 -0
- package/dist/consolidation/enqueue.js +145 -0
- package/dist/consolidation/enqueue.js.map +1 -0
- package/dist/consolidation/index.d.ts +19 -0
- package/dist/consolidation/index.d.ts.map +1 -0
- package/dist/consolidation/index.js +15 -0
- package/dist/consolidation/index.js.map +1 -0
- package/dist/consolidation/lock.d.ts +5 -0
- package/dist/consolidation/lock.d.ts.map +1 -0
- package/dist/consolidation/lock.js +33 -0
- package/dist/consolidation/lock.js.map +1 -0
- package/dist/consolidation/log.d.ts +8 -0
- package/dist/consolidation/log.d.ts.map +1 -0
- package/dist/consolidation/log.js +32 -0
- package/dist/consolidation/log.js.map +1 -0
- package/dist/consolidation/memoryIndex.d.ts +18 -0
- package/dist/consolidation/memoryIndex.d.ts.map +1 -0
- package/dist/consolidation/memoryIndex.js +100 -0
- package/dist/consolidation/memoryIndex.js.map +1 -0
- package/dist/consolidation/phase2/runPhase2.d.ts +23 -0
- package/dist/consolidation/phase2/runPhase2.d.ts.map +1 -0
- package/dist/consolidation/phase2/runPhase2.js +196 -0
- package/dist/consolidation/phase2/runPhase2.js.map +1 -0
- package/dist/consolidation/scheduler/launchd.d.ts +5 -0
- package/dist/consolidation/scheduler/launchd.d.ts.map +1 -0
- package/dist/consolidation/scheduler/launchd.js +54 -0
- package/dist/consolidation/scheduler/launchd.js.map +1 -0
- package/dist/consolidation/scheduler/runConsolidate.d.ts +26 -0
- package/dist/consolidation/scheduler/runConsolidate.d.ts.map +1 -0
- package/dist/consolidation/scheduler/runConsolidate.js +83 -0
- package/dist/consolidation/scheduler/runConsolidate.js.map +1 -0
- package/dist/consolidation/scheduler/setupSchedule.d.ts +3 -0
- package/dist/consolidation/scheduler/setupSchedule.d.ts.map +1 -0
- package/dist/consolidation/scheduler/setupSchedule.js +79 -0
- package/dist/consolidation/scheduler/setupSchedule.js.map +1 -0
- package/dist/consolidation/scheduler/systemd.d.ts +9 -0
- package/dist/consolidation/scheduler/systemd.d.ts.map +1 -0
- package/dist/consolidation/scheduler/systemd.js +57 -0
- package/dist/consolidation/scheduler/systemd.js.map +1 -0
- package/dist/consolidation/scheduler/types.d.ts +24 -0
- package/dist/consolidation/scheduler/types.d.ts.map +1 -0
- package/dist/consolidation/scheduler/types.js +2 -0
- package/dist/consolidation/scheduler/types.js.map +1 -0
- package/dist/consolidation/scope.d.ts +12 -0
- package/dist/consolidation/scope.d.ts.map +1 -0
- package/dist/consolidation/scope.js +51 -0
- package/dist/consolidation/scope.js.map +1 -0
- package/dist/consolidation/stage1/deltaExtract.d.ts +3 -0
- package/dist/consolidation/stage1/deltaExtract.d.ts.map +1 -0
- package/dist/consolidation/stage1/deltaExtract.js +19 -0
- package/dist/consolidation/stage1/deltaExtract.js.map +1 -0
- package/dist/consolidation/stage1/drainQueue.d.ts +21 -0
- package/dist/consolidation/stage1/drainQueue.d.ts.map +1 -0
- package/dist/consolidation/stage1/drainQueue.js +113 -0
- package/dist/consolidation/stage1/drainQueue.js.map +1 -0
- package/dist/consolidation/stage1/extractSession.d.ts +6 -0
- package/dist/consolidation/stage1/extractSession.d.ts.map +1 -0
- package/dist/consolidation/stage1/extractSession.js +47 -0
- package/dist/consolidation/stage1/extractSession.js.map +1 -0
- package/dist/consolidation/stage1/store.d.ts +45 -0
- package/dist/consolidation/stage1/store.d.ts.map +1 -0
- package/dist/consolidation/stage1/store.js +297 -0
- package/dist/consolidation/stage1/store.js.map +1 -0
- package/dist/consolidation/types.d.ts +49 -0
- package/dist/consolidation/types.d.ts.map +1 -0
- package/dist/consolidation/types.js +2 -0
- package/dist/consolidation/types.js.map +1 -0
- package/dist/fallback/llmRerank.d.ts.map +1 -1
- package/dist/fallback/llmRerank.js +9 -1
- package/dist/fallback/llmRerank.js.map +1 -1
- package/dist/fallback/sessionIndex.d.ts.map +1 -1
- package/dist/fallback/sessionIndex.js +15 -63
- package/dist/fallback/sessionIndex.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/local/graphQuery.d.ts +10 -0
- package/dist/local/graphQuery.d.ts.map +1 -1
- package/dist/local/graphQuery.js +25 -0
- package/dist/local/graphQuery.js.map +1 -1
- package/dist/pi-extension.d.ts.map +1 -1
- package/dist/pi-extension.js +356 -22
- package/dist/pi-extension.js.map +1 -1
- package/dist/preflight/detectIntents.d.ts.map +1 -1
- package/dist/preflight/detectIntents.js +7 -0
- package/dist/preflight/detectIntents.js.map +1 -1
- package/dist/preflight/hook.d.ts +18 -0
- package/dist/preflight/hook.d.ts.map +1 -1
- package/dist/preflight/hook.js +52 -7
- package/dist/preflight/hook.js.map +1 -1
- package/dist/preflight/render.d.ts +16 -2
- package/dist/preflight/render.d.ts.map +1 -1
- package/dist/preflight/render.js +24 -4
- package/dist/preflight/render.js.map +1 -1
- package/dist/service.d.ts +16 -0
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +57 -0
- package/dist/service.js.map +1 -1
- package/dist/session/activeBranch.d.ts +17 -0
- package/dist/session/activeBranch.d.ts.map +1 -0
- package/dist/session/activeBranch.js +251 -0
- package/dist/session/activeBranch.js.map +1 -0
- package/dist/settings.d.ts +5 -2
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +18 -1
- package/dist/settings.js.map +1 -1
- package/dist/tools/memoryAppend.d.ts +14 -4
- package/dist/tools/memoryAppend.d.ts.map +1 -1
- package/dist/tools/memoryAppend.js +46 -7
- package/dist/tools/memoryAppend.js.map +1 -1
- package/dist/tools/memoryRecall.d.ts +2 -2
- package/dist/tools/memoryRecall.d.ts.map +1 -1
- package/dist/tools/memoryRecall.js +35 -9
- package/dist/tools/memoryRecall.js.map +1 -1
- package/dist/trainer/extractFacts.d.ts +1 -1
- package/dist/trainer/index.d.ts +1 -1
- package/dist/trainer/index.d.ts.map +1 -1
- package/dist/trainer/index.js +1 -1
- package/dist/trainer/index.js.map +1 -1
- package/dist/trainer/scheduler.d.ts +2 -0
- package/dist/trainer/scheduler.d.ts.map +1 -1
- package/dist/trainer/scheduler.js +1 -0
- package/dist/trainer/scheduler.js.map +1 -1
- package/dist/trainer/sessionLoader.d.ts +3 -0
- package/dist/trainer/sessionLoader.d.ts.map +1 -1
- package/dist/trainer/sessionLoader.js +37 -74
- package/dist/trainer/sessionLoader.js.map +1 -1
- package/package.json +17 -4
- package/src/cache/memoryCaches.ts +72 -0
- package/src/cli.ts +144 -0
- package/src/config.ts +89 -1
- package/src/consolidation/enqueue.ts +205 -0
- package/src/consolidation/index.ts +90 -0
- package/src/consolidation/lock.ts +41 -0
- package/src/consolidation/log.ts +43 -0
- package/src/consolidation/memoryIndex.ts +127 -0
- package/src/consolidation/phase2/runPhase2.ts +255 -0
- package/src/consolidation/scheduler/launchd.ts +62 -0
- package/src/consolidation/scheduler/runConsolidate.ts +115 -0
- package/src/consolidation/scheduler/setupSchedule.ts +106 -0
- package/src/consolidation/scheduler/systemd.ts +78 -0
- package/src/consolidation/scheduler/types.ts +27 -0
- package/src/consolidation/scope.ts +58 -0
- package/src/consolidation/stage1/deltaExtract.ts +29 -0
- package/src/consolidation/stage1/drainQueue.ts +143 -0
- package/src/consolidation/stage1/extractSession.ts +58 -0
- package/src/consolidation/stage1/store.ts +411 -0
- package/src/consolidation/types.ts +52 -0
- package/src/fallback/llmRerank.ts +8 -1
- package/src/fallback/sessionIndex.ts +14 -51
- package/src/index.ts +8 -0
- package/src/local/graphQuery.ts +24 -0
- package/src/pi-extension.ts +393 -28
- package/src/preflight/detectIntents.ts +6 -0
- package/src/preflight/hook.ts +84 -7
- package/src/preflight/render.ts +35 -3
- package/src/service.ts +57 -0
- package/src/session/activeBranch.ts +294 -0
- package/src/settings.ts +37 -2
- package/src/tools/memoryAppend.ts +60 -7
- package/src/tools/memoryRecall.ts +33 -9
- package/src/trainer/index.ts +1 -1
- package/src/trainer/scheduler.ts +3 -0
- package/src/trainer/sessionLoader.ts +38 -87
package/src/cli.ts
CHANGED
|
@@ -5,6 +5,15 @@ import path from "node:path";
|
|
|
5
5
|
import { createStandaloneLLMClient } from "./adapters/piComplete.js";
|
|
6
6
|
import { installBundle } from "./bundle/install.js";
|
|
7
7
|
import type { MemoryConfig } from "./config.js";
|
|
8
|
+
import { getMemoryIndexStats } from "./consolidation/memoryIndex.js";
|
|
9
|
+
import { readRecentConsolidationLogs } from "./consolidation/log.js";
|
|
10
|
+
import { getConsolidationStatus } from "./consolidation/enqueue.js";
|
|
11
|
+
import {
|
|
12
|
+
defaultConsolidationDbPath,
|
|
13
|
+
runConsolidate,
|
|
14
|
+
} from "./consolidation/scheduler/runConsolidate.js";
|
|
15
|
+
import { setupSchedule } from "./consolidation/scheduler/setupSchedule.js";
|
|
16
|
+
import type { SchedulePlatform } from "./consolidation/scheduler/types.js";
|
|
8
17
|
import { loadMemoryConfig } from "./settings.js";
|
|
9
18
|
import { openSessionIndex } from "./fallback/sessionIndex.js";
|
|
10
19
|
import { SidecarClient } from "./sidecar/client.js";
|
|
@@ -73,6 +82,55 @@ async function main(): Promise<void> {
|
|
|
73
82
|
return;
|
|
74
83
|
}
|
|
75
84
|
|
|
85
|
+
if (cmd === "memory-status") {
|
|
86
|
+
const dbPath = defaultConsolidationDbPath(cfg);
|
|
87
|
+
const status = getConsolidationStatus(dbPath);
|
|
88
|
+
const memoryIndex = getMemoryIndexStats(cfg.memoryMdPaths, {
|
|
89
|
+
maxLines: cfg.consolidation.memory_index_max_lines,
|
|
90
|
+
maxBytes: cfg.consolidation.memory_index_max_bytes,
|
|
91
|
+
});
|
|
92
|
+
const recentLogs = await readRecentConsolidationLogs(
|
|
93
|
+
cfg.consolidation.schedule.log_path,
|
|
94
|
+
5,
|
|
95
|
+
);
|
|
96
|
+
const schedule = await readScheduleStatus(cfg);
|
|
97
|
+
console.log(JSON.stringify({
|
|
98
|
+
db_path: dbPath,
|
|
99
|
+
queue: status,
|
|
100
|
+
memory_index: memoryIndex,
|
|
101
|
+
schedule,
|
|
102
|
+
recent_logs: recentLogs,
|
|
103
|
+
}, null, 2));
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (cmd === "consolidate") {
|
|
108
|
+
const flags = parseConsolidateFlags(rest);
|
|
109
|
+
const result = await runConsolidate({
|
|
110
|
+
config: cfg,
|
|
111
|
+
dryRun: flags.dryRun,
|
|
112
|
+
phase1Only: flags.phase1Only,
|
|
113
|
+
phase2Only: flags.phase2Only,
|
|
114
|
+
});
|
|
115
|
+
console.log(JSON.stringify(result, null, 2));
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (cmd === "setup-schedule") {
|
|
120
|
+
const flags = parseScheduleFlags(rest);
|
|
121
|
+
const platform = resolveSchedulePlatform();
|
|
122
|
+
const result = await setupSchedule({
|
|
123
|
+
hour: flags.hour ?? cfg.consolidation.schedule.hour,
|
|
124
|
+
minute: flags.minute ?? cfg.consolidation.schedule.minute,
|
|
125
|
+
logPath: cfg.consolidation.schedule.log_path,
|
|
126
|
+
dryRun: flags.dryRun,
|
|
127
|
+
remove: flags.remove,
|
|
128
|
+
status: flags.status,
|
|
129
|
+
}, platform);
|
|
130
|
+
console.log(JSON.stringify(result, null, 2));
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
76
134
|
if (cmd === "install-bundle") {
|
|
77
135
|
const bundlePath = rest[0];
|
|
78
136
|
if (!bundlePath) {
|
|
@@ -214,12 +272,98 @@ function parseTrainFlags(args: string[]): {
|
|
|
214
272
|
return { sessionsDir, full, dryRun, noMerge, extractor, watch, model };
|
|
215
273
|
}
|
|
216
274
|
|
|
275
|
+
function parseConsolidateFlags(args: string[]): {
|
|
276
|
+
dryRun: boolean;
|
|
277
|
+
phase1Only: boolean;
|
|
278
|
+
phase2Only: boolean;
|
|
279
|
+
} {
|
|
280
|
+
return {
|
|
281
|
+
dryRun: args.includes("--dry-run"),
|
|
282
|
+
phase1Only: args.includes("--phase1-only"),
|
|
283
|
+
phase2Only: args.includes("--phase2-only"),
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function parseScheduleFlags(args: string[]): {
|
|
288
|
+
hour?: number;
|
|
289
|
+
minute?: number;
|
|
290
|
+
dryRun: boolean;
|
|
291
|
+
remove: boolean;
|
|
292
|
+
status: boolean;
|
|
293
|
+
} {
|
|
294
|
+
let hour: number | undefined;
|
|
295
|
+
let minute: number | undefined;
|
|
296
|
+
for (let i = 0; i < args.length; i++) {
|
|
297
|
+
const arg = args[i]!;
|
|
298
|
+
if (arg === "--hour" && i + 1 < args.length) {
|
|
299
|
+
hour = Number(args[++i]);
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
if (arg === "--minute" && i + 1 < args.length) {
|
|
303
|
+
minute = Number(args[++i]);
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return {
|
|
308
|
+
hour,
|
|
309
|
+
minute,
|
|
310
|
+
dryRun: args.includes("--dry-run"),
|
|
311
|
+
remove: args.includes("--remove"),
|
|
312
|
+
status: args.includes("--status"),
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function resolveSchedulePlatform(): SchedulePlatform {
|
|
317
|
+
if (process.platform === "darwin") return "darwin";
|
|
318
|
+
if (process.platform === "linux") return "linux";
|
|
319
|
+
throw new Error(`setup-schedule is not supported on ${process.platform}`);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function tryResolveSchedulePlatform(): SchedulePlatform | null {
|
|
323
|
+
if (process.platform === "darwin") return "darwin";
|
|
324
|
+
if (process.platform === "linux") return "linux";
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
async function readScheduleStatus(cfg: MemoryConfig) {
|
|
329
|
+
const platform = tryResolveSchedulePlatform();
|
|
330
|
+
if (!platform) {
|
|
331
|
+
return {
|
|
332
|
+
supported: false,
|
|
333
|
+
platform: process.platform,
|
|
334
|
+
files: [],
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
const result = await setupSchedule({
|
|
338
|
+
hour: cfg.consolidation.schedule.hour,
|
|
339
|
+
minute: cfg.consolidation.schedule.minute,
|
|
340
|
+
logPath: cfg.consolidation.schedule.log_path,
|
|
341
|
+
status: true,
|
|
342
|
+
}, platform);
|
|
343
|
+
return {
|
|
344
|
+
supported: true,
|
|
345
|
+
platform,
|
|
346
|
+
files: result.files,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
|
|
217
350
|
function printHelp(): void {
|
|
218
351
|
console.log(`pi-memory — local TLM episodic memory (mode B)
|
|
219
352
|
|
|
220
353
|
Commands:
|
|
221
354
|
health Start sidecar (if bundle present) and print /health
|
|
222
355
|
status Print MemoryService status snapshot
|
|
356
|
+
memory-status Print consolidation queue, MEMORY.md cap, schedule status, and recent job logs
|
|
357
|
+
consolidate Run offline memory consolidation
|
|
358
|
+
--dry-run Report planned work without writing MEMORY.md/stage1
|
|
359
|
+
--phase1-only Drain queue and train graph only
|
|
360
|
+
--phase2-only Consume existing stage1 rows only
|
|
361
|
+
setup-schedule Install/update the OS user scheduler
|
|
362
|
+
--hour Local hour (default from memory.json consolidation.schedule.hour)
|
|
363
|
+
--minute Local minute (default from memory.json consolidation.schedule.minute)
|
|
364
|
+
--dry-run Print files that would be written
|
|
365
|
+
--remove Remove scheduler files
|
|
366
|
+
--status Report scheduler file existence
|
|
223
367
|
query POST /query with JSON QueryIntent
|
|
224
368
|
install-bundle Copy a local bundle dir into ~/.pi/memory/current
|
|
225
369
|
train Build a bundle from session history (delta merge by default)
|
package/src/config.ts
CHANGED
|
@@ -21,6 +21,32 @@ export interface TrainerConfig {
|
|
|
21
21
|
auto_interval: string | null;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
export interface ConsolidationScheduleConfig {
|
|
25
|
+
/** Local hour for the background consolidation job (default 3). */
|
|
26
|
+
hour: number;
|
|
27
|
+
/** Local minute for the background consolidation job (default 0). */
|
|
28
|
+
minute: number;
|
|
29
|
+
/** Structured JSONL log path (default ~/.pi/memory/consolidation.log). */
|
|
30
|
+
log_path: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ConsolidationConfig {
|
|
34
|
+
/** Whether session_shutdown enqueues sessions for offline consolidation. */
|
|
35
|
+
enabled: boolean;
|
|
36
|
+
/** Minimum non-empty user turns before enqueue (default 3). */
|
|
37
|
+
min_user_turns: number;
|
|
38
|
+
/** MEMORY.md session cap by line count (default 200). */
|
|
39
|
+
memory_index_max_lines: number;
|
|
40
|
+
/** MEMORY.md session cap by UTF-8 bytes (default 25KB). */
|
|
41
|
+
memory_index_max_bytes: number;
|
|
42
|
+
/** Maximum stage1 rows selected per Phase2 run (default 20). */
|
|
43
|
+
phase2_top_n: number;
|
|
44
|
+
/** M4 prune threshold by last usage (default 90 days). */
|
|
45
|
+
max_unused_days: number;
|
|
46
|
+
/** Daily OS scheduler defaults. */
|
|
47
|
+
schedule: ConsolidationScheduleConfig;
|
|
48
|
+
}
|
|
49
|
+
|
|
24
50
|
export interface MemoryConfig {
|
|
25
51
|
provider: MemoryProvider;
|
|
26
52
|
tlmPath: string;
|
|
@@ -32,6 +58,7 @@ export interface MemoryConfig {
|
|
|
32
58
|
sessionsDir: string;
|
|
33
59
|
memoryMdPaths: string[];
|
|
34
60
|
trainer: TrainerConfig;
|
|
61
|
+
consolidation: ConsolidationConfig;
|
|
35
62
|
}
|
|
36
63
|
|
|
37
64
|
export const defaultTrainerConfig: TrainerConfig = {
|
|
@@ -40,10 +67,37 @@ export const defaultTrainerConfig: TrainerConfig = {
|
|
|
40
67
|
auto_interval: null,
|
|
41
68
|
};
|
|
42
69
|
|
|
70
|
+
export function defaultConsolidationConfig(
|
|
71
|
+
overrides: Partial<ConsolidationConfig> & {
|
|
72
|
+
schedule?: Partial<ConsolidationScheduleConfig>;
|
|
73
|
+
} = {},
|
|
74
|
+
): ConsolidationConfig {
|
|
75
|
+
const defaultSchedule: ConsolidationScheduleConfig = {
|
|
76
|
+
hour: 3,
|
|
77
|
+
minute: 0,
|
|
78
|
+
log_path: path.join(defaultBundleRoot(), "consolidation.log"),
|
|
79
|
+
};
|
|
80
|
+
const { schedule, ...rest } = overrides;
|
|
81
|
+
return {
|
|
82
|
+
enabled: true,
|
|
83
|
+
min_user_turns: 3,
|
|
84
|
+
memory_index_max_lines: 200,
|
|
85
|
+
memory_index_max_bytes: 25_600,
|
|
86
|
+
phase2_top_n: 20,
|
|
87
|
+
max_unused_days: 90,
|
|
88
|
+
schedule: { ...defaultSchedule, ...schedule },
|
|
89
|
+
...rest,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
43
93
|
export function defaultMemoryConfig(
|
|
44
94
|
overrides: Partial<MemoryConfig> = {},
|
|
45
95
|
): MemoryConfig {
|
|
46
|
-
const {
|
|
96
|
+
const {
|
|
97
|
+
trainer: trainerOverrides,
|
|
98
|
+
consolidation: consolidationOverrides,
|
|
99
|
+
...rest
|
|
100
|
+
} = overrides;
|
|
47
101
|
return {
|
|
48
102
|
provider: "local",
|
|
49
103
|
tlmPath: "tlm",
|
|
@@ -55,6 +109,7 @@ export function defaultMemoryConfig(
|
|
|
55
109
|
sessionsDir: defaultSessionsDir(),
|
|
56
110
|
memoryMdPaths: [path.join(defaultPiHome(), "MEMORY.md")],
|
|
57
111
|
trainer: { ...defaultTrainerConfig, ...trainerOverrides },
|
|
112
|
+
consolidation: defaultConsolidationConfig(consolidationOverrides),
|
|
58
113
|
...rest,
|
|
59
114
|
};
|
|
60
115
|
}
|
|
@@ -65,6 +120,12 @@ export function normalizeMemoryConfig(
|
|
|
65
120
|
): MemoryConfig {
|
|
66
121
|
const base = defaultMemoryConfig();
|
|
67
122
|
const rawTrainer = (raw.trainer ?? {}) as Partial<TrainerConfig>;
|
|
123
|
+
const rawConsolidation = (raw.consolidation ?? {}) as
|
|
124
|
+
Partial<ConsolidationConfig> & {
|
|
125
|
+
schedule?: Partial<ConsolidationScheduleConfig>;
|
|
126
|
+
};
|
|
127
|
+
const rawSchedule = (rawConsolidation.schedule ?? {}) as
|
|
128
|
+
Partial<ConsolidationScheduleConfig>;
|
|
68
129
|
return {
|
|
69
130
|
provider: (raw.provider as MemoryProvider) ?? base.provider,
|
|
70
131
|
tlmPath: expandPath(String(raw.tlmPath ?? base.tlmPath)),
|
|
@@ -88,5 +149,32 @@ export function normalizeMemoryConfig(
|
|
|
88
149
|
? rawTrainer.auto_interval
|
|
89
150
|
: base.trainer.auto_interval,
|
|
90
151
|
},
|
|
152
|
+
consolidation: {
|
|
153
|
+
enabled: rawConsolidation.enabled ?? base.consolidation.enabled,
|
|
154
|
+
min_user_turns: Number(
|
|
155
|
+
rawConsolidation.min_user_turns ?? base.consolidation.min_user_turns,
|
|
156
|
+
),
|
|
157
|
+
memory_index_max_lines: Number(
|
|
158
|
+
rawConsolidation.memory_index_max_lines ??
|
|
159
|
+
base.consolidation.memory_index_max_lines,
|
|
160
|
+
),
|
|
161
|
+
memory_index_max_bytes: Number(
|
|
162
|
+
rawConsolidation.memory_index_max_bytes ??
|
|
163
|
+
base.consolidation.memory_index_max_bytes,
|
|
164
|
+
),
|
|
165
|
+
phase2_top_n: Number(
|
|
166
|
+
rawConsolidation.phase2_top_n ?? base.consolidation.phase2_top_n,
|
|
167
|
+
),
|
|
168
|
+
max_unused_days: Number(
|
|
169
|
+
rawConsolidation.max_unused_days ?? base.consolidation.max_unused_days,
|
|
170
|
+
),
|
|
171
|
+
schedule: {
|
|
172
|
+
hour: Number(rawSchedule.hour ?? base.consolidation.schedule.hour),
|
|
173
|
+
minute: Number(rawSchedule.minute ?? base.consolidation.schedule.minute),
|
|
174
|
+
log_path: expandPath(
|
|
175
|
+
String(rawSchedule.log_path ?? base.consolidation.schedule.log_path),
|
|
176
|
+
),
|
|
177
|
+
},
|
|
178
|
+
},
|
|
91
179
|
};
|
|
92
180
|
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
ConsolidationStatus,
|
|
5
|
+
JobReport,
|
|
6
|
+
PendingSession,
|
|
7
|
+
ConsolidationStatusReport,
|
|
8
|
+
} from "./types.js";
|
|
9
|
+
import { openConsolidationStore, type SqliteDatabase } from "./stage1/store.js";
|
|
10
|
+
|
|
11
|
+
export interface EnqueueSessionInput {
|
|
12
|
+
session_id: string;
|
|
13
|
+
session_file: string;
|
|
14
|
+
cwd: string;
|
|
15
|
+
git_root: string | null;
|
|
16
|
+
project_hash: string | null;
|
|
17
|
+
parent_session_id: string | null;
|
|
18
|
+
parent_session_file: string | null;
|
|
19
|
+
user_turn_count: number;
|
|
20
|
+
ended_at: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface EnqueueOptions {
|
|
24
|
+
enabled?: boolean;
|
|
25
|
+
minUserTurns?: number;
|
|
26
|
+
/** Injected sqlite handle for tests. */
|
|
27
|
+
db?: SqliteDatabase;
|
|
28
|
+
/** Force replacing existing done/skipped sessions. */
|
|
29
|
+
force?: boolean;
|
|
30
|
+
/** Override status for the newly enqueued job (default: pending). */
|
|
31
|
+
status?: ConsolidationStatus;
|
|
32
|
+
/** Optional now source for created_at / updated_at. */
|
|
33
|
+
now?: Date | string;
|
|
34
|
+
/** Test seam: production defaults to checking session file existence. */
|
|
35
|
+
checkFileExists?: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function toIso(value: Date | string | undefined): string {
|
|
39
|
+
const dt = value ? new Date(value) : new Date();
|
|
40
|
+
return dt.toISOString();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function isMissingFile(file: string): boolean {
|
|
44
|
+
return typeof file !== "string" || !file.trim();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function isAllowedStatus(status: string): status is ConsolidationStatus {
|
|
48
|
+
return status === "pending" || status === "processing" || status === "done" || status === "skipped" ||
|
|
49
|
+
status === "failed";
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function enqueueSession(
|
|
53
|
+
dbPath: string,
|
|
54
|
+
meta: EnqueueSessionInput,
|
|
55
|
+
opts: EnqueueOptions = {},
|
|
56
|
+
): JobReport {
|
|
57
|
+
const enabled = opts.enabled !== false;
|
|
58
|
+
const minUserTurns = opts.minUserTurns ?? 3;
|
|
59
|
+
const now = toIso(opts.now);
|
|
60
|
+
|
|
61
|
+
if (!enabled) {
|
|
62
|
+
return {
|
|
63
|
+
session_id: meta.session_id,
|
|
64
|
+
enqueued: false,
|
|
65
|
+
action: "skipped",
|
|
66
|
+
status: "skipped",
|
|
67
|
+
reason: "consolidation_disabled",
|
|
68
|
+
now,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (isMissingFile(meta.session_file)) {
|
|
73
|
+
return {
|
|
74
|
+
session_id: meta.session_id,
|
|
75
|
+
enqueued: false,
|
|
76
|
+
action: "skipped",
|
|
77
|
+
status: "skipped",
|
|
78
|
+
reason: "missing_session_file",
|
|
79
|
+
now,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (opts.checkFileExists !== false && !fs.existsSync(meta.session_file)) {
|
|
84
|
+
return {
|
|
85
|
+
session_id: meta.session_id,
|
|
86
|
+
enqueued: false,
|
|
87
|
+
action: "skipped",
|
|
88
|
+
status: "skipped",
|
|
89
|
+
reason: "session_file_not_found",
|
|
90
|
+
now,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (meta.user_turn_count < minUserTurns) {
|
|
95
|
+
return {
|
|
96
|
+
session_id: meta.session_id,
|
|
97
|
+
enqueued: false,
|
|
98
|
+
action: "skipped",
|
|
99
|
+
status: "skipped",
|
|
100
|
+
reason: "insufficient_user_turns",
|
|
101
|
+
now,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const store = openConsolidationStore(dbPath, opts.db);
|
|
106
|
+
if (!store) {
|
|
107
|
+
return {
|
|
108
|
+
session_id: meta.session_id,
|
|
109
|
+
enqueued: false,
|
|
110
|
+
action: "skipped",
|
|
111
|
+
status: "skipped",
|
|
112
|
+
reason: "missing_sqlite",
|
|
113
|
+
now,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const incomingStatus: ConsolidationStatus = isAllowedStatus(opts.status ?? "") ? opts.status! : "pending";
|
|
118
|
+
const existing = store.getPendingSession(meta.session_id);
|
|
119
|
+
const isFinal = existing?.status === "done" || existing?.status === "skipped";
|
|
120
|
+
|
|
121
|
+
if (existing && isFinal && !opts.force) {
|
|
122
|
+
store.close();
|
|
123
|
+
return {
|
|
124
|
+
session_id: meta.session_id,
|
|
125
|
+
enqueued: false,
|
|
126
|
+
action: "skipped",
|
|
127
|
+
status: existing.status,
|
|
128
|
+
reason: "already_finalized",
|
|
129
|
+
now,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const existingCreatedAt = existing?.created_at ?? now;
|
|
134
|
+
const payload: Omit<PendingSession, "created_at" | "updated_at"> & {
|
|
135
|
+
created_at: string;
|
|
136
|
+
updated_at: string;
|
|
137
|
+
} = {
|
|
138
|
+
session_id: meta.session_id,
|
|
139
|
+
session_file: meta.session_file,
|
|
140
|
+
cwd: meta.cwd,
|
|
141
|
+
git_root: meta.git_root,
|
|
142
|
+
project_hash: meta.project_hash,
|
|
143
|
+
parent_session_id: meta.parent_session_id,
|
|
144
|
+
parent_session_file: meta.parent_session_file,
|
|
145
|
+
user_turn_count: meta.user_turn_count,
|
|
146
|
+
ended_at: meta.ended_at,
|
|
147
|
+
status: incomingStatus,
|
|
148
|
+
error_message: null,
|
|
149
|
+
created_at: existingCreatedAt,
|
|
150
|
+
updated_at: now,
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
store.upsertPendingSession(payload);
|
|
155
|
+
const action = existing ? "updated" : "created";
|
|
156
|
+
return {
|
|
157
|
+
session_id: meta.session_id,
|
|
158
|
+
enqueued: true,
|
|
159
|
+
action,
|
|
160
|
+
status: incomingStatus,
|
|
161
|
+
now,
|
|
162
|
+
};
|
|
163
|
+
} finally {
|
|
164
|
+
store.close();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export interface ConsolidationStatusQueryOptions {
|
|
169
|
+
db?: SqliteDatabase;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function getConsolidationStatus(
|
|
173
|
+
dbPath: string,
|
|
174
|
+
opts: ConsolidationStatusQueryOptions = {},
|
|
175
|
+
): ConsolidationStatusReport {
|
|
176
|
+
const store = openConsolidationStore(dbPath, opts.db);
|
|
177
|
+
if (!store) {
|
|
178
|
+
return {
|
|
179
|
+
pending: 0,
|
|
180
|
+
processing: 0,
|
|
181
|
+
done: 0,
|
|
182
|
+
skipped: 0,
|
|
183
|
+
failed: 0,
|
|
184
|
+
stage1Count: 0,
|
|
185
|
+
lastGeneratedAt: null,
|
|
186
|
+
lastUpdatedAt: null,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
const byStatus = store.getConsolidationStatusCounts();
|
|
192
|
+
return {
|
|
193
|
+
pending: byStatus.pending,
|
|
194
|
+
processing: byStatus.processing,
|
|
195
|
+
done: byStatus.done,
|
|
196
|
+
skipped: byStatus.skipped,
|
|
197
|
+
failed: byStatus.failed,
|
|
198
|
+
stage1Count: store.getStage1Count(),
|
|
199
|
+
lastGeneratedAt: store.getLastGeneratedAt(),
|
|
200
|
+
lastUpdatedAt: store.getLastUpdatedAt(),
|
|
201
|
+
};
|
|
202
|
+
} finally {
|
|
203
|
+
store.close();
|
|
204
|
+
}
|
|
205
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
export type {
|
|
2
|
+
ConsolidationStatus,
|
|
3
|
+
ConsolidationStatusReport,
|
|
4
|
+
JobReport,
|
|
5
|
+
PendingSession,
|
|
6
|
+
Stage1Output,
|
|
7
|
+
} from "./types.js";
|
|
8
|
+
|
|
9
|
+
export {
|
|
10
|
+
getMemoryIndexStats,
|
|
11
|
+
memoryIndexSnippet,
|
|
12
|
+
readMemoryIndexCap,
|
|
13
|
+
type MemoryIndexCapOptions,
|
|
14
|
+
type MemoryIndexStats,
|
|
15
|
+
} from "./memoryIndex.js";
|
|
16
|
+
|
|
17
|
+
export {
|
|
18
|
+
findGitRoot,
|
|
19
|
+
lineMatchesScope,
|
|
20
|
+
parseScopeComment,
|
|
21
|
+
projectHash,
|
|
22
|
+
projectScope,
|
|
23
|
+
scopeForCwd,
|
|
24
|
+
type MemoryScope,
|
|
25
|
+
} from "./scope.js";
|
|
26
|
+
|
|
27
|
+
export {
|
|
28
|
+
appendConsolidationLog,
|
|
29
|
+
readRecentConsolidationLogs,
|
|
30
|
+
type ConsolidationLogEntry,
|
|
31
|
+
} from "./log.js";
|
|
32
|
+
|
|
33
|
+
export { withFileLock } from "./lock.js";
|
|
34
|
+
|
|
35
|
+
export { enqueueSession, getConsolidationStatus } from "./enqueue.js";
|
|
36
|
+
export type {
|
|
37
|
+
ConsolidationStatusQueryOptions,
|
|
38
|
+
EnqueueOptions,
|
|
39
|
+
EnqueueSessionInput,
|
|
40
|
+
} from "./enqueue.js";
|
|
41
|
+
|
|
42
|
+
export { openConsolidationStore } from "./stage1/store.js";
|
|
43
|
+
export type { SqliteDatabase, ConsolidationStore } from "./stage1/store.js";
|
|
44
|
+
|
|
45
|
+
export {
|
|
46
|
+
computeDeltaTurns,
|
|
47
|
+
} from "./stage1/deltaExtract.js";
|
|
48
|
+
|
|
49
|
+
export {
|
|
50
|
+
drainQueue,
|
|
51
|
+
type DrainQueueOptions,
|
|
52
|
+
type DrainQueueReport,
|
|
53
|
+
} from "./stage1/drainQueue.js";
|
|
54
|
+
|
|
55
|
+
export { extractSessionToStage1 } from "./stage1/extractSession.js";
|
|
56
|
+
|
|
57
|
+
export {
|
|
58
|
+
runPhase2,
|
|
59
|
+
type Phase2Options,
|
|
60
|
+
type Phase2Report,
|
|
61
|
+
} from "./phase2/runPhase2.js";
|
|
62
|
+
|
|
63
|
+
export {
|
|
64
|
+
defaultConsolidationDbPath,
|
|
65
|
+
runConsolidate,
|
|
66
|
+
type ConsolidateOptions,
|
|
67
|
+
type ConsolidateReport,
|
|
68
|
+
} from "./scheduler/runConsolidate.js";
|
|
69
|
+
|
|
70
|
+
export {
|
|
71
|
+
buildLaunchdPlist,
|
|
72
|
+
launchdPlistPath,
|
|
73
|
+
} from "./scheduler/launchd.js";
|
|
74
|
+
|
|
75
|
+
export {
|
|
76
|
+
buildSystemdService,
|
|
77
|
+
buildSystemdTimer,
|
|
78
|
+
systemdServicePath,
|
|
79
|
+
systemdTimerPath,
|
|
80
|
+
} from "./scheduler/systemd.js";
|
|
81
|
+
|
|
82
|
+
export { setupSchedule } from "./scheduler/setupSchedule.js";
|
|
83
|
+
|
|
84
|
+
export type {
|
|
85
|
+
ScheduleAction,
|
|
86
|
+
ScheduledFile,
|
|
87
|
+
ScheduleOptions,
|
|
88
|
+
SchedulePlatform,
|
|
89
|
+
SetupScheduleResult,
|
|
90
|
+
} from "./scheduler/types.js";
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
const LOCK_RETRIES = 50;
|
|
5
|
+
const LOCK_DELAY_MS = 100;
|
|
6
|
+
|
|
7
|
+
async function sleep(ms: number): Promise<void> {
|
|
8
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function withFileLock<T>(
|
|
12
|
+
lockPath: string,
|
|
13
|
+
fn: () => Promise<T>,
|
|
14
|
+
opts: { retries?: number; delayMs?: number } = {},
|
|
15
|
+
): Promise<T> {
|
|
16
|
+
const retries = opts.retries ?? LOCK_RETRIES;
|
|
17
|
+
const delayMs = opts.delayMs ?? LOCK_DELAY_MS;
|
|
18
|
+
let handle: fs.FileHandle | null = null;
|
|
19
|
+
|
|
20
|
+
await fs.mkdir(path.dirname(lockPath), { recursive: true, mode: 0o700 });
|
|
21
|
+
|
|
22
|
+
for (let i = 0; i < retries; i++) {
|
|
23
|
+
try {
|
|
24
|
+
handle = await fs.open(lockPath, "wx");
|
|
25
|
+
break;
|
|
26
|
+
} catch {
|
|
27
|
+
await sleep(delayMs);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!handle) {
|
|
32
|
+
throw new Error(`could not acquire lock: ${lockPath}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
return await fn();
|
|
37
|
+
} finally {
|
|
38
|
+
await handle.close().catch(() => {});
|
|
39
|
+
await fs.unlink(lockPath).catch(() => {});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
export interface ConsolidationLogEntry {
|
|
5
|
+
ts?: string;
|
|
6
|
+
phase: string;
|
|
7
|
+
[key: string]: unknown;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function appendConsolidationLog(
|
|
11
|
+
logPath: string,
|
|
12
|
+
entry: ConsolidationLogEntry,
|
|
13
|
+
): Promise<void> {
|
|
14
|
+
await fs.mkdir(path.dirname(logPath), { recursive: true, mode: 0o700 });
|
|
15
|
+
const line = JSON.stringify({
|
|
16
|
+
ts: entry.ts ?? new Date().toISOString(),
|
|
17
|
+
...entry,
|
|
18
|
+
});
|
|
19
|
+
await fs.appendFile(logPath, `${line}\n`, { encoding: "utf8", mode: 0o600 });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function readRecentConsolidationLogs(
|
|
23
|
+
logPath: string,
|
|
24
|
+
limit = 5,
|
|
25
|
+
): Promise<ConsolidationLogEntry[]> {
|
|
26
|
+
let text: string;
|
|
27
|
+
try {
|
|
28
|
+
text = await fs.readFile(logPath, "utf8");
|
|
29
|
+
} catch {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
return text
|
|
33
|
+
.split("\n")
|
|
34
|
+
.filter((line) => line.trim())
|
|
35
|
+
.slice(-limit)
|
|
36
|
+
.map((line) => {
|
|
37
|
+
try {
|
|
38
|
+
return JSON.parse(line) as ConsolidationLogEntry;
|
|
39
|
+
} catch {
|
|
40
|
+
return { phase: "invalid_log_line", raw: line };
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|