praana 0.5.0

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.
Files changed (204) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +124 -0
  3. package/bin/praana.js +17 -0
  4. package/bin/pran.js +17 -0
  5. package/dist/app-banner.d.ts +11 -0
  6. package/dist/app-banner.js +161 -0
  7. package/dist/app-controller.d.ts +44 -0
  8. package/dist/app-controller.js +143 -0
  9. package/dist/app-identity.d.ts +18 -0
  10. package/dist/app-identity.js +52 -0
  11. package/dist/auto-compact.d.ts +16 -0
  12. package/dist/auto-compact.js +101 -0
  13. package/dist/cli-args.d.ts +14 -0
  14. package/dist/cli-args.js +69 -0
  15. package/dist/compile-classic.d.ts +21 -0
  16. package/dist/compile-classic.js +106 -0
  17. package/dist/compiler.d.ts +75 -0
  18. package/dist/compiler.js +406 -0
  19. package/dist/config.d.ts +3 -0
  20. package/dist/config.js +433 -0
  21. package/dist/context-engine/activity-log.d.ts +9 -0
  22. package/dist/context-engine/activity-log.js +109 -0
  23. package/dist/context-engine/artifact-store.d.ts +32 -0
  24. package/dist/context-engine/artifact-store.js +272 -0
  25. package/dist/context-engine/bm25.d.ts +3 -0
  26. package/dist/context-engine/bm25.js +32 -0
  27. package/dist/context-engine/checkpoint.d.ts +34 -0
  28. package/dist/context-engine/checkpoint.js +430 -0
  29. package/dist/context-engine/classify.d.ts +3 -0
  30. package/dist/context-engine/classify.js +60 -0
  31. package/dist/context-engine/db.d.ts +73 -0
  32. package/dist/context-engine/db.js +505 -0
  33. package/dist/context-engine/distiller.d.ts +30 -0
  34. package/dist/context-engine/distiller.js +67 -0
  35. package/dist/context-engine/engine-compiler.d.ts +23 -0
  36. package/dist/context-engine/engine-compiler.js +297 -0
  37. package/dist/context-engine/error-tracker.d.ts +21 -0
  38. package/dist/context-engine/error-tracker.js +74 -0
  39. package/dist/context-engine/event-lineage.d.ts +26 -0
  40. package/dist/context-engine/event-lineage.js +120 -0
  41. package/dist/context-engine/extraction.d.ts +26 -0
  42. package/dist/context-engine/extraction.js +83 -0
  43. package/dist/context-engine/index.d.ts +82 -0
  44. package/dist/context-engine/index.js +238 -0
  45. package/dist/context-engine/scoring.d.ts +13 -0
  46. package/dist/context-engine/scoring.js +47 -0
  47. package/dist/context-engine/state-snapshot.d.ts +8 -0
  48. package/dist/context-engine/state-snapshot.js +50 -0
  49. package/dist/context-engine/summarize.d.ts +6 -0
  50. package/dist/context-engine/summarize.js +32 -0
  51. package/dist/context-engine/telemetry.d.ts +25 -0
  52. package/dist/context-engine/telemetry.js +64 -0
  53. package/dist/context-engine/turn-digest.d.ts +50 -0
  54. package/dist/context-engine/turn-digest.js +250 -0
  55. package/dist/context-engine/turn-ledger.d.ts +18 -0
  56. package/dist/context-engine/turn-ledger.js +184 -0
  57. package/dist/context-engine/turn-recorder.d.ts +24 -0
  58. package/dist/context-engine/turn-recorder.js +88 -0
  59. package/dist/context-engine/types.d.ts +201 -0
  60. package/dist/context-engine/types.js +4 -0
  61. package/dist/context-pressure.d.ts +19 -0
  62. package/dist/context-pressure.js +36 -0
  63. package/dist/distillers/generic.d.ts +14 -0
  64. package/dist/distillers/generic.js +93 -0
  65. package/dist/distillers/git-diff.d.ts +8 -0
  66. package/dist/distillers/git-diff.js +119 -0
  67. package/dist/distillers/index.d.ts +2 -0
  68. package/dist/distillers/index.js +16 -0
  69. package/dist/distillers/npm-test.d.ts +8 -0
  70. package/dist/distillers/npm-test.js +50 -0
  71. package/dist/distillers/rg-results.d.ts +8 -0
  72. package/dist/distillers/rg-results.js +28 -0
  73. package/dist/distillers/tsc-errors.d.ts +8 -0
  74. package/dist/distillers/tsc-errors.js +52 -0
  75. package/dist/event-log.d.ts +56 -0
  76. package/dist/event-log.js +214 -0
  77. package/dist/llm.d.ts +29 -0
  78. package/dist/llm.js +155 -0
  79. package/dist/logger.d.ts +94 -0
  80. package/dist/logger.js +287 -0
  81. package/dist/main.d.ts +1 -0
  82. package/dist/main.js +54 -0
  83. package/dist/memory/confidence.d.ts +7 -0
  84. package/dist/memory/confidence.js +37 -0
  85. package/dist/memory/consolidation.d.ts +26 -0
  86. package/dist/memory/consolidation.js +166 -0
  87. package/dist/memory/db.d.ts +40 -0
  88. package/dist/memory/db.js +283 -0
  89. package/dist/memory/dedup.d.ts +6 -0
  90. package/dist/memory/dedup.js +50 -0
  91. package/dist/memory/embedder-factory.d.ts +3 -0
  92. package/dist/memory/embedder-factory.js +81 -0
  93. package/dist/memory/embeddings.d.ts +15 -0
  94. package/dist/memory/embeddings.js +67 -0
  95. package/dist/memory/index.d.ts +9 -0
  96. package/dist/memory/index.js +11 -0
  97. package/dist/memory/ollama-summarizer.d.ts +19 -0
  98. package/dist/memory/ollama-summarizer.js +72 -0
  99. package/dist/memory/openai-summarizer.d.ts +21 -0
  100. package/dist/memory/openai-summarizer.js +51 -0
  101. package/dist/memory/store.d.ts +61 -0
  102. package/dist/memory/store.js +502 -0
  103. package/dist/memory/summarizer-factory.d.ts +3 -0
  104. package/dist/memory/summarizer-factory.js +69 -0
  105. package/dist/memory/summarizer.d.ts +4 -0
  106. package/dist/memory/summarizer.js +112 -0
  107. package/dist/memory/types.d.ts +87 -0
  108. package/dist/memory/types.js +17 -0
  109. package/dist/model-context.d.ts +15 -0
  110. package/dist/model-context.js +212 -0
  111. package/dist/project-detector.d.ts +37 -0
  112. package/dist/project-detector.js +604 -0
  113. package/dist/render.d.ts +15 -0
  114. package/dist/render.js +46 -0
  115. package/dist/session.d.ts +118 -0
  116. package/dist/session.js +809 -0
  117. package/dist/skills/index.d.ts +69 -0
  118. package/dist/skills/index.js +885 -0
  119. package/dist/skills/types.d.ts +93 -0
  120. package/dist/skills/types.js +8 -0
  121. package/dist/slash-commands.d.ts +14 -0
  122. package/dist/slash-commands.js +301 -0
  123. package/dist/state-graph.d.ts +38 -0
  124. package/dist/state-graph.js +255 -0
  125. package/dist/status-bar.d.ts +54 -0
  126. package/dist/status-bar.js +184 -0
  127. package/dist/thinking-display.d.ts +21 -0
  128. package/dist/thinking-display.js +37 -0
  129. package/dist/tool-summary.d.ts +4 -0
  130. package/dist/tool-summary.js +67 -0
  131. package/dist/tools/index.d.ts +925 -0
  132. package/dist/tools/index.js +86 -0
  133. package/dist/tools/knowledge.d.ts +140 -0
  134. package/dist/tools/knowledge.js +260 -0
  135. package/dist/tools/memory.d.ts +39 -0
  136. package/dist/tools/memory.js +300 -0
  137. package/dist/tools/search-code.d.ts +134 -0
  138. package/dist/tools/search-code.js +390 -0
  139. package/dist/tools/system.d.ts +16 -0
  140. package/dist/tools/system.js +499 -0
  141. package/dist/tools/tool-def.d.ts +6 -0
  142. package/dist/tools/tool-def.js +3 -0
  143. package/dist/turn-control.d.ts +51 -0
  144. package/dist/turn-control.js +210 -0
  145. package/dist/turn.d.ts +20 -0
  146. package/dist/turn.js +624 -0
  147. package/dist/types.d.ts +233 -0
  148. package/dist/types.js +4 -0
  149. package/dist/ui/readline-ui.d.ts +2 -0
  150. package/dist/ui/readline-ui.js +176 -0
  151. package/dist/ui/tui/app.d.ts +13 -0
  152. package/dist/ui/tui/app.js +270 -0
  153. package/dist/ui/tui/busy-indicator.d.ts +2 -0
  154. package/dist/ui/tui/busy-indicator.js +13 -0
  155. package/dist/ui/tui/components/gutter-rule.d.ts +5 -0
  156. package/dist/ui/tui/components/gutter-rule.js +9 -0
  157. package/dist/ui/tui/components/inline-tool-row.d.ts +10 -0
  158. package/dist/ui/tui/components/inline-tool-row.js +8 -0
  159. package/dist/ui/tui/components/prompt-input.d.ts +20 -0
  160. package/dist/ui/tui/components/prompt-input.js +120 -0
  161. package/dist/ui/tui/components/system-line.d.ts +5 -0
  162. package/dist/ui/tui/components/system-line.js +6 -0
  163. package/dist/ui/tui/components/thinking-block.d.ts +11 -0
  164. package/dist/ui/tui/components/thinking-block.js +31 -0
  165. package/dist/ui/tui/components/toast-line.d.ts +4 -0
  166. package/dist/ui/tui/components/toast-line.js +8 -0
  167. package/dist/ui/tui/components/tool-result-line.d.ts +5 -0
  168. package/dist/ui/tui/components/tool-result-line.js +6 -0
  169. package/dist/ui/tui/components/turn-footer.d.ts +5 -0
  170. package/dist/ui/tui/components/turn-footer.js +7 -0
  171. package/dist/ui/tui/components/user-block.d.ts +6 -0
  172. package/dist/ui/tui/components/user-block.js +6 -0
  173. package/dist/ui/tui/logo-banner.d.ts +5 -0
  174. package/dist/ui/tui/logo-banner.js +8 -0
  175. package/dist/ui/tui/markdown-render.d.ts +16 -0
  176. package/dist/ui/tui/markdown-render.js +218 -0
  177. package/dist/ui/tui/palette.d.ts +12 -0
  178. package/dist/ui/tui/palette.js +13 -0
  179. package/dist/ui/tui/reasoning-summary.d.ts +12 -0
  180. package/dist/ui/tui/reasoning-summary.js +27 -0
  181. package/dist/ui/tui/reducer.d.ts +92 -0
  182. package/dist/ui/tui/reducer.js +260 -0
  183. package/dist/ui/tui/run.d.ts +3 -0
  184. package/dist/ui/tui/run.js +40 -0
  185. package/dist/ui/tui/sink.d.ts +4 -0
  186. package/dist/ui/tui/sink.js +89 -0
  187. package/dist/ui/tui/status-bar-view.d.ts +5 -0
  188. package/dist/ui/tui/status-bar-view.js +44 -0
  189. package/dist/ui/tui/terminal-height.d.ts +12 -0
  190. package/dist/ui/tui/terminal-height.js +20 -0
  191. package/dist/ui/tui/terminal-width.d.ts +2 -0
  192. package/dist/ui/tui/terminal-width.js +5 -0
  193. package/dist/ui/tui/tool-display.d.ts +23 -0
  194. package/dist/ui/tui/tool-display.js +217 -0
  195. package/dist/ui/tui/transcript-line.d.ts +12 -0
  196. package/dist/ui/tui/transcript-line.js +43 -0
  197. package/dist/ui/tui/transcript-replay.d.ts +12 -0
  198. package/dist/ui/tui/transcript-replay.js +117 -0
  199. package/dist/ui-events.d.ts +39 -0
  200. package/dist/ui-events.js +33 -0
  201. package/dist/ui.d.ts +77 -0
  202. package/dist/ui.js +179 -0
  203. package/package.json +73 -0
  204. package/praana.config.example.toml +231 -0
@@ -0,0 +1,505 @@
1
+ import Database from "better-sqlite3";
2
+ const ARTIFACT_SCHEMA = `
3
+ CREATE TABLE IF NOT EXISTS context_artifacts (
4
+ id TEXT PRIMARY KEY,
5
+ sha256 TEXT NOT NULL,
6
+ session_id TEXT NOT NULL,
7
+ source_tool TEXT NOT NULL,
8
+ command TEXT,
9
+ created_turn INTEGER NOT NULL,
10
+ raw_tokens INTEGER NOT NULL,
11
+ raw_text TEXT NOT NULL,
12
+ summary TEXT NOT NULL,
13
+ content_type TEXT NOT NULL,
14
+ last_accessed_turn INTEGER NOT NULL,
15
+ access_count INTEGER NOT NULL DEFAULT 0,
16
+ created_at INTEGER NOT NULL
17
+ );
18
+
19
+ CREATE INDEX IF NOT EXISTS idx_context_artifacts_session
20
+ ON context_artifacts(session_id);
21
+ CREATE INDEX IF NOT EXISTS idx_context_artifacts_sha256
22
+ ON context_artifacts(sha256);
23
+
24
+ CREATE TABLE IF NOT EXISTS distiller_stats (
25
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
26
+ session_id TEXT NOT NULL,
27
+ tool TEXT NOT NULL,
28
+ content_type TEXT NOT NULL,
29
+ distiller TEXT NOT NULL,
30
+ input_tokens INTEGER NOT NULL,
31
+ output_tokens INTEGER NOT NULL,
32
+ savings_pct REAL NOT NULL,
33
+ exec_time_ms INTEGER NOT NULL,
34
+ turn INTEGER NOT NULL,
35
+ created_at INTEGER NOT NULL
36
+ );
37
+
38
+ CREATE INDEX IF NOT EXISTS idx_distiller_stats_session
39
+ ON distiller_stats(session_id);
40
+
41
+ CREATE TABLE IF NOT EXISTS turn_ledger (
42
+ session_id TEXT NOT NULL,
43
+ turn INTEGER NOT NULL,
44
+ user_message TEXT NOT NULL,
45
+ assistant_message TEXT NOT NULL,
46
+ tool_calls_json TEXT NOT NULL,
47
+ artifact_ids_json TEXT NOT NULL,
48
+ files_read_json TEXT NOT NULL,
49
+ files_written_json TEXT NOT NULL,
50
+ errors_json TEXT NOT NULL,
51
+ token_count INTEGER NOT NULL,
52
+ search_text TEXT NOT NULL,
53
+ timestamp INTEGER NOT NULL,
54
+ PRIMARY KEY (session_id, turn)
55
+ );
56
+
57
+ CREATE INDEX IF NOT EXISTS idx_turn_ledger_session
58
+ ON turn_ledger(session_id);
59
+
60
+ CREATE TABLE IF NOT EXISTS turn_digests (
61
+ session_id TEXT NOT NULL,
62
+ turn INTEGER NOT NULL,
63
+ digest_json TEXT NOT NULL,
64
+ created_at INTEGER NOT NULL,
65
+ PRIMARY KEY (session_id, turn)
66
+ );
67
+
68
+ CREATE TABLE IF NOT EXISTS activity_log (
69
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
70
+ session_id TEXT NOT NULL,
71
+ turn INTEGER NOT NULL,
72
+ type TEXT NOT NULL,
73
+ summary TEXT NOT NULL,
74
+ artifact_ref TEXT,
75
+ created_at INTEGER NOT NULL
76
+ );
77
+
78
+ CREATE INDEX IF NOT EXISTS idx_activity_log_session
79
+ ON activity_log(session_id, id DESC);
80
+
81
+ CREATE TABLE IF NOT EXISTS extraction_state (
82
+ session_id TEXT PRIMARY KEY,
83
+ state_json TEXT NOT NULL,
84
+ updated_at INTEGER NOT NULL
85
+ );
86
+
87
+ CREATE TABLE IF NOT EXISTS session_checkpoints (
88
+ session_id TEXT PRIMARY KEY,
89
+ checkpoint_json TEXT NOT NULL,
90
+ updated_at INTEGER NOT NULL
91
+ );
92
+
93
+ CREATE TABLE IF NOT EXISTS artifact_access (
94
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
95
+ artifact_id TEXT NOT NULL,
96
+ session_id TEXT NOT NULL,
97
+ access_type TEXT NOT NULL,
98
+ turn INTEGER NOT NULL,
99
+ created_at INTEGER NOT NULL
100
+ );
101
+
102
+ CREATE INDEX IF NOT EXISTS idx_artifact_access_session
103
+ ON artifact_access(session_id, artifact_id);
104
+
105
+ CREATE TABLE IF NOT EXISTS session_stats (
106
+ session_id TEXT PRIMARY KEY,
107
+ context_engine_enabled INTEGER NOT NULL DEFAULT 0,
108
+ pressure_events INTEGER NOT NULL DEFAULT 0,
109
+ compaction_triggers INTEGER NOT NULL DEFAULT 0,
110
+ artifact_retrievals INTEGER NOT NULL DEFAULT 0,
111
+ total_distiller_savings REAL NOT NULL DEFAULT 0,
112
+ total_turns INTEGER NOT NULL DEFAULT 0,
113
+ updated_at INTEGER NOT NULL
114
+ );
115
+ `;
116
+ export function openContextEngineDb(dbPath) {
117
+ const db = new Database(dbPath);
118
+ db.pragma("journal_mode = WAL");
119
+ db.exec(ARTIFACT_SCHEMA);
120
+ return db;
121
+ }
122
+ function rowToArtifact(row) {
123
+ return {
124
+ id: row.id,
125
+ sha256: row.sha256,
126
+ sessionId: row.session_id,
127
+ sourceTool: row.source_tool,
128
+ command: row.command ?? undefined,
129
+ createdTurn: row.created_turn,
130
+ rawTokens: row.raw_tokens,
131
+ rawText: row.raw_text,
132
+ summary: row.summary,
133
+ contentType: row.content_type,
134
+ lastAccessedTurn: row.last_accessed_turn,
135
+ accessCount: row.access_count,
136
+ };
137
+ }
138
+ export function findArtifactByHash(db, sha256) {
139
+ const row = db
140
+ .prepare("SELECT * FROM context_artifacts WHERE sha256 = ? LIMIT 1")
141
+ .get(sha256);
142
+ return row ? rowToArtifact(row) : null;
143
+ }
144
+ export function getArtifactById(db, id) {
145
+ const row = db
146
+ .prepare("SELECT * FROM context_artifacts WHERE id = ?")
147
+ .get(id);
148
+ return row ? rowToArtifact(row) : null;
149
+ }
150
+ export function insertArtifact(db, artifact) {
151
+ db.prepare(`INSERT INTO context_artifacts (
152
+ id, sha256, session_id, source_tool, command, created_turn,
153
+ raw_tokens, raw_text, summary, content_type,
154
+ last_accessed_turn, access_count, created_at
155
+ ) VALUES (
156
+ @id, @sha256, @sessionId, @sourceTool, @command, @createdTurn,
157
+ @rawTokens, @rawText, @summary, @contentType,
158
+ @lastAccessedTurn, @accessCount, @createdAt
159
+ )`).run({
160
+ id: artifact.id,
161
+ sha256: artifact.sha256,
162
+ sessionId: artifact.sessionId,
163
+ sourceTool: artifact.sourceTool,
164
+ command: artifact.command ?? null,
165
+ createdTurn: artifact.createdTurn,
166
+ rawTokens: artifact.rawTokens,
167
+ rawText: artifact.rawText,
168
+ summary: artifact.summary,
169
+ contentType: artifact.contentType,
170
+ lastAccessedTurn: artifact.lastAccessedTurn,
171
+ accessCount: artifact.accessCount,
172
+ createdAt: Date.now(),
173
+ });
174
+ }
175
+ export function touchArtifactAccess(db, id, turn) {
176
+ db.prepare(`UPDATE context_artifacts
177
+ SET last_accessed_turn = @turn, access_count = access_count + 1
178
+ WHERE id = @id`).run({ id, turn });
179
+ }
180
+ export function updateArtifactSummary(db, id, summary) {
181
+ db.prepare("UPDATE context_artifacts SET summary = @summary WHERE id = @id").run({
182
+ id,
183
+ summary,
184
+ });
185
+ }
186
+ export function insertDistillerStat(db, row) {
187
+ db.prepare(`INSERT INTO distiller_stats (
188
+ session_id, tool, content_type, distiller,
189
+ input_tokens, output_tokens, savings_pct, exec_time_ms, turn, created_at
190
+ ) VALUES (
191
+ @sessionId, @tool, @contentType, @distiller,
192
+ @inputTokens, @outputTokens, @savingsPct, @execTimeMs, @turn, @createdAt
193
+ )`).run({
194
+ sessionId: row.sessionId,
195
+ tool: row.tool,
196
+ contentType: row.contentType,
197
+ distiller: row.distiller,
198
+ inputTokens: row.inputTokens,
199
+ outputTokens: row.outputTokens,
200
+ savingsPct: row.savingsPct,
201
+ execTimeMs: row.execTimeMs,
202
+ turn: row.turn,
203
+ createdAt: Date.now(),
204
+ });
205
+ }
206
+ export function evictStaleArtifacts(db, currentTurn, ttlTurns) {
207
+ const cutoff = currentTurn - ttlTurns;
208
+ const extendedCutoff = currentTurn - ttlTurns * 2;
209
+ const result = db
210
+ .prepare(`DELETE FROM context_artifacts
211
+ WHERE (access_count < 4 AND last_accessed_turn < @cutoff)
212
+ OR (access_count >= 4 AND last_accessed_turn < @extendedCutoff)`)
213
+ .run({ cutoff, extendedCutoff });
214
+ return result.changes;
215
+ }
216
+ function rowToTurnRecord(row) {
217
+ return {
218
+ turn: row.turn,
219
+ userMessage: row.user_message,
220
+ assistantMessage: row.assistant_message,
221
+ toolCalls: JSON.parse(row.tool_calls_json),
222
+ artifactIds: JSON.parse(row.artifact_ids_json),
223
+ filesRead: JSON.parse(row.files_read_json),
224
+ filesWritten: JSON.parse(row.files_written_json),
225
+ errors: JSON.parse(row.errors_json),
226
+ tokenCount: row.token_count,
227
+ timestamp: row.timestamp,
228
+ };
229
+ }
230
+ export function getMaxLedgerTurn(db, sessionId) {
231
+ const row = db
232
+ .prepare("SELECT MAX(turn) AS max_turn FROM turn_ledger WHERE session_id = ?")
233
+ .get(sessionId);
234
+ return row?.max_turn ?? null;
235
+ }
236
+ export function hasLedgerTurn(db, sessionId, turn) {
237
+ const row = db
238
+ .prepare("SELECT 1 FROM turn_ledger WHERE session_id = ? AND turn = ? LIMIT 1")
239
+ .get(sessionId, turn);
240
+ return !!row;
241
+ }
242
+ export function insertTurnRecord(db, sessionId, record, searchText) {
243
+ db.prepare(`INSERT OR IGNORE INTO turn_ledger (
244
+ session_id, turn, user_message, assistant_message,
245
+ tool_calls_json, artifact_ids_json, files_read_json, files_written_json,
246
+ errors_json, token_count, search_text, timestamp
247
+ ) VALUES (
248
+ @sessionId, @turn, @userMessage, @assistantMessage,
249
+ @toolCallsJson, @artifactIdsJson, @filesReadJson, @filesWrittenJson,
250
+ @errorsJson, @tokenCount, @searchText, @timestamp
251
+ )`).run({
252
+ sessionId,
253
+ turn: record.turn,
254
+ userMessage: record.userMessage,
255
+ assistantMessage: record.assistantMessage,
256
+ toolCallsJson: JSON.stringify(record.toolCalls),
257
+ artifactIdsJson: JSON.stringify(record.artifactIds),
258
+ filesReadJson: JSON.stringify(record.filesRead),
259
+ filesWrittenJson: JSON.stringify(record.filesWritten),
260
+ errorsJson: JSON.stringify(record.errors),
261
+ tokenCount: record.tokenCount,
262
+ searchText,
263
+ timestamp: record.timestamp,
264
+ });
265
+ }
266
+ export function listTurnRecords(db, sessionId) {
267
+ const rows = db
268
+ .prepare(`SELECT * FROM turn_ledger
269
+ WHERE session_id = ?
270
+ ORDER BY turn ASC`)
271
+ .all(sessionId);
272
+ return rows.map(rowToTurnRecord);
273
+ }
274
+ export function getTurnRecord(db, sessionId, turn) {
275
+ const row = db
276
+ .prepare("SELECT * FROM turn_ledger WHERE session_id = ? AND turn = ?")
277
+ .get(sessionId, turn);
278
+ return row ? rowToTurnRecord(row) : null;
279
+ }
280
+ export function listArtifactIdsForTurn(db, sessionId, turn) {
281
+ const rows = db
282
+ .prepare(`SELECT id FROM context_artifacts
283
+ WHERE session_id = ? AND created_turn = ?`)
284
+ .all(sessionId, turn);
285
+ return rows.map((r) => r.id);
286
+ }
287
+ export function insertTurnDigest(db, sessionId, digest) {
288
+ db.prepare(`INSERT OR REPLACE INTO turn_digests (session_id, turn, digest_json, created_at)
289
+ VALUES (@sessionId, @turn, @digestJson, @createdAt)`).run({
290
+ sessionId,
291
+ turn: digest.turnId,
292
+ digestJson: JSON.stringify(digest),
293
+ createdAt: Date.now(),
294
+ });
295
+ }
296
+ export function insertActivityEntries(db, sessionId, entries) {
297
+ if (entries.length === 0)
298
+ return;
299
+ const stmt = db.prepare(`INSERT INTO activity_log (session_id, turn, type, summary, artifact_ref, created_at)
300
+ VALUES (@sessionId, @turn, @type, @summary, @artifactRef, @createdAt)`);
301
+ const createdAt = Date.now();
302
+ for (const entry of entries) {
303
+ stmt.run({
304
+ sessionId,
305
+ turn: entry.turn,
306
+ type: entry.type,
307
+ summary: entry.summary,
308
+ artifactRef: entry.artifactRef ?? null,
309
+ createdAt,
310
+ });
311
+ }
312
+ }
313
+ export function listActivityEntries(db, sessionId, limit) {
314
+ const rows = db
315
+ .prepare(`SELECT turn, type, summary, artifact_ref
316
+ FROM activity_log
317
+ WHERE session_id = ?
318
+ ORDER BY id DESC
319
+ LIMIT ?`)
320
+ .all(sessionId, limit);
321
+ return rows.reverse().map((row) => ({
322
+ turn: row.turn,
323
+ type: row.type,
324
+ summary: row.summary,
325
+ artifactRef: row.artifact_ref ?? undefined,
326
+ }));
327
+ }
328
+ export function getExtractionState(db, sessionId) {
329
+ const row = db
330
+ .prepare("SELECT state_json FROM extraction_state WHERE session_id = ?")
331
+ .get(sessionId);
332
+ if (!row)
333
+ return null;
334
+ return JSON.parse(row.state_json);
335
+ }
336
+ export function upsertExtractionState(db, sessionId, state) {
337
+ db.prepare(`INSERT INTO extraction_state (session_id, state_json, updated_at)
338
+ VALUES (@sessionId, @stateJson, @updatedAt)
339
+ ON CONFLICT(session_id) DO UPDATE SET
340
+ state_json = excluded.state_json,
341
+ updated_at = excluded.updated_at`).run({
342
+ sessionId,
343
+ stateJson: JSON.stringify(state),
344
+ updatedAt: Date.now(),
345
+ });
346
+ }
347
+ export function getSessionCheckpoint(db, sessionId) {
348
+ const row = db
349
+ .prepare("SELECT checkpoint_json FROM session_checkpoints WHERE session_id = ?")
350
+ .get(sessionId);
351
+ if (!row)
352
+ return null;
353
+ return JSON.parse(row.checkpoint_json);
354
+ }
355
+ export function upsertSessionCheckpoint(db, sessionId, checkpoint) {
356
+ db.prepare(`INSERT INTO session_checkpoints (session_id, checkpoint_json, updated_at)
357
+ VALUES (@sessionId, @checkpointJson, @updatedAt)
358
+ ON CONFLICT(session_id) DO UPDATE SET
359
+ checkpoint_json = excluded.checkpoint_json,
360
+ updated_at = excluded.updated_at`).run({
361
+ sessionId,
362
+ checkpointJson: JSON.stringify(checkpoint),
363
+ updatedAt: Date.now(),
364
+ });
365
+ }
366
+ export function getTurnDigest(db, sessionId, turn) {
367
+ const row = db
368
+ .prepare("SELECT digest_json FROM turn_digests WHERE session_id = ? AND turn = ?")
369
+ .get(sessionId, turn);
370
+ if (!row)
371
+ return null;
372
+ return normalizeStoredTurnDigest(JSON.parse(row.digest_json));
373
+ }
374
+ export function listTurnDigests(db, sessionId) {
375
+ const rows = db
376
+ .prepare(`SELECT digest_json FROM turn_digests
377
+ WHERE session_id = ?
378
+ ORDER BY turn ASC`)
379
+ .all(sessionId);
380
+ return rows.map((row) => normalizeStoredTurnDigest(JSON.parse(row.digest_json)));
381
+ }
382
+ function normalizeStoredTurnDigest(raw) {
383
+ const decisions = (raw.decisions ?? []).map((d) => typeof d === "string" ? { summary: d } : d);
384
+ return {
385
+ ...raw,
386
+ filesWritten: raw.filesWritten ?? [],
387
+ decisions,
388
+ };
389
+ }
390
+ export function listSessionArtifacts(db, sessionId) {
391
+ const rows = db
392
+ .prepare(`SELECT * FROM context_artifacts
393
+ WHERE session_id = ?
394
+ ORDER BY created_turn ASC, id ASC`)
395
+ .all(sessionId);
396
+ return rows.map(rowToArtifact);
397
+ }
398
+ export function countSessionArtifacts(db, sessionId) {
399
+ const row = db
400
+ .prepare("SELECT COUNT(*) AS count FROM context_artifacts WHERE session_id = ?")
401
+ .get(sessionId);
402
+ return row.count;
403
+ }
404
+ export function listAllActivityEntries(db, sessionId) {
405
+ const rows = db
406
+ .prepare(`SELECT turn, type, summary, artifact_ref
407
+ FROM activity_log
408
+ WHERE session_id = ?
409
+ ORDER BY id ASC`)
410
+ .all(sessionId);
411
+ return rows.map((row) => ({
412
+ turn: row.turn,
413
+ type: row.type,
414
+ summary: row.summary,
415
+ artifactRef: row.artifact_ref ?? undefined,
416
+ }));
417
+ }
418
+ export function insertArtifactAccess(db, sessionId, artifactId, accessType, turn) {
419
+ db.prepare(`INSERT INTO artifact_access (artifact_id, session_id, access_type, turn, created_at)
420
+ VALUES (@artifactId, @sessionId, @accessType, @turn, @createdAt)`).run({
421
+ artifactId,
422
+ sessionId,
423
+ accessType,
424
+ turn,
425
+ createdAt: Date.now(),
426
+ });
427
+ }
428
+ export function getSessionStatsRow(db, sessionId) {
429
+ const row = db
430
+ .prepare(`SELECT session_id, context_engine_enabled, pressure_events, compaction_triggers, artifact_retrievals,
431
+ total_distiller_savings, total_turns
432
+ FROM session_stats WHERE session_id = ?`)
433
+ .get(sessionId);
434
+ if (!row)
435
+ return null;
436
+ return {
437
+ sessionId: row.session_id,
438
+ contextEngineEnabled: row.context_engine_enabled === 1,
439
+ pressureEvents: row.pressure_events,
440
+ compactionTriggers: row.compaction_triggers,
441
+ artifactRetrievals: row.artifact_retrievals,
442
+ totalDistillerSavings: row.total_distiller_savings,
443
+ totalTurns: row.total_turns,
444
+ };
445
+ }
446
+ function ensureSessionStatsRow(db, sessionId, contextEngineEnabled = false) {
447
+ db.prepare(`INSERT OR IGNORE INTO session_stats (
448
+ session_id, context_engine_enabled, pressure_events, compaction_triggers, artifact_retrievals,
449
+ total_distiller_savings, total_turns, updated_at
450
+ ) VALUES (@sessionId, @contextEngineEnabled, 0, 0, 0, 0, 0, @updatedAt)`).run({ sessionId, contextEngineEnabled: contextEngineEnabled ? 1 : 0, updatedAt: Date.now() });
451
+ }
452
+ export function incrementSessionStat(db, sessionId, field, amount = 1, contextEngineEnabled = false) {
453
+ ensureSessionStatsRow(db, sessionId, contextEngineEnabled);
454
+ db.prepare(`UPDATE session_stats
455
+ SET ${field} = ${field} + @amount, updated_at = @updatedAt
456
+ WHERE session_id = @sessionId`).run({ sessionId, amount, updatedAt: Date.now() });
457
+ }
458
+ export function finalizeSessionStats(db, sessionId, totalTurns, contextEngineEnabled = false) {
459
+ ensureSessionStatsRow(db, sessionId, contextEngineEnabled);
460
+ const savingsRow = db
461
+ .prepare(`SELECT COALESCE(SUM(input_tokens - output_tokens), 0) AS savings
462
+ FROM distiller_stats WHERE session_id = ?`)
463
+ .get(sessionId);
464
+ db.prepare(`UPDATE session_stats
465
+ SET total_turns = @totalTurns,
466
+ total_distiller_savings = @savings,
467
+ context_engine_enabled = @contextEngineEnabled,
468
+ updated_at = @updatedAt
469
+ WHERE session_id = @sessionId`).run({
470
+ sessionId,
471
+ totalTurns,
472
+ savings: savingsRow.savings,
473
+ contextEngineEnabled: contextEngineEnabled ? 1 : 0,
474
+ updatedAt: Date.now(),
475
+ });
476
+ return getSessionStatsRow(db, sessionId);
477
+ }
478
+ export function listDistillerCostRanking(db, sessionId) {
479
+ const rows = db
480
+ .prepare(`SELECT distiller, tool,
481
+ COUNT(*) AS runs,
482
+ AVG(input_tokens) AS avg_input,
483
+ AVG(savings_pct) AS avg_savings_pct,
484
+ SUM(input_tokens * (1.0 - (savings_pct / 100.0))) AS estimated_cost
485
+ FROM distiller_stats
486
+ WHERE session_id = ?
487
+ GROUP BY distiller, tool
488
+ ORDER BY estimated_cost DESC`)
489
+ .all(sessionId);
490
+ return rows.map((row) => ({
491
+ distiller: row.distiller,
492
+ tool: row.tool,
493
+ runs: row.runs,
494
+ avgInputTokens: Math.round(row.avg_input),
495
+ avgSavingsPct: Number(row.avg_savings_pct.toFixed(1)),
496
+ estimatedCost: Math.round(row.estimated_cost),
497
+ }));
498
+ }
499
+ export function countArtifactAccessByType(db, sessionId, accessType) {
500
+ const row = db
501
+ .prepare(`SELECT COUNT(*) AS count FROM artifact_access
502
+ WHERE session_id = ? AND access_type = ?`)
503
+ .get(sessionId, accessType);
504
+ return row.count;
505
+ }
@@ -0,0 +1,30 @@
1
+ import type { ContentType } from "./types.js";
2
+ export type DistillerIntensity = "lite" | "full";
3
+ export type DistillerMode = "sync" | "deferred";
4
+ export interface Distiller {
5
+ readonly name: string;
6
+ readonly contentTypes: ContentType[];
7
+ readonly mode: DistillerMode;
8
+ distill(input: string, intensity: DistillerIntensity, contentType?: ContentType): string;
9
+ }
10
+ export interface DistillResult {
11
+ summary: string;
12
+ distillerName: string;
13
+ execTimeMs: number;
14
+ deferred: boolean;
15
+ }
16
+ export interface DistillDeferredResult {
17
+ pendingSummary: string;
18
+ distillerName: string;
19
+ backfill: () => Promise<DistillResult>;
20
+ }
21
+ export declare function buildPendingSummary(): string;
22
+ export declare function isPendingSummary(summary: string): boolean;
23
+ export declare class DistillerRegistry {
24
+ private readonly distillers;
25
+ register(distiller: Distiller): void;
26
+ find(contentType: ContentType): Distiller | null;
27
+ selectIntensity(rawTokens: number, defaultIntensity: DistillerIntensity): DistillerIntensity;
28
+ distillSync(input: string, contentType: ContentType, intensity: DistillerIntensity): DistillResult;
29
+ distillForIngestion(input: string, contentType: ContentType, intensity: DistillerIntensity): DistillResult | DistillDeferredResult;
30
+ }
@@ -0,0 +1,67 @@
1
+ import { summarizeGeneric } from "./summarize.js";
2
+ const PENDING_MARKER = "[compression pending — full content available via retrieve_artifact]";
3
+ export function buildPendingSummary() {
4
+ return PENDING_MARKER;
5
+ }
6
+ export function isPendingSummary(summary) {
7
+ return summary.includes(PENDING_MARKER);
8
+ }
9
+ export class DistillerRegistry {
10
+ distillers = [];
11
+ register(distiller) {
12
+ this.distillers.push(distiller);
13
+ }
14
+ find(contentType) {
15
+ return this.distillers.find((d) => d.contentTypes.includes(contentType)) ?? null;
16
+ }
17
+ selectIntensity(rawTokens, defaultIntensity) {
18
+ if (rawTokens > 2000)
19
+ return "full";
20
+ if (rawTokens > 400)
21
+ return "lite";
22
+ return defaultIntensity;
23
+ }
24
+ distillSync(input, contentType, intensity) {
25
+ const distiller = this.find(contentType);
26
+ const start = performance.now();
27
+ if (!distiller || distiller.mode === "deferred") {
28
+ const summary = summarizeGeneric(input, contentType);
29
+ return {
30
+ summary,
31
+ distillerName: distiller?.name ?? "generic-fallback",
32
+ execTimeMs: performance.now() - start,
33
+ deferred: false,
34
+ };
35
+ }
36
+ const summary = distiller.distill(input, intensity, contentType);
37
+ return {
38
+ summary,
39
+ distillerName: distiller.name,
40
+ execTimeMs: performance.now() - start,
41
+ deferred: false,
42
+ };
43
+ }
44
+ distillForIngestion(input, contentType, intensity) {
45
+ const distiller = this.find(contentType);
46
+ if (!distiller) {
47
+ return this.distillSync(input, contentType, intensity);
48
+ }
49
+ if (distiller.mode === "deferred") {
50
+ return {
51
+ pendingSummary: buildPendingSummary(),
52
+ distillerName: distiller.name,
53
+ backfill: async () => {
54
+ const start = performance.now();
55
+ const summary = distiller.distill(input, intensity, contentType);
56
+ return {
57
+ summary,
58
+ distillerName: distiller.name,
59
+ execTimeMs: performance.now() - start,
60
+ deferred: true,
61
+ };
62
+ },
63
+ };
64
+ }
65
+ return this.distillSync(input, contentType, intensity);
66
+ }
67
+ }
@@ -0,0 +1,23 @@
1
+ import type { CompileInput, CompileMetrics } from "../compiler.js";
2
+ import { recencyScore, scoreContextUnit } from "./scoring.js";
3
+ import type { ActivityEntry, CompileScoreRecord, PressureMode, TurnRecord } from "./types.js";
4
+ import type { ContextEngineConfig } from "../types.js";
5
+ export interface EngineCompileInput extends CompileInput {
6
+ currentTurn: number;
7
+ turnRecords: TurnRecord[];
8
+ activityEntries?: ActivityEntry[];
9
+ engineConfig: ContextEngineConfig;
10
+ /** Model input context window; pressure is measured against this when set. */
11
+ contextWindowTokens?: number;
12
+ }
13
+ export interface EngineCompileResult {
14
+ prompt: string;
15
+ metrics: CompileMetrics;
16
+ scoreRecords: CompileScoreRecord[];
17
+ pressureRatio: number;
18
+ pressureMode: PressureMode;
19
+ excludedScoredUnits: number;
20
+ }
21
+ export declare function compileEngineWithMetrics(input: EngineCompileInput): EngineCompileResult;
22
+ export declare function explainUnitScore(unitId: string, records: CompileScoreRecord[], currentTurn: number, userInput: string, weights: ContextEngineConfig["scoring"], bandTokenBudget: number, bandUsedTokens: number): string[];
23
+ export { recencyScore, scoreContextUnit };