pi-mega-compact 0.8.25 → 0.8.26
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/dist/src/raptor-inject-summaries.test.js +10 -3
- package/dist/src/recall.js +24 -9
- package/dist/src/store/sqlite/turns.js +1 -3
- package/package.json +1 -1
- package/src/raptor-inject-summaries.test.ts +22 -6
- package/src/recall.ts +436 -368
- package/src/store/sqlite/turns.ts +177 -167
|
@@ -25,28 +25,28 @@ import type { DatabaseSync } from "node:sqlite";
|
|
|
25
25
|
|
|
26
26
|
/** A row in `turns`. */
|
|
27
27
|
export interface TurnRow {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
28
|
+
id: number;
|
|
29
|
+
conversationId: string;
|
|
30
|
+
sessionId: string;
|
|
31
|
+
turnIndex: number;
|
|
32
|
+
role: string | null;
|
|
33
|
+
startedAt: number;
|
|
34
|
+
endedAt: number | null;
|
|
35
|
+
ctxTokens: number | null;
|
|
36
|
+
ctxPercent: number | null;
|
|
37
|
+
pressureBand: string | null;
|
|
38
|
+
modelId: string | null;
|
|
39
|
+
epochId: string | null;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
/** A row in `turn_recall`. */
|
|
43
43
|
export interface TurnRecallRow {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
44
|
+
id: number;
|
|
45
|
+
turnId: number;
|
|
46
|
+
checkpointId: string;
|
|
47
|
+
score: number;
|
|
48
|
+
source: string;
|
|
49
|
+
raptorLevel: number | null;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
/** Where a recalled hit came from (recorded on turn_recall.source). */
|
|
@@ -54,24 +54,24 @@ export type RecallSource = "flat" | "raptor" | "cross-repo" | "memory";
|
|
|
54
54
|
|
|
55
55
|
/** Generate a new conversation id (`conv_` + 16 hex). */
|
|
56
56
|
export function newConversationId(): string {
|
|
57
|
-
|
|
57
|
+
return `conv_${randomBytes(8).toString("hex")}`;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
function rowToTurn(r: Record<string, unknown>): TurnRow {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
61
|
+
return {
|
|
62
|
+
id: r.id as number,
|
|
63
|
+
conversationId: r.conversation_id as string,
|
|
64
|
+
sessionId: r.session_id as string,
|
|
65
|
+
turnIndex: r.turn_index as number,
|
|
66
|
+
role: (r.role as string | null) ?? null,
|
|
67
|
+
startedAt: r.started_at as number,
|
|
68
|
+
endedAt: (r.ended_at as number | null) ?? null,
|
|
69
|
+
ctxTokens: (r.ctx_tokens as number | null) ?? null,
|
|
70
|
+
ctxPercent: (r.ctx_percent as number | null) ?? null,
|
|
71
|
+
pressureBand: (r.pressure_band as string | null) ?? null,
|
|
72
|
+
modelId: (r.model_id as string | null) ?? null,
|
|
73
|
+
epochId: (r.epoch_id as string | null) ?? null,
|
|
74
|
+
};
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
/**
|
|
@@ -80,26 +80,26 @@ function rowToTurn(r: Record<string, unknown>): TurnRow {
|
|
|
80
80
|
* Idempotent on (session_id, turn_index) — re-upserting overwrites metrics.
|
|
81
81
|
*/
|
|
82
82
|
export function recordTurn(
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
83
|
+
input: {
|
|
84
|
+
conversationId: string;
|
|
85
|
+
sessionId: string;
|
|
86
|
+
turnIndex: number;
|
|
87
|
+
role?: string;
|
|
88
|
+
startedAt?: number;
|
|
89
|
+
endedAt?: number;
|
|
90
|
+
ctxTokens?: number;
|
|
91
|
+
ctxPercent?: number;
|
|
92
|
+
pressureBand?: string;
|
|
93
|
+
modelId?: string;
|
|
94
|
+
epochId?: string;
|
|
95
|
+
},
|
|
96
|
+
stateDir: string = getStateDir(),
|
|
97
97
|
): number {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
98
|
+
const db = openStore(stateDir);
|
|
99
|
+
const sid = normalizeSessionId(input.sessionId);
|
|
100
|
+
const startedAt = input.startedAt ?? Date.now();
|
|
101
|
+
db.prepare(
|
|
102
|
+
`INSERT INTO turns (conversation_id, session_id, turn_index, role, started_at,
|
|
103
103
|
ended_at, ctx_tokens, ctx_percent, pressure_band, model_id, epoch_id)
|
|
104
104
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
105
105
|
ON CONFLICT(session_id, turn_index) DO UPDATE SET
|
|
@@ -112,23 +112,23 @@ export function recordTurn(
|
|
|
112
112
|
pressure_band = COALESCE(excluded.pressure_band, pressure_band),
|
|
113
113
|
model_id = COALESCE(excluded.model_id, model_id),
|
|
114
114
|
epoch_id = COALESCE(excluded.epoch_id, epoch_id)`,
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
115
|
+
).run(
|
|
116
|
+
input.conversationId,
|
|
117
|
+
sid,
|
|
118
|
+
input.turnIndex,
|
|
119
|
+
input.role ?? null,
|
|
120
|
+
startedAt,
|
|
121
|
+
input.endedAt ?? null,
|
|
122
|
+
input.ctxTokens ?? null,
|
|
123
|
+
input.ctxPercent ?? null,
|
|
124
|
+
input.pressureBand ?? null,
|
|
125
|
+
input.modelId ?? null,
|
|
126
|
+
input.epochId ?? null,
|
|
127
|
+
);
|
|
128
|
+
const row = db
|
|
129
|
+
.prepare("SELECT id FROM turns WHERE session_id = ? AND turn_index = ?")
|
|
130
|
+
.get(sid, input.turnIndex) as { id: number };
|
|
131
|
+
return row.id;
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
/**
|
|
@@ -137,108 +137,116 @@ export function recordTurn(
|
|
|
137
137
|
* path + score. RAPTOR cluster hits carry raptorLevel. Best-effort + non-fatal.
|
|
138
138
|
*/
|
|
139
139
|
export function recordTurnRecall(
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
140
|
+
turnId: number,
|
|
141
|
+
hits: {
|
|
142
|
+
checkpointId: string;
|
|
143
|
+
score: number;
|
|
144
|
+
source: RecallSource;
|
|
145
|
+
raptorLevel?: number;
|
|
146
|
+
}[],
|
|
147
|
+
stateDir: string = getStateDir(),
|
|
148
148
|
): void {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
149
|
+
if (hits.length === 0) return;
|
|
150
|
+
const db = openStore(stateDir);
|
|
151
|
+
const stmt = db.prepare(
|
|
152
|
+
`INSERT INTO turn_recall (turn_id, checkpoint_id, score, source, raptor_level)
|
|
153
153
|
VALUES (?, ?, ?, ?, ?)
|
|
154
154
|
ON CONFLICT(turn_id, checkpoint_id) DO UPDATE SET
|
|
155
155
|
score = excluded.score, source = excluded.source,
|
|
156
156
|
raptor_level = excluded.raptor_level`,
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
157
|
+
);
|
|
158
|
+
withTx(db, () => {
|
|
159
|
+
for (const h of hits) {
|
|
160
|
+
stmt.run(
|
|
161
|
+
turnId,
|
|
162
|
+
h.checkpointId,
|
|
163
|
+
h.score,
|
|
164
|
+
h.source,
|
|
165
|
+
h.raptorLevel ?? null,
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
163
169
|
}
|
|
164
170
|
|
|
165
171
|
/** Get a turn by conversation id + turn index (the lookup a fork uses). */
|
|
166
172
|
export function getTurn(
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
173
|
+
conversationId: string,
|
|
174
|
+
turnIndex: number,
|
|
175
|
+
stateDir: string = getStateDir(),
|
|
170
176
|
): TurnRow | null {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
.get(conversationId, turnIndex) as Record<string, unknown> | undefined;
|
|
177
|
-
return row ? rowToTurn(row) : null;
|
|
177
|
+
const db = openStore(stateDir);
|
|
178
|
+
const row = db
|
|
179
|
+
.prepare(`SELECT * FROM turns WHERE conversation_id = ? AND turn_index = ?`)
|
|
180
|
+
.get(conversationId, turnIndex) as Record<string, unknown> | undefined;
|
|
181
|
+
return row ? rowToTurn(row) : null;
|
|
178
182
|
}
|
|
179
183
|
|
|
180
184
|
/** Get a turn by its global id. */
|
|
181
185
|
export function getTurnById(
|
|
182
|
-
|
|
183
|
-
|
|
186
|
+
turnId: number,
|
|
187
|
+
stateDir: string = getStateDir(),
|
|
184
188
|
): TurnRow | null {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
189
|
+
const db = openStore(stateDir);
|
|
190
|
+
const row = db.prepare("SELECT * FROM turns WHERE id = ?").get(turnId) as
|
|
191
|
+
| Record<string, unknown>
|
|
192
|
+
| undefined;
|
|
193
|
+
return row ? rowToTurn(row) : null;
|
|
190
194
|
}
|
|
191
195
|
|
|
192
196
|
/** All turn_recall rows for a turn (what was injected at that turn). */
|
|
193
197
|
export function listTurnRecall(
|
|
194
|
-
|
|
195
|
-
|
|
198
|
+
turnId: number,
|
|
199
|
+
stateDir: string = getStateDir(),
|
|
196
200
|
): TurnRecallRow[] {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
+
const db = openStore(stateDir);
|
|
202
|
+
const rows = db
|
|
203
|
+
.prepare(
|
|
204
|
+
`SELECT id, turn_id, checkpoint_id, score, source, raptor_level
|
|
201
205
|
FROM turn_recall WHERE turn_id = ? ORDER BY score DESC`,
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
206
|
+
)
|
|
207
|
+
.all(turnId) as Array<Record<string, unknown>>;
|
|
208
|
+
return rows.map((r) => ({
|
|
209
|
+
id: r.id as number,
|
|
210
|
+
turnId: r.turn_id as number,
|
|
211
|
+
checkpointId: r.checkpoint_id as string,
|
|
212
|
+
score: r.score as number,
|
|
213
|
+
source: r.source as string,
|
|
214
|
+
raptorLevel: (r.raptor_level as number | null) ?? null,
|
|
215
|
+
}));
|
|
212
216
|
}
|
|
213
217
|
|
|
214
218
|
/** All turns in a conversation, ascending by turn_index. */
|
|
215
219
|
export function listConversationTurns(
|
|
216
|
-
|
|
217
|
-
|
|
220
|
+
conversationId: string,
|
|
221
|
+
stateDir: string = getStateDir(),
|
|
218
222
|
): TurnRow[] {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
223
|
+
const db = openStore(stateDir);
|
|
224
|
+
const rows = db
|
|
225
|
+
.prepare(
|
|
226
|
+
`SELECT * FROM turns WHERE conversation_id = ? ORDER BY turn_index ASC`,
|
|
227
|
+
)
|
|
228
|
+
.all(conversationId) as Array<Record<string, unknown>>;
|
|
229
|
+
return rows.map(rowToTurn);
|
|
226
230
|
}
|
|
227
231
|
|
|
228
232
|
/** Resolve a session's conversation id, generating + persisting one if none.
|
|
229
233
|
* A resumed session inherits its existing conversationId from session_state. */
|
|
230
234
|
export function ensureConversationId(
|
|
231
|
-
|
|
232
|
-
|
|
235
|
+
sessionId: string,
|
|
236
|
+
stateDir: string = getStateDir(),
|
|
233
237
|
): string {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
238
|
+
const st = loadSessionState(sessionId, stateDir);
|
|
239
|
+
if (st.conversationId) return st.conversationId;
|
|
240
|
+
const conv = newConversationId();
|
|
241
|
+
saveSessionState(
|
|
242
|
+
sessionId,
|
|
243
|
+
{
|
|
244
|
+
...st,
|
|
245
|
+
conversationId: conv,
|
|
246
|
+
},
|
|
247
|
+
stateDir,
|
|
248
|
+
);
|
|
249
|
+
return conv;
|
|
242
250
|
}
|
|
243
251
|
|
|
244
252
|
/**
|
|
@@ -252,41 +260,43 @@ export function ensureConversationId(
|
|
|
252
260
|
* injected in the new session's session_state so they're not re-recalled.
|
|
253
261
|
*/
|
|
254
262
|
export function forkConversation(
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
263
|
+
parentConversationId: string,
|
|
264
|
+
forkTurnId: number,
|
|
265
|
+
stateDir: string = getStateDir(),
|
|
258
266
|
): { conversationId: string; recalled: TurnRecallRow[] } {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
267
|
+
const childId = newConversationId();
|
|
268
|
+
const db: DatabaseSync = openStore(stateDir);
|
|
269
|
+
withTx(db, () => {
|
|
270
|
+
db.prepare(
|
|
271
|
+
`INSERT INTO conversation_branches
|
|
264
272
|
(conversation_id, parent_conversation_id, fork_turn_id, created_at)
|
|
265
273
|
VALUES (?, ?, ?, ?)
|
|
266
274
|
ON CONFLICT(conversation_id) DO NOTHING`,
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
275
|
+
).run(childId, parentConversationId, forkTurnId, Date.now());
|
|
276
|
+
});
|
|
277
|
+
// Replay set: the parent's injected checkpoints at the fork turn.
|
|
278
|
+
const recalled = listTurnRecall(forkTurnId, stateDir);
|
|
279
|
+
return { conversationId: childId, recalled };
|
|
272
280
|
}
|
|
273
281
|
|
|
274
282
|
/** Clear turn tracking rows for a session (tests / DR). */
|
|
275
283
|
export function clearTurns(
|
|
276
|
-
|
|
277
|
-
|
|
284
|
+
sessionId: string,
|
|
285
|
+
stateDir: string = getStateDir(),
|
|
278
286
|
): void {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
287
|
+
const db: DatabaseSync = openStore(stateDir);
|
|
288
|
+
const sid = normalizeSessionId(sessionId);
|
|
289
|
+
withTx(db, () => {
|
|
290
|
+
const turnIds = db
|
|
291
|
+
.prepare("SELECT id FROM turns WHERE session_id = ?")
|
|
292
|
+
.all(sid) as Array<{ id: number }>;
|
|
293
|
+
const ids = turnIds.map((t) => t.id);
|
|
294
|
+
if (ids.length > 0) {
|
|
295
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
296
|
+
db.prepare(
|
|
297
|
+
`DELETE FROM turn_recall WHERE turn_id IN (${placeholders})`,
|
|
298
|
+
).run(...ids);
|
|
299
|
+
}
|
|
300
|
+
db.prepare("DELETE FROM turns WHERE session_id = ?").run(sid);
|
|
301
|
+
});
|
|
292
302
|
}
|