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.
@@ -25,28 +25,28 @@ import type { DatabaseSync } from "node:sqlite";
25
25
 
26
26
  /** A row in `turns`. */
27
27
  export interface TurnRow {
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;
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
- id: number;
45
- turnId: number;
46
- checkpointId: string;
47
- score: number;
48
- source: string;
49
- raptorLevel: number | null;
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
- return `conv_${randomBytes(8).toString("hex")}`;
57
+ return `conv_${randomBytes(8).toString("hex")}`;
58
58
  }
59
59
 
60
60
  function rowToTurn(r: Record<string, unknown>): TurnRow {
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
- };
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
- 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(),
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
- 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,
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
- ).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;
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
- turnId: number,
141
- hits: {
142
- checkpointId: string;
143
- score: number;
144
- source: RecallSource;
145
- raptorLevel?: number;
146
- }[],
147
- stateDir: string = getStateDir(),
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
- 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)
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
- withTx(db, () => {
159
- for (const h of hits) {
160
- stmt.run(turnId, h.checkpointId, h.score, h.source, h.raptorLevel ?? null);
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
- conversationId: string,
168
- turnIndex: number,
169
- stateDir: string = getStateDir(),
173
+ conversationId: string,
174
+ turnIndex: number,
175
+ stateDir: string = getStateDir(),
170
176
  ): TurnRow | null {
171
- const db = openStore(stateDir);
172
- const row = db
173
- .prepare(
174
- `SELECT * FROM turns WHERE conversation_id = ? AND turn_index = ?`,
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
- turnId: number,
183
- stateDir: string = getStateDir(),
186
+ turnId: number,
187
+ stateDir: string = getStateDir(),
184
188
  ): TurnRow | null {
185
- const db = openStore(stateDir);
186
- const row = db
187
- .prepare("SELECT * FROM turns WHERE id = ?")
188
- .get(turnId) as Record<string, unknown> | undefined;
189
- return row ? rowToTurn(row) : null;
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
- turnId: number,
195
- stateDir: string = getStateDir(),
198
+ turnId: number,
199
+ stateDir: string = getStateDir(),
196
200
  ): TurnRecallRow[] {
197
- const db = openStore(stateDir);
198
- const rows = db
199
- .prepare(
200
- `SELECT id, turn_id, checkpoint_id, score, source, raptor_level
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
- .all(turnId) as Array<Record<string, unknown>>;
204
- return rows.map((r) => ({
205
- id: r.id as number,
206
- turnId: r.turn_id as number,
207
- checkpointId: r.checkpoint_id as string,
208
- score: r.score as number,
209
- source: r.source as string,
210
- raptorLevel: (r.raptor_level as number | null) ?? null,
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
- conversationId: string,
217
- stateDir: string = getStateDir(),
220
+ conversationId: string,
221
+ stateDir: string = getStateDir(),
218
222
  ): TurnRow[] {
219
- const db = openStore(stateDir);
220
- const rows = db
221
- .prepare(
222
- `SELECT * FROM turns WHERE conversation_id = ? ORDER BY turn_index ASC`,
223
- )
224
- .all(conversationId) as Array<Record<string, unknown>>;
225
- return rows.map(rowToTurn);
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
- sessionId: string,
232
- stateDir: string = getStateDir(),
235
+ sessionId: string,
236
+ stateDir: string = getStateDir(),
233
237
  ): string {
234
- const st = loadSessionState(sessionId, stateDir);
235
- if (st.conversationId) return st.conversationId;
236
- const conv = newConversationId();
237
- saveSessionState(sessionId, {
238
- ...st,
239
- conversationId: conv,
240
- }, stateDir);
241
- return conv;
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
- parentConversationId: string,
256
- forkTurnId: number,
257
- stateDir: string = getStateDir(),
263
+ parentConversationId: string,
264
+ forkTurnId: number,
265
+ stateDir: string = getStateDir(),
258
266
  ): { conversationId: string; recalled: TurnRecallRow[] } {
259
- const childId = newConversationId();
260
- const db: DatabaseSync = openStore(stateDir);
261
- withTx(db, () => {
262
- db.prepare(
263
- `INSERT INTO conversation_branches
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
- ).run(childId, parentConversationId, forkTurnId, Date.now());
268
- });
269
- // Replay set: the parent's injected checkpoints at the fork turn.
270
- const recalled = listTurnRecall(forkTurnId, stateDir);
271
- return { conversationId: childId, recalled };
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
- sessionId: string,
277
- stateDir: string = getStateDir(),
284
+ sessionId: string,
285
+ stateDir: string = getStateDir(),
278
286
  ): void {
279
- const db: DatabaseSync = openStore(stateDir);
280
- const sid = normalizeSessionId(sessionId);
281
- withTx(db, () => {
282
- const turnIds = db
283
- .prepare("SELECT id FROM turns WHERE session_id = ?")
284
- .all(sid) as Array<{ id: number }>;
285
- const ids = turnIds.map((t) => t.id);
286
- if (ids.length > 0) {
287
- const placeholders = ids.map(() => "?").join(",");
288
- db.prepare(`DELETE FROM turn_recall WHERE turn_id IN (${placeholders})`).run(...ids);
289
- }
290
- db.prepare("DELETE FROM turns WHERE session_id = ?").run(sid);
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
  }