@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
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
PendingSession,
|
|
5
|
+
Stage1Output,
|
|
6
|
+
ConsolidationStatus,
|
|
7
|
+
} from "../types.js";
|
|
8
|
+
|
|
9
|
+
export interface SqliteDatabase {
|
|
10
|
+
pragma(sql: string): unknown;
|
|
11
|
+
exec(sql: string): void;
|
|
12
|
+
prepare(sql: string): {
|
|
13
|
+
run(...args: unknown[]): unknown;
|
|
14
|
+
get(...args: unknown[]): unknown;
|
|
15
|
+
all(...args: unknown[]): unknown[];
|
|
16
|
+
};
|
|
17
|
+
transaction<T>(fn: () => T): () => T;
|
|
18
|
+
close(): void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
type SqliteConstructor = new (path: string) => SqliteDatabase;
|
|
22
|
+
|
|
23
|
+
function loadSqlite(): SqliteConstructor | null {
|
|
24
|
+
try {
|
|
25
|
+
const require = createRequire(import.meta.url);
|
|
26
|
+
const mod = require("better-sqlite3") as SqliteConstructor;
|
|
27
|
+
return mod;
|
|
28
|
+
} catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const SCHEMA_VERSION = 1;
|
|
34
|
+
|
|
35
|
+
const SCHEMA_SQL = `
|
|
36
|
+
CREATE TABLE IF NOT EXISTS pending_sessions (
|
|
37
|
+
session_id TEXT PRIMARY KEY,
|
|
38
|
+
session_file TEXT NOT NULL,
|
|
39
|
+
cwd TEXT NOT NULL,
|
|
40
|
+
git_root TEXT,
|
|
41
|
+
project_hash TEXT,
|
|
42
|
+
parent_session_id TEXT,
|
|
43
|
+
parent_session_file TEXT,
|
|
44
|
+
user_turn_count INTEGER NOT NULL,
|
|
45
|
+
ended_at TEXT NOT NULL,
|
|
46
|
+
status TEXT NOT NULL CHECK (status IN ('pending', 'processing', 'done', 'skipped', 'failed')),
|
|
47
|
+
error_message TEXT,
|
|
48
|
+
created_at TEXT NOT NULL,
|
|
49
|
+
updated_at TEXT NOT NULL
|
|
50
|
+
);
|
|
51
|
+
CREATE INDEX IF NOT EXISTS idx_pending_sessions_status ON pending_sessions(status);
|
|
52
|
+
CREATE TABLE IF NOT EXISTS stage1_outputs (
|
|
53
|
+
session_id TEXT PRIMARY KEY,
|
|
54
|
+
session_file TEXT NOT NULL,
|
|
55
|
+
source_mtime_ms INTEGER NOT NULL,
|
|
56
|
+
generated_at TEXT NOT NULL,
|
|
57
|
+
raw_memory TEXT NOT NULL,
|
|
58
|
+
rollout_summary TEXT NOT NULL,
|
|
59
|
+
scope TEXT NOT NULL,
|
|
60
|
+
status TEXT NOT NULL CHECK (status IN ('pending', 'processing', 'done', 'skipped', 'failed')),
|
|
61
|
+
selected_for_phase2 INTEGER NOT NULL DEFAULT 0,
|
|
62
|
+
usage_count INTEGER NOT NULL DEFAULT 0,
|
|
63
|
+
last_usage TEXT,
|
|
64
|
+
error_message TEXT
|
|
65
|
+
);
|
|
66
|
+
CREATE INDEX IF NOT EXISTS idx_stage1_outputs_status ON stage1_outputs(status);
|
|
67
|
+
`;
|
|
68
|
+
|
|
69
|
+
const UPSERT_PENDING_SQL = `
|
|
70
|
+
INSERT INTO pending_sessions (
|
|
71
|
+
session_id,
|
|
72
|
+
session_file,
|
|
73
|
+
cwd,
|
|
74
|
+
git_root,
|
|
75
|
+
project_hash,
|
|
76
|
+
parent_session_id,
|
|
77
|
+
parent_session_file,
|
|
78
|
+
user_turn_count,
|
|
79
|
+
ended_at,
|
|
80
|
+
status,
|
|
81
|
+
error_message,
|
|
82
|
+
created_at,
|
|
83
|
+
updated_at
|
|
84
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
85
|
+
ON CONFLICT(session_id) DO UPDATE SET
|
|
86
|
+
session_file = excluded.session_file,
|
|
87
|
+
cwd = excluded.cwd,
|
|
88
|
+
git_root = excluded.git_root,
|
|
89
|
+
project_hash = excluded.project_hash,
|
|
90
|
+
parent_session_id = excluded.parent_session_id,
|
|
91
|
+
parent_session_file = excluded.parent_session_file,
|
|
92
|
+
user_turn_count = excluded.user_turn_count,
|
|
93
|
+
ended_at = excluded.ended_at,
|
|
94
|
+
status = excluded.status,
|
|
95
|
+
error_message = excluded.error_message,
|
|
96
|
+
updated_at = excluded.updated_at;
|
|
97
|
+
`;
|
|
98
|
+
|
|
99
|
+
const UPSERT_STAGE1_SQL = `
|
|
100
|
+
INSERT INTO stage1_outputs (
|
|
101
|
+
session_id,
|
|
102
|
+
session_file,
|
|
103
|
+
source_mtime_ms,
|
|
104
|
+
generated_at,
|
|
105
|
+
raw_memory,
|
|
106
|
+
rollout_summary,
|
|
107
|
+
scope,
|
|
108
|
+
status,
|
|
109
|
+
selected_for_phase2,
|
|
110
|
+
usage_count,
|
|
111
|
+
last_usage,
|
|
112
|
+
error_message
|
|
113
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
114
|
+
ON CONFLICT(session_id) DO UPDATE SET
|
|
115
|
+
session_file = excluded.session_file,
|
|
116
|
+
source_mtime_ms = excluded.source_mtime_ms,
|
|
117
|
+
generated_at = excluded.generated_at,
|
|
118
|
+
raw_memory = excluded.raw_memory,
|
|
119
|
+
rollout_summary = excluded.rollout_summary,
|
|
120
|
+
scope = excluded.scope,
|
|
121
|
+
status = excluded.status,
|
|
122
|
+
selected_for_phase2 = excluded.selected_for_phase2,
|
|
123
|
+
usage_count = excluded.usage_count,
|
|
124
|
+
last_usage = excluded.last_usage,
|
|
125
|
+
error_message = excluded.error_message;
|
|
126
|
+
`;
|
|
127
|
+
|
|
128
|
+
export interface ConsolidationStore {
|
|
129
|
+
upsertPendingSession(session: Omit<PendingSession, "created_at" | "updated_at"> & {
|
|
130
|
+
created_at: string;
|
|
131
|
+
updated_at: string;
|
|
132
|
+
}): PendingSession;
|
|
133
|
+
upsertStage1Output(output: Stage1Output): void;
|
|
134
|
+
getPendingSession(sessionId: string): PendingSession | null;
|
|
135
|
+
listPendingSessions(limit?: number): PendingSession[];
|
|
136
|
+
setPendingSessionStatus(
|
|
137
|
+
sessionId: string,
|
|
138
|
+
status: ConsolidationStatus,
|
|
139
|
+
errorMessage?: string | null,
|
|
140
|
+
updatedAt?: string,
|
|
141
|
+
): void;
|
|
142
|
+
getStage1Output(sessionId: string): Stage1Output | null;
|
|
143
|
+
listUnselectedStage1(limit: number): Stage1Output[];
|
|
144
|
+
listStage1OlderThan(cutoffIso: string, limit?: number): Stage1Output[];
|
|
145
|
+
markStage1Selected(sessionIds: string[]): void;
|
|
146
|
+
incrementStage1Usage(sessionId: string, usedAt?: string): void;
|
|
147
|
+
countPendingSessions(): number;
|
|
148
|
+
getConsolidationStatusCounts(): {
|
|
149
|
+
pending: number;
|
|
150
|
+
processing: number;
|
|
151
|
+
done: number;
|
|
152
|
+
skipped: number;
|
|
153
|
+
failed: number;
|
|
154
|
+
};
|
|
155
|
+
getStage1Count(): number;
|
|
156
|
+
getLastGeneratedAt(): string | null;
|
|
157
|
+
getLastUpdatedAt(): string | null;
|
|
158
|
+
close(): void;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function toIsoTimestamp(value: Date | string | undefined = new Date()): string {
|
|
162
|
+
const dt = typeof value === "string" ? new Date(value) : value;
|
|
163
|
+
return dt.toISOString();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function getUserVersion(db: SqliteDatabase): number {
|
|
167
|
+
const raw = db.pragma("user_version");
|
|
168
|
+
if (Array.isArray(raw) && raw[0] && typeof raw[0] === "object" && "user_version" in raw[0]) {
|
|
169
|
+
return Number((raw[0] as { user_version: number }).user_version);
|
|
170
|
+
}
|
|
171
|
+
if (typeof raw === "number") return raw;
|
|
172
|
+
return 0;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function setUserVersion(db: SqliteDatabase, version: number): void {
|
|
176
|
+
db.pragma(`user_version = ${version}`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function normalizeSessionRow(row: Record<string, unknown> | undefined | null): PendingSession | null {
|
|
180
|
+
if (!row) return null;
|
|
181
|
+
return {
|
|
182
|
+
session_id: String(row.session_id ?? ""),
|
|
183
|
+
session_file: String(row.session_file ?? ""),
|
|
184
|
+
cwd: String(row.cwd ?? ""),
|
|
185
|
+
git_root: row.git_root == null ? null : String(row.git_root),
|
|
186
|
+
project_hash: row.project_hash == null ? null : String(row.project_hash),
|
|
187
|
+
parent_session_id: row.parent_session_id == null ? null : String(row.parent_session_id),
|
|
188
|
+
parent_session_file: row.parent_session_file == null ? null : String(row.parent_session_file),
|
|
189
|
+
user_turn_count: Number(row.user_turn_count ?? 0),
|
|
190
|
+
ended_at: String(row.ended_at ?? ""),
|
|
191
|
+
status: String(row.status ?? "pending") as ConsolidationStatus,
|
|
192
|
+
error_message: row.error_message == null ? null : String(row.error_message),
|
|
193
|
+
created_at: String(row.created_at ?? ""),
|
|
194
|
+
updated_at: String(row.updated_at ?? ""),
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function normalizeStage1Row(row: Record<string, unknown> | undefined | null): Stage1Output | null {
|
|
199
|
+
if (!row) return null;
|
|
200
|
+
return {
|
|
201
|
+
session_id: String(row.session_id ?? ""),
|
|
202
|
+
session_file: String(row.session_file ?? ""),
|
|
203
|
+
source_mtime_ms: toNumber(row.source_mtime_ms, 0),
|
|
204
|
+
generated_at: String(row.generated_at ?? ""),
|
|
205
|
+
raw_memory: String(row.raw_memory ?? ""),
|
|
206
|
+
rollout_summary: String(row.rollout_summary ?? ""),
|
|
207
|
+
scope: String(row.scope ?? "global"),
|
|
208
|
+
status: String(row.status ?? "pending") as ConsolidationStatus,
|
|
209
|
+
selected_for_phase2: Number(row.selected_for_phase2 ?? 0) === 1,
|
|
210
|
+
usage_count: toNumber(row.usage_count, 0),
|
|
211
|
+
last_usage: row.last_usage == null ? null : String(row.last_usage),
|
|
212
|
+
error_message: row.error_message == null ? null : String(row.error_message),
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function toBoolInt(value: boolean): number {
|
|
217
|
+
return value ? 1 : 0;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function toNumber(value: unknown, fallback = 0): number {
|
|
221
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
222
|
+
if (typeof value === "string") {
|
|
223
|
+
const parsed = Number(value);
|
|
224
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
225
|
+
}
|
|
226
|
+
return fallback;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Lazy-open a sqlite database and ensure the consolidation schema exists.
|
|
231
|
+
* Returns null when better-sqlite3 is unavailable and no injected DB is provided.
|
|
232
|
+
*/
|
|
233
|
+
export function openConsolidationStore(
|
|
234
|
+
dbPath: string,
|
|
235
|
+
injectedDb?: SqliteDatabase,
|
|
236
|
+
): ConsolidationStore | null {
|
|
237
|
+
let db: SqliteDatabase;
|
|
238
|
+
if (injectedDb) {
|
|
239
|
+
db = injectedDb;
|
|
240
|
+
} else {
|
|
241
|
+
const Sqlite = loadSqlite();
|
|
242
|
+
if (!Sqlite) return null;
|
|
243
|
+
db = new Sqlite(dbPath);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
db.pragma("journal_mode = WAL");
|
|
247
|
+
const current = getUserVersion(db);
|
|
248
|
+
if (current !== SCHEMA_VERSION) {
|
|
249
|
+
db.exec(SCHEMA_SQL);
|
|
250
|
+
setUserVersion(db, SCHEMA_VERSION);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const upsertPendingStmt = db.prepare(UPSERT_PENDING_SQL);
|
|
254
|
+
const upsertStage1Stmt = db.prepare(UPSERT_STAGE1_SQL);
|
|
255
|
+
const selectPendingByIdStmt = db.prepare("SELECT * FROM pending_sessions WHERE session_id = ?");
|
|
256
|
+
const listPendingStmt = db.prepare(
|
|
257
|
+
"SELECT * FROM pending_sessions WHERE status IN ('pending', 'failed') ORDER BY ended_at ASC LIMIT ?",
|
|
258
|
+
);
|
|
259
|
+
const updatePendingStatusStmt = db.prepare(
|
|
260
|
+
"UPDATE pending_sessions SET status = ?, error_message = ?, updated_at = ? WHERE session_id = ?",
|
|
261
|
+
);
|
|
262
|
+
const selectStage1ByIdStmt = db.prepare("SELECT * FROM stage1_outputs WHERE session_id = ?");
|
|
263
|
+
const listUnselectedStage1Stmt = db.prepare(
|
|
264
|
+
"SELECT * FROM stage1_outputs WHERE selected_for_phase2 = 0 AND status = 'done' ORDER BY generated_at DESC LIMIT ?",
|
|
265
|
+
);
|
|
266
|
+
const listStage1OlderThanStmt = db.prepare(
|
|
267
|
+
"SELECT * FROM stage1_outputs WHERE last_usage IS NOT NULL AND last_usage < ? ORDER BY last_usage ASC LIMIT ?",
|
|
268
|
+
);
|
|
269
|
+
const markStage1SelectedStmt = db.prepare(
|
|
270
|
+
"UPDATE stage1_outputs SET selected_for_phase2 = 1 WHERE session_id = ?",
|
|
271
|
+
);
|
|
272
|
+
const incrementStage1UsageStmt = db.prepare(
|
|
273
|
+
"UPDATE stage1_outputs SET usage_count = usage_count + 1, last_usage = ? WHERE session_id = ?",
|
|
274
|
+
);
|
|
275
|
+
const countPendingAllStmt = db.prepare("SELECT COUNT(*) AS count FROM pending_sessions");
|
|
276
|
+
const countPendingByStatusStmt = db.prepare(
|
|
277
|
+
"SELECT status, COUNT(*) AS count FROM pending_sessions GROUP BY status",
|
|
278
|
+
);
|
|
279
|
+
const countStage1Stmt = db.prepare("SELECT COUNT(*) AS count FROM stage1_outputs");
|
|
280
|
+
const lastGeneratedStmt = db.prepare("SELECT MAX(generated_at) AS generated_at FROM stage1_outputs");
|
|
281
|
+
const lastUpdatedStmt = db.prepare("SELECT MAX(updated_at) AS updated_at FROM pending_sessions");
|
|
282
|
+
|
|
283
|
+
return {
|
|
284
|
+
upsertPendingSession(session): PendingSession {
|
|
285
|
+
const now = toIsoTimestamp(session.updated_at);
|
|
286
|
+
const payload = {
|
|
287
|
+
...session,
|
|
288
|
+
created_at: session.created_at ?? now,
|
|
289
|
+
updated_at: now,
|
|
290
|
+
};
|
|
291
|
+
upsertPendingStmt.run(
|
|
292
|
+
payload.session_id,
|
|
293
|
+
payload.session_file,
|
|
294
|
+
payload.cwd,
|
|
295
|
+
payload.git_root,
|
|
296
|
+
payload.project_hash,
|
|
297
|
+
payload.parent_session_id,
|
|
298
|
+
payload.parent_session_file,
|
|
299
|
+
payload.user_turn_count,
|
|
300
|
+
payload.ended_at,
|
|
301
|
+
payload.status,
|
|
302
|
+
payload.error_message,
|
|
303
|
+
payload.created_at,
|
|
304
|
+
payload.updated_at,
|
|
305
|
+
);
|
|
306
|
+
return payload;
|
|
307
|
+
},
|
|
308
|
+
upsertStage1Output(output): void {
|
|
309
|
+
upsertStage1Stmt.run(
|
|
310
|
+
output.session_id,
|
|
311
|
+
output.session_file,
|
|
312
|
+
output.source_mtime_ms,
|
|
313
|
+
output.generated_at,
|
|
314
|
+
output.raw_memory,
|
|
315
|
+
output.rollout_summary,
|
|
316
|
+
output.scope,
|
|
317
|
+
output.status,
|
|
318
|
+
toBoolInt(output.selected_for_phase2),
|
|
319
|
+
output.usage_count,
|
|
320
|
+
output.last_usage,
|
|
321
|
+
output.error_message,
|
|
322
|
+
);
|
|
323
|
+
},
|
|
324
|
+
getPendingSession(sessionId: string): PendingSession | null {
|
|
325
|
+
return normalizeSessionRow(
|
|
326
|
+
selectPendingByIdStmt.get(sessionId) as Record<string, unknown> | undefined,
|
|
327
|
+
);
|
|
328
|
+
},
|
|
329
|
+
listPendingSessions(limit = 100): PendingSession[] {
|
|
330
|
+
const rows = listPendingStmt.all(limit) as Array<Record<string, unknown>>;
|
|
331
|
+
return rows
|
|
332
|
+
.map((row) => normalizeSessionRow(row))
|
|
333
|
+
.filter((row): row is PendingSession => row != null);
|
|
334
|
+
},
|
|
335
|
+
setPendingSessionStatus(
|
|
336
|
+
sessionId: string,
|
|
337
|
+
status: ConsolidationStatus,
|
|
338
|
+
errorMessage: string | null = null,
|
|
339
|
+
updatedAt = new Date().toISOString(),
|
|
340
|
+
): void {
|
|
341
|
+
updatePendingStatusStmt.run(status, errorMessage, updatedAt, sessionId);
|
|
342
|
+
},
|
|
343
|
+
getStage1Output(sessionId: string): Stage1Output | null {
|
|
344
|
+
return normalizeStage1Row(
|
|
345
|
+
selectStage1ByIdStmt.get(sessionId) as Record<string, unknown> | undefined,
|
|
346
|
+
);
|
|
347
|
+
},
|
|
348
|
+
listUnselectedStage1(limit: number): Stage1Output[] {
|
|
349
|
+
const rows = listUnselectedStage1Stmt.all(limit) as Array<Record<string, unknown>>;
|
|
350
|
+
return rows
|
|
351
|
+
.map((row) => normalizeStage1Row(row))
|
|
352
|
+
.filter((row): row is Stage1Output => row != null);
|
|
353
|
+
},
|
|
354
|
+
listStage1OlderThan(cutoffIso: string, limit = 100): Stage1Output[] {
|
|
355
|
+
const rows = listStage1OlderThanStmt.all(cutoffIso, limit) as Array<Record<string, unknown>>;
|
|
356
|
+
return rows
|
|
357
|
+
.map((row) => normalizeStage1Row(row))
|
|
358
|
+
.filter((row): row is Stage1Output => row != null);
|
|
359
|
+
},
|
|
360
|
+
markStage1Selected(sessionIds: string[]): void {
|
|
361
|
+
const tx = db.transaction(() => {
|
|
362
|
+
for (const sessionId of sessionIds) markStage1SelectedStmt.run(sessionId);
|
|
363
|
+
});
|
|
364
|
+
tx();
|
|
365
|
+
},
|
|
366
|
+
incrementStage1Usage(sessionId: string, usedAt = new Date().toISOString()): void {
|
|
367
|
+
incrementStage1UsageStmt.run(usedAt, sessionId);
|
|
368
|
+
},
|
|
369
|
+
countPendingSessions(): number {
|
|
370
|
+
const row = countPendingAllStmt.get() as { count?: number | string } | undefined;
|
|
371
|
+
return toNumber(row?.count, 0);
|
|
372
|
+
},
|
|
373
|
+
getConsolidationStatusCounts() {
|
|
374
|
+
const rows = countPendingByStatusStmt.all() as Array<{
|
|
375
|
+
status: ConsolidationStatus;
|
|
376
|
+
count: number | string;
|
|
377
|
+
}>;
|
|
378
|
+
const counts = {
|
|
379
|
+
pending: 0,
|
|
380
|
+
processing: 0,
|
|
381
|
+
done: 0,
|
|
382
|
+
skipped: 0,
|
|
383
|
+
failed: 0,
|
|
384
|
+
};
|
|
385
|
+
for (const row of rows) {
|
|
386
|
+
const status = row.status;
|
|
387
|
+
if (status in counts) {
|
|
388
|
+
counts[status] = toNumber(row.count, 0);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
return counts;
|
|
392
|
+
},
|
|
393
|
+
getStage1Count(): number {
|
|
394
|
+
const row = countStage1Stmt.get() as { count?: number | string } | undefined;
|
|
395
|
+
return toNumber(row?.count, 0);
|
|
396
|
+
},
|
|
397
|
+
getLastGeneratedAt(): string | null {
|
|
398
|
+
const row = lastGeneratedStmt.get() as { generated_at?: string } | undefined;
|
|
399
|
+
if (!row?.generated_at) return null;
|
|
400
|
+
return String(row.generated_at);
|
|
401
|
+
},
|
|
402
|
+
getLastUpdatedAt(): string | null {
|
|
403
|
+
const row = lastUpdatedStmt.get() as { updated_at?: string } | undefined;
|
|
404
|
+
if (!row?.updated_at) return null;
|
|
405
|
+
return String(row.updated_at);
|
|
406
|
+
},
|
|
407
|
+
close(): void {
|
|
408
|
+
db.close();
|
|
409
|
+
},
|
|
410
|
+
};
|
|
411
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export type ConsolidationStatus = "pending" | "processing" | "done" | "skipped" | "failed";
|
|
2
|
+
|
|
3
|
+
export interface PendingSession {
|
|
4
|
+
session_id: string;
|
|
5
|
+
session_file: string;
|
|
6
|
+
cwd: string;
|
|
7
|
+
git_root: string | null;
|
|
8
|
+
project_hash: string | null;
|
|
9
|
+
parent_session_id: string | null;
|
|
10
|
+
parent_session_file: string | null;
|
|
11
|
+
user_turn_count: number;
|
|
12
|
+
ended_at: string;
|
|
13
|
+
status: ConsolidationStatus;
|
|
14
|
+
error_message: string | null;
|
|
15
|
+
created_at: string;
|
|
16
|
+
updated_at: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface Stage1Output {
|
|
20
|
+
session_id: string;
|
|
21
|
+
session_file: string;
|
|
22
|
+
source_mtime_ms: number;
|
|
23
|
+
generated_at: string;
|
|
24
|
+
raw_memory: string;
|
|
25
|
+
rollout_summary: string;
|
|
26
|
+
scope: string;
|
|
27
|
+
status: ConsolidationStatus;
|
|
28
|
+
selected_for_phase2: boolean;
|
|
29
|
+
usage_count: number;
|
|
30
|
+
last_usage: string | null;
|
|
31
|
+
error_message: string | null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface JobReport {
|
|
35
|
+
session_id: string;
|
|
36
|
+
enqueued: boolean;
|
|
37
|
+
action: "created" | "updated" | "skipped";
|
|
38
|
+
status: ConsolidationStatus;
|
|
39
|
+
reason?: string;
|
|
40
|
+
now: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface ConsolidationStatusReport {
|
|
44
|
+
pending: number;
|
|
45
|
+
processing: number;
|
|
46
|
+
done: number;
|
|
47
|
+
skipped: number;
|
|
48
|
+
failed: number;
|
|
49
|
+
stage1Count: number;
|
|
50
|
+
lastGeneratedAt: string | null;
|
|
51
|
+
lastUpdatedAt: string | null;
|
|
52
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { LLMClient } from "../trainer/llmExtractor.js";
|
|
2
2
|
import type { SessionSearchHit } from "./sessionSearch.js";
|
|
3
|
+
import { cacheKeyForRerank, rerankCache } from "../cache/memoryCaches.js";
|
|
3
4
|
|
|
4
5
|
export interface RerankOptions {
|
|
5
6
|
client: LLMClient;
|
|
@@ -79,11 +80,17 @@ export async function rerankWithLLM(
|
|
|
79
80
|
const maxCandidates = opts.maxCandidates ?? DEFAULT_MAX_CANDIDATES;
|
|
80
81
|
const truncated = hits.slice(0, maxCandidates);
|
|
81
82
|
|
|
83
|
+
const cacheKey = cacheKeyForRerank(query, truncated);
|
|
84
|
+
const cached = rerankCache.get(cacheKey);
|
|
85
|
+
if (cached) return cached;
|
|
86
|
+
|
|
82
87
|
const prompt = buildRerankPrompt(query, truncated);
|
|
83
88
|
|
|
84
89
|
try {
|
|
85
90
|
const response = await opts.client.complete(prompt);
|
|
86
|
-
|
|
91
|
+
const results = parseRerankResponse(response, truncated.length);
|
|
92
|
+
if (results) rerankCache.set(cacheKey, results);
|
|
93
|
+
return results;
|
|
87
94
|
} catch {
|
|
88
95
|
return null;
|
|
89
96
|
}
|
|
@@ -3,6 +3,7 @@ import fs from "node:fs/promises";
|
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import type { SessionSearchHit } from "./sessionSearch.js";
|
|
6
|
+
import { messageText, parseJsonlSession as parseActiveBranchJsonl } from "../session/activeBranch.js";
|
|
6
7
|
|
|
7
8
|
export interface SqliteDatabase {
|
|
8
9
|
pragma(sql: string): unknown;
|
|
@@ -44,21 +45,6 @@ interface PiSessionFile {
|
|
|
44
45
|
messages?: PiSessionMessage[];
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
function messageText(content: unknown): string {
|
|
48
|
-
if (typeof content === "string") return content;
|
|
49
|
-
if (!Array.isArray(content)) return "";
|
|
50
|
-
const parts: string[] = [];
|
|
51
|
-
for (const block of content) {
|
|
52
|
-
if (typeof block === "string") { parts.push(block); continue; }
|
|
53
|
-
if (block && typeof block === "object") {
|
|
54
|
-
const b = block as Record<string, unknown>;
|
|
55
|
-
if (typeof b.text === "string") parts.push(b.text);
|
|
56
|
-
else if (typeof b.content === "string") parts.push(b.content);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
return parts.join("\n");
|
|
60
|
-
}
|
|
61
|
-
|
|
62
48
|
export interface SessionIndex {
|
|
63
49
|
/** Full rebuild — drop and re-populate from all session files. */
|
|
64
50
|
rebuildIndex(sessionsDir: string): Promise<{ indexed: number }>;
|
|
@@ -167,40 +153,6 @@ export function openSessionIndex(dbPath: string, injectedDb?: SqliteDatabase): S
|
|
|
167
153
|
return files;
|
|
168
154
|
}
|
|
169
155
|
|
|
170
|
-
function parseJsonlMessages(raw: string, filePath: string): {
|
|
171
|
-
id: string; title: string; createdAt: string;
|
|
172
|
-
messages: Array<{ role: string; content: string; index: number }>;
|
|
173
|
-
} | null {
|
|
174
|
-
const lines = raw.split("\n").filter((l) => l.trim());
|
|
175
|
-
if (lines.length === 0) return null;
|
|
176
|
-
let id = path.basename(filePath, path.extname(filePath));
|
|
177
|
-
let title = "";
|
|
178
|
-
let createdAt = "";
|
|
179
|
-
const messages: Array<{ role: string; content: string; index: number }> = [];
|
|
180
|
-
let idx = 0;
|
|
181
|
-
for (const line of lines) {
|
|
182
|
-
let obj: Record<string, unknown>;
|
|
183
|
-
try { obj = JSON.parse(line) as Record<string, unknown>; } catch { continue; }
|
|
184
|
-
if (obj.type === "session") {
|
|
185
|
-
id = (obj.id as string) ?? id;
|
|
186
|
-
title = (obj.title as string) ?? "";
|
|
187
|
-
createdAt = (obj.timestamp as string) ?? "";
|
|
188
|
-
continue;
|
|
189
|
-
}
|
|
190
|
-
if (obj.type === "message") {
|
|
191
|
-
const msg = (obj as { message?: PiSessionMessage }).message;
|
|
192
|
-
if (!msg?.role || !msg.content) continue;
|
|
193
|
-
if (msg.role !== "user" && msg.role !== "assistant") continue;
|
|
194
|
-
const text = messageText(msg.content);
|
|
195
|
-
if (text.trim()) {
|
|
196
|
-
messages.push({ role: msg.role, content: text, index: idx++ });
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
if (messages.length === 0) return null;
|
|
201
|
-
return { id, title, createdAt, messages };
|
|
202
|
-
}
|
|
203
|
-
|
|
204
156
|
async function loadSessionFiles(sessionsDir: string, modifiedAfter?: Date | null): Promise<Array<{
|
|
205
157
|
id: string;
|
|
206
158
|
title: string;
|
|
@@ -223,8 +175,19 @@ export function openSessionIndex(dbPath: string, injectedDb?: SqliteDatabase): S
|
|
|
223
175
|
try { raw = await fs.readFile(filePath, "utf8"); } catch { continue; }
|
|
224
176
|
|
|
225
177
|
if (filePath.endsWith(".jsonl")) {
|
|
226
|
-
const parsed =
|
|
227
|
-
if (parsed)
|
|
178
|
+
const parsed = parseActiveBranchJsonl(raw, filePath);
|
|
179
|
+
if (!parsed) continue;
|
|
180
|
+
const messages = parsed.turns.map((turn) => ({
|
|
181
|
+
role: turn.role,
|
|
182
|
+
content: turn.content,
|
|
183
|
+
index: turn.turnIndex,
|
|
184
|
+
}));
|
|
185
|
+
results.push({
|
|
186
|
+
id: parsed.id,
|
|
187
|
+
title: parsed.title,
|
|
188
|
+
createdAt: parsed.createdAt,
|
|
189
|
+
messages,
|
|
190
|
+
});
|
|
228
191
|
} else {
|
|
229
192
|
let session: PiSessionFile;
|
|
230
193
|
try { session = JSON.parse(raw) as PiSessionFile; } catch { continue; }
|
package/src/index.ts
CHANGED
|
@@ -19,8 +19,11 @@ export type {
|
|
|
19
19
|
} from "./types.js";
|
|
20
20
|
|
|
21
21
|
export {
|
|
22
|
+
defaultConsolidationConfig,
|
|
22
23
|
defaultMemoryConfig,
|
|
23
24
|
normalizeMemoryConfig,
|
|
25
|
+
type ConsolidationConfig,
|
|
26
|
+
type ConsolidationScheduleConfig,
|
|
24
27
|
type MemoryConfig,
|
|
25
28
|
type MemoryProvider,
|
|
26
29
|
} from "./config.js";
|
|
@@ -97,10 +100,12 @@ export {
|
|
|
97
100
|
|
|
98
101
|
export {
|
|
99
102
|
PRIVATE_MEMORY_BODY_BYTE_CAP,
|
|
103
|
+
SEMANTIC_FALLBACK_CANDIDATES,
|
|
100
104
|
renderFallbackPrivateMemory,
|
|
101
105
|
renderPrivateMemoryContext,
|
|
102
106
|
sanitizeUserBlock,
|
|
103
107
|
truncatePrivateMemoryBody,
|
|
108
|
+
type FallbackRenderOptions,
|
|
104
109
|
type PreflightQueryResult,
|
|
105
110
|
} from "./preflight/render.js";
|
|
106
111
|
|
|
@@ -156,6 +161,7 @@ export {
|
|
|
156
161
|
|
|
157
162
|
export {
|
|
158
163
|
trainBundle,
|
|
164
|
+
loadSessionFile,
|
|
159
165
|
loadSessions,
|
|
160
166
|
extractFacts,
|
|
161
167
|
extractFactsFromSessions,
|
|
@@ -237,3 +243,5 @@ export {
|
|
|
237
243
|
type RerankOptions,
|
|
238
244
|
type RankedResult,
|
|
239
245
|
} from "./fallback/llmRerank.js";
|
|
246
|
+
|
|
247
|
+
export * from "./consolidation/index.js";
|
package/src/local/graphQuery.ts
CHANGED
|
@@ -47,6 +47,7 @@ export class LocalGraphQuerier {
|
|
|
47
47
|
private entityById = new Map<string, BundleEntity>();
|
|
48
48
|
private entityByLabel = new Map<string, BundleEntity>();
|
|
49
49
|
private loaded = false;
|
|
50
|
+
private graphMtime = 0;
|
|
50
51
|
|
|
51
52
|
constructor(private readonly bundleRoot: string) {}
|
|
52
53
|
|
|
@@ -68,6 +69,7 @@ export class LocalGraphQuerier {
|
|
|
68
69
|
this.entityByLabel.set(alias.toLowerCase(), e);
|
|
69
70
|
}
|
|
70
71
|
}
|
|
72
|
+
this.graphMtime = fs.statSync(graphPath).mtimeMs;
|
|
71
73
|
this.loaded = true;
|
|
72
74
|
return true;
|
|
73
75
|
} catch {
|
|
@@ -79,6 +81,28 @@ export class LocalGraphQuerier {
|
|
|
79
81
|
return this.loaded;
|
|
80
82
|
}
|
|
81
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Returns true when the on-disk graph.json has changed since the last load().
|
|
86
|
+
* Always returns false when not yet loaded.
|
|
87
|
+
*/
|
|
88
|
+
isStale(): boolean {
|
|
89
|
+
if (!this.loaded) return false;
|
|
90
|
+
const graphPath = path.join(this.bundleRoot, "current", "graph.json");
|
|
91
|
+
try {
|
|
92
|
+
return fs.statSync(graphPath).mtimeMs !== this.graphMtime;
|
|
93
|
+
} catch {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Re-loads from disk if isStale(). Returns true when a reload actually happened.
|
|
100
|
+
*/
|
|
101
|
+
reloadIfStale(): boolean {
|
|
102
|
+
if (!this.isStale()) return false;
|
|
103
|
+
return this.load();
|
|
104
|
+
}
|
|
105
|
+
|
|
82
106
|
private findEntity(mention: string): BundleEntity | null {
|
|
83
107
|
const key = mention.toLowerCase().trim();
|
|
84
108
|
return this.entityByLabel.get(key) ?? null;
|