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
package/dist/config.js ADDED
@@ -0,0 +1,433 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import * as toml from "toml";
4
+ import { getAppLogger } from "./logger.js";
5
+ import { APP_HOME_DIR, LEGACY_APP_HOME_DIR, envFlag, envOverride, resolveDefaultMemoryDbPath, resolveDefaultSessionLogDir, } from "./app-identity.js";
6
+ function configWarn(message, cause) {
7
+ getAppLogger().child("config").warn(message, {
8
+ code: "CONFIG_INVALID",
9
+ ...(cause ? { cause } : {}),
10
+ });
11
+ }
12
+ /** Tracks which config files were loaded in the last loadConfig() call. */
13
+ let _loadedSources = [];
14
+ export function getLoadedConfigSources() {
15
+ return _loadedSources;
16
+ }
17
+ const DEFAULT_CONFIG = {
18
+ llm: {
19
+ provider: "openrouter",
20
+ model: "deepseek/deepseek-v4-flash:free",
21
+ },
22
+ memory: {
23
+ enabled: true,
24
+ summarizer: "openrouter",
25
+ db_path: `~/${APP_HOME_DIR}/memory.db`,
26
+ embedder: "auto",
27
+ ollama_url: "http://localhost:11434",
28
+ ollama_model: "nomic-embed-text",
29
+ },
30
+ compiler: {
31
+ token_budget: 100_000,
32
+ recent_turns: 10,
33
+ recent_turns_token_budget: 30_000,
34
+ recall_min_score: 0.35,
35
+ memories_budget_ratio: 0.2,
36
+ agents_budget_ratio: 0.3,
37
+ reserved_output_tokens: 0,
38
+ auto_compact_at: 0.75,
39
+ auto_compact_clear_at: 0.55,
40
+ compact_chunk_fraction: 0.25,
41
+ verbatim_only: false,
42
+ compression_watermark: 0.75,
43
+ compression_flush_fraction: 0.30,
44
+ },
45
+ tiers: {
46
+ idle_soft_after_turns: 20,
47
+ idle_hard_after_turns: 50,
48
+ },
49
+ session: {
50
+ log_dir: `~/${APP_HOME_DIR}/sessions`,
51
+ },
52
+ consolidation: {
53
+ enabled: true,
54
+ promotion_threshold: 3,
55
+ run_delay_seconds: 30,
56
+ },
57
+ shell: {
58
+ enabled: false,
59
+ allowed_paths: [],
60
+ },
61
+ edit: {
62
+ confirm: false,
63
+ },
64
+ skills: {
65
+ enabled: true,
66
+ max_token_budget_ratio: 0.2,
67
+ active_skill_idle_turns: 5,
68
+ warm_skill_eviction_turns: 20,
69
+ max_depth: 6,
70
+ },
71
+ ui: {
72
+ mode: "tui",
73
+ screen: "preserve",
74
+ markdown_rendering: true,
75
+ syntax_highlighting: true,
76
+ syntax_theme: "solarized-dark",
77
+ },
78
+ context_engine: {
79
+ enabled: true,
80
+ measurement_mode: false,
81
+ artifact_inline_threshold: 400,
82
+ artifact_ttl_turns: 50,
83
+ distiller: {
84
+ default_intensity: "full",
85
+ },
86
+ llm_digest: false,
87
+ activity_log_max_entries: 15,
88
+ checkpoint_enabled: true,
89
+ scoring: {
90
+ w_pin: 1.0,
91
+ w_recency: 0.5,
92
+ w_relevance: 0.3,
93
+ },
94
+ pressure: {
95
+ compact_at: 0.7,
96
+ emergency_at: 0.85,
97
+ },
98
+ },
99
+ project_detection: {
100
+ enabled: true,
101
+ },
102
+ };
103
+ function expandHome(p) {
104
+ return p.startsWith("~/") ? p.replace(/^~\//, `${homedir()}/`) : p;
105
+ }
106
+ function deepMerge(base, override) {
107
+ const out = { ...base };
108
+ for (const [k, v] of Object.entries(override)) {
109
+ const bv = base[k];
110
+ if (v &&
111
+ typeof v === "object" &&
112
+ !Array.isArray(v) &&
113
+ bv &&
114
+ typeof bv === "object" &&
115
+ !Array.isArray(bv)) {
116
+ out[k] = deepMerge(bv, v);
117
+ }
118
+ else {
119
+ out[k] = v;
120
+ }
121
+ }
122
+ return out;
123
+ }
124
+ function loadJsonConfig(path) {
125
+ if (existsSync(path)) {
126
+ try {
127
+ return JSON.parse(readFileSync(path, "utf-8"));
128
+ }
129
+ catch (err) {
130
+ configWarn(`Failed to parse JSON config ${path}`, err);
131
+ }
132
+ }
133
+ return {};
134
+ }
135
+ function loadTomlConfig(path) {
136
+ if (existsSync(path)) {
137
+ try {
138
+ return toml.parse(readFileSync(path, "utf-8"));
139
+ }
140
+ catch (err) {
141
+ configWarn(`Failed to parse TOML config ${path}`, err);
142
+ }
143
+ }
144
+ return {};
145
+ }
146
+ export function loadConfig(configPath) {
147
+ let userConfig = {};
148
+ _loadedSources = []; // reset on each call
149
+ if (configPath) {
150
+ // If explicit path provided, use it (try both .json and .toml)
151
+ if (configPath.endsWith('.json')) {
152
+ userConfig = loadJsonConfig(configPath);
153
+ if (Object.keys(userConfig).length > 0)
154
+ _loadedSources.push(configPath);
155
+ }
156
+ else if (configPath.endsWith('.toml')) {
157
+ userConfig = loadTomlConfig(configPath);
158
+ if (Object.keys(userConfig).length > 0)
159
+ _loadedSources.push(configPath);
160
+ }
161
+ else {
162
+ // Try both extensions
163
+ const jsonPath = configPath + '.json';
164
+ userConfig = loadJsonConfig(jsonPath);
165
+ if (Object.keys(userConfig).length > 0) {
166
+ _loadedSources.push(jsonPath);
167
+ }
168
+ else {
169
+ const tomlPath = configPath + '.toml';
170
+ userConfig = loadTomlConfig(tomlPath);
171
+ if (Object.keys(userConfig).length > 0)
172
+ _loadedSources.push(tomlPath);
173
+ }
174
+ }
175
+ }
176
+ else {
177
+ // Load and merge configs from all sources in order
178
+ // Order: global JSON -> global TOML -> local JSON -> local TOML
179
+ // Later sources override earlier ones
180
+ // Order: legacy global → global → legacy local → local (later overrides earlier)
181
+ const configs = [
182
+ { path: expandHome(`~/${LEGACY_APP_HOME_DIR}/aria.config.json`), loader: loadJsonConfig },
183
+ { path: expandHome(`~/${LEGACY_APP_HOME_DIR}/config.toml`), loader: loadTomlConfig },
184
+ { path: expandHome(`~/${APP_HOME_DIR}/praana.config.json`), loader: loadJsonConfig },
185
+ { path: expandHome(`~/${APP_HOME_DIR}/config.toml`), loader: loadTomlConfig },
186
+ { path: "aria.config.json", loader: loadJsonConfig },
187
+ { path: "aria.config.toml", loader: loadTomlConfig },
188
+ { path: "praana.config.json", loader: loadJsonConfig },
189
+ { path: "praana.config.toml", loader: loadTomlConfig },
190
+ ];
191
+ // Merge all configs in order (later overrides earlier)
192
+ for (const { path, loader } of configs) {
193
+ const config = loader(path);
194
+ if (Object.keys(config).length > 0) {
195
+ userConfig = deepMerge(userConfig, config);
196
+ _loadedSources.push(path);
197
+ }
198
+ }
199
+ }
200
+ const merged = deepMerge(DEFAULT_CONFIG, userConfig);
201
+ // Backward compat: skills_budget_ratio → agents_budget_ratio
202
+ const compiler = userConfig.compiler;
203
+ if (compiler?.skills_budget_ratio !== undefined && compiler.agents_budget_ratio === undefined) {
204
+ merged.compiler.agents_budget_ratio = compiler.skills_budget_ratio;
205
+ }
206
+ // Backward compat: map old [bodha] config to [memory]
207
+ if (userConfig.bodha && !userConfig.memory) {
208
+ merged.memory = { ...merged.memory, ...userConfig.bodha };
209
+ }
210
+ const modelOverride = envOverride("PRAANA_MODEL", "ARIA_MODEL");
211
+ if (modelOverride)
212
+ merged.llm.model = modelOverride;
213
+ const contextEngineFlag = envFlag("PRAANA_CONTEXT_ENGINE", "ARIA_CONTEXT_ENGINE");
214
+ if (contextEngineFlag !== undefined) {
215
+ merged.context_engine.enabled = contextEngineFlag;
216
+ }
217
+ const measurementFlag = envFlag("PRAANA_MEASUREMENT_MODE", "ARIA_MEASUREMENT_MODE");
218
+ if (measurementFlag !== undefined) {
219
+ merged.context_engine.measurement_mode = measurementFlag;
220
+ }
221
+ // Expand paths — fall back to legacy home when the new default paths are unused
222
+ merged.session.log_dir = expandHome(merged.session.log_dir);
223
+ if (merged.memory?.db_path) {
224
+ merged.memory.db_path = expandHome(merged.memory.db_path);
225
+ }
226
+ if (merged.session.log_dir.endsWith(`/${APP_HOME_DIR}/sessions`)) {
227
+ merged.session.log_dir = resolveDefaultSessionLogDir();
228
+ }
229
+ if (merged.memory?.db_path?.endsWith(`/${APP_HOME_DIR}/memory.db`)) {
230
+ merged.memory.db_path = resolveDefaultMemoryDbPath();
231
+ }
232
+ return validateConfig(merged);
233
+ }
234
+ function validateConfig(config) {
235
+ const out = deepMerge(config, {});
236
+ if (!out.llm.model || !out.llm.model.trim()) {
237
+ configWarn("Invalid llm.model, using default deepseek/deepseek-v4-flash:free");
238
+ out.llm.model = DEFAULT_CONFIG.llm.model;
239
+ }
240
+ const validEmbedders = new Set(["auto", "ollama", "transformers", "llama-cpp", "hash"]);
241
+ if (out.memory.embedder && !validEmbedders.has(out.memory.embedder)) {
242
+ configWarn("Invalid memory.embedder, using default 'auto'");
243
+ out.memory.embedder = DEFAULT_CONFIG.memory.embedder;
244
+ }
245
+ if (!out.memory.embedder) {
246
+ out.memory.embedder = DEFAULT_CONFIG.memory.embedder;
247
+ }
248
+ const validSummarizers = new Set(["disabled", "ollama", "openrouter", "openai"]);
249
+ const summarizer = out.memory.summarizer?.toLowerCase();
250
+ if (summarizer && !validSummarizers.has(summarizer)) {
251
+ configWarn(`Invalid memory.summarizer '${out.memory.summarizer}', using 'disabled'`);
252
+ out.memory.summarizer = "disabled";
253
+ }
254
+ if (!Number.isFinite(out.compiler.token_budget) || out.compiler.token_budget <= 1000) {
255
+ configWarn("Invalid compiler.token_budget, using default 100000");
256
+ out.compiler.token_budget = DEFAULT_CONFIG.compiler.token_budget;
257
+ }
258
+ if (!Number.isFinite(out.compiler.recent_turns) ||
259
+ out.compiler.recent_turns < 1 ||
260
+ out.compiler.recent_turns > 100) {
261
+ configWarn("Invalid compiler.recent_turns, using default 10");
262
+ out.compiler.recent_turns = DEFAULT_CONFIG.compiler.recent_turns;
263
+ }
264
+ if (out.compiler.recent_turns_token_budget !== undefined &&
265
+ (!Number.isFinite(out.compiler.recent_turns_token_budget) ||
266
+ out.compiler.recent_turns_token_budget < 0)) {
267
+ configWarn("Invalid compiler.recent_turns_token_budget, using default");
268
+ out.compiler.recent_turns_token_budget =
269
+ DEFAULT_CONFIG.compiler.recent_turns_token_budget;
270
+ }
271
+ // Auto-compaction config validation
272
+ const compactAt = out.compiler.auto_compact_at ?? out.compiler.compression_watermark;
273
+ if (compactAt !== undefined &&
274
+ (!Number.isFinite(compactAt) || compactAt < 0.5 || compactAt > 1.0)) {
275
+ configWarn("Invalid compiler.auto_compact_at (must be 0.5–1.0), using default 0.75");
276
+ out.compiler.auto_compact_at = DEFAULT_CONFIG.compiler.auto_compact_at;
277
+ }
278
+ else if (out.compiler.auto_compact_at === undefined && compactAt !== undefined) {
279
+ out.compiler.auto_compact_at = compactAt;
280
+ }
281
+ else if (out.compiler.auto_compact_at === undefined) {
282
+ out.compiler.auto_compact_at = DEFAULT_CONFIG.compiler.auto_compact_at;
283
+ }
284
+ if (out.compiler.auto_compact_clear_at !== undefined &&
285
+ (!Number.isFinite(out.compiler.auto_compact_clear_at) ||
286
+ out.compiler.auto_compact_clear_at < 0.1 ||
287
+ out.compiler.auto_compact_clear_at >= (out.compiler.auto_compact_at ?? 0.75))) {
288
+ configWarn("Invalid compiler.auto_compact_clear_at, using default 0.55");
289
+ out.compiler.auto_compact_clear_at = DEFAULT_CONFIG.compiler.auto_compact_clear_at;
290
+ }
291
+ else if (out.compiler.auto_compact_clear_at === undefined) {
292
+ out.compiler.auto_compact_clear_at = DEFAULT_CONFIG.compiler.auto_compact_clear_at;
293
+ }
294
+ const chunkFraction = out.compiler.compact_chunk_fraction ?? out.compiler.compression_flush_fraction;
295
+ if (chunkFraction !== undefined &&
296
+ (!Number.isFinite(chunkFraction) || chunkFraction < 0.05 || chunkFraction > 0.5)) {
297
+ configWarn("Invalid compiler.compact_chunk_fraction (must be 0.05–0.5), using default 0.25");
298
+ out.compiler.compact_chunk_fraction = DEFAULT_CONFIG.compiler.compact_chunk_fraction;
299
+ }
300
+ else if (out.compiler.compact_chunk_fraction === undefined && chunkFraction !== undefined) {
301
+ out.compiler.compact_chunk_fraction = chunkFraction;
302
+ }
303
+ else if (out.compiler.compact_chunk_fraction === undefined) {
304
+ out.compiler.compact_chunk_fraction = DEFAULT_CONFIG.compiler.compact_chunk_fraction;
305
+ }
306
+ if (typeof out.compiler.verbatim_only !== "boolean") {
307
+ out.compiler.verbatim_only = DEFAULT_CONFIG.compiler.verbatim_only;
308
+ }
309
+ if (out.llm.context_window !== undefined &&
310
+ (!Number.isFinite(out.llm.context_window) || out.llm.context_window <= 1000)) {
311
+ configWarn("Invalid llm.context_window, ignoring override");
312
+ delete out.llm.context_window;
313
+ }
314
+ if (!out.context_engine) {
315
+ out.context_engine = { ...DEFAULT_CONFIG.context_engine };
316
+ }
317
+ if (typeof out.context_engine.enabled !== "boolean") {
318
+ out.context_engine.enabled = DEFAULT_CONFIG.context_engine.enabled;
319
+ }
320
+ if (typeof out.context_engine.measurement_mode !== "boolean") {
321
+ out.context_engine.measurement_mode = DEFAULT_CONFIG.context_engine.measurement_mode;
322
+ }
323
+ if (!Number.isFinite(out.context_engine.artifact_inline_threshold) ||
324
+ out.context_engine.artifact_inline_threshold < 0) {
325
+ out.context_engine.artifact_inline_threshold =
326
+ DEFAULT_CONFIG.context_engine.artifact_inline_threshold;
327
+ }
328
+ if (!Number.isFinite(out.context_engine.artifact_ttl_turns) ||
329
+ out.context_engine.artifact_ttl_turns < 1) {
330
+ out.context_engine.artifact_ttl_turns = DEFAULT_CONFIG.context_engine.artifact_ttl_turns;
331
+ }
332
+ if (!out.context_engine.distiller) {
333
+ out.context_engine.distiller = { ...DEFAULT_CONFIG.context_engine.distiller };
334
+ }
335
+ const intensity = out.context_engine.distiller.default_intensity;
336
+ if (intensity !== "lite" && intensity !== "full") {
337
+ out.context_engine.distiller.default_intensity =
338
+ DEFAULT_CONFIG.context_engine.distiller.default_intensity;
339
+ }
340
+ if (typeof out.context_engine.llm_digest !== "boolean") {
341
+ out.context_engine.llm_digest = DEFAULT_CONFIG.context_engine.llm_digest;
342
+ }
343
+ if (!Number.isFinite(out.context_engine.activity_log_max_entries) ||
344
+ out.context_engine.activity_log_max_entries < 1) {
345
+ out.context_engine.activity_log_max_entries =
346
+ DEFAULT_CONFIG.context_engine.activity_log_max_entries;
347
+ }
348
+ if (typeof out.context_engine.checkpoint_enabled !== "boolean") {
349
+ out.context_engine.checkpoint_enabled =
350
+ DEFAULT_CONFIG.context_engine.checkpoint_enabled;
351
+ }
352
+ if (!out.context_engine.scoring) {
353
+ out.context_engine.scoring = { ...DEFAULT_CONFIG.context_engine.scoring };
354
+ }
355
+ else {
356
+ for (const key of ["w_pin", "w_recency", "w_relevance"]) {
357
+ if (!Number.isFinite(out.context_engine.scoring[key])) {
358
+ out.context_engine.scoring[key] = DEFAULT_CONFIG.context_engine.scoring[key];
359
+ }
360
+ }
361
+ }
362
+ if (!out.context_engine.pressure) {
363
+ out.context_engine.pressure = { ...DEFAULT_CONFIG.context_engine.pressure };
364
+ }
365
+ else {
366
+ if (!Number.isFinite(out.context_engine.pressure.compact_at)) {
367
+ out.context_engine.pressure.compact_at =
368
+ DEFAULT_CONFIG.context_engine.pressure.compact_at;
369
+ }
370
+ if (!Number.isFinite(out.context_engine.pressure.emergency_at)) {
371
+ out.context_engine.pressure.emergency_at =
372
+ DEFAULT_CONFIG.context_engine.pressure.emergency_at;
373
+ }
374
+ }
375
+ // Shell sandbox config validation
376
+ if (out.shell) {
377
+ if (typeof out.shell.enabled !== 'boolean') {
378
+ configWarn("shell.enabled must be boolean, defaulting to false");
379
+ out.shell.enabled = false;
380
+ }
381
+ if (!Array.isArray(out.shell.allowed_paths)) {
382
+ configWarn("shell.allowed_paths must be string array, defaulting to []");
383
+ out.shell.allowed_paths = [];
384
+ }
385
+ }
386
+ // search_code config validation
387
+ if (out.search_code) {
388
+ if (typeof out.search_code.rg_path !== "string" &&
389
+ out.search_code.rg_path !== undefined) {
390
+ configWarn("search_code.rg_path must be a string, ignoring");
391
+ out.search_code.rg_path = undefined;
392
+ }
393
+ else if (typeof out.search_code.rg_path === "string") {
394
+ out.search_code.rg_path = expandHome(out.search_code.rg_path);
395
+ }
396
+ }
397
+ // UI config validation
398
+ if (out.ui) {
399
+ if (typeof out.ui.markdown_rendering !== 'boolean') {
400
+ out.ui.markdown_rendering = DEFAULT_CONFIG.ui.markdown_rendering;
401
+ }
402
+ if (typeof out.ui.syntax_highlighting !== 'boolean') {
403
+ out.ui.syntax_highlighting = DEFAULT_CONFIG.ui.syntax_highlighting;
404
+ }
405
+ // Dynamic lookup of theme via cli-highlight if theme doesn't exist, fallback to solarized-dark
406
+ // cli-highlight themes are usually packaged under its theme directory, but let's check safety.
407
+ // If cli-highlight throws a parse error on a test piece of code, it means the theme is invalid.
408
+ if (typeof out.ui.syntax_theme !== 'string' || !out.ui.syntax_theme.trim()) {
409
+ configWarn("Invalid ui.syntax_theme, using default 'solarized-dark'");
410
+ out.ui.syntax_theme = DEFAULT_CONFIG.ui.syntax_theme;
411
+ }
412
+ else {
413
+ try {
414
+ // cli-highlight is direct CJS dependency, so we require it or dynamically load safely.
415
+ // Since we are in ESM, we can either check synchronously from standard node pathing or do:
416
+ import("cli-highlight").then(({ highlight }) => {
417
+ try {
418
+ highlight("const x = 1;", { theme: out.ui.syntax_theme });
419
+ }
420
+ catch {
421
+ configWarn(`Theme '${out.ui.syntax_theme}' not found or invalid. Falling back to 'solarized-dark'`);
422
+ out.ui.syntax_theme = DEFAULT_CONFIG.ui.syntax_theme;
423
+ }
424
+ }).catch(() => { });
425
+ }
426
+ catch {
427
+ // If any error occurs, default back safely
428
+ out.ui.syntax_theme = DEFAULT_CONFIG.ui.syntax_theme;
429
+ }
430
+ }
431
+ }
432
+ return out;
433
+ }
@@ -0,0 +1,9 @@
1
+ import type { ActivityEntry, TurnDigest, TurnRecord } from "./types.js";
2
+ export declare function deriveActivityEntries(turn: number, digest: TurnDigest, record: TurnRecord, testWasFailing: boolean): ActivityEntry[];
3
+ export declare class ActivityLog {
4
+ private readonly maxEntries;
5
+ private entries;
6
+ constructor(maxEntries: number, initial?: ActivityEntry[]);
7
+ append(entries: ActivityEntry[]): void;
8
+ list(): ActivityEntry[];
9
+ }
@@ -0,0 +1,109 @@
1
+ import { isTestCommand } from "./error-tracker.js";
2
+ const TEST_FAIL_RE = /(\d+)\s+failing|FAIL|failed/i;
3
+ export function deriveActivityEntries(turn, digest, record, testWasFailing) {
4
+ const entries = [];
5
+ for (const tc of record.toolCalls) {
6
+ entries.push(...activityFromToolCall(turn, tc, testWasFailing));
7
+ }
8
+ for (const decision of digest.decisions) {
9
+ const summary = typeof decision === "string" ? decision : decision.summary;
10
+ entries.push({
11
+ turn,
12
+ type: "decision_made",
13
+ summary: `Decided: ${summary}`,
14
+ });
15
+ }
16
+ for (const fixed of digest.errorsFixed) {
17
+ entries.push({
18
+ turn,
19
+ type: "error_fixed",
20
+ summary: `Fixed: ${fixed}`,
21
+ });
22
+ }
23
+ return entries;
24
+ }
25
+ function activityFromToolCall(turn, tc, testWasFailing) {
26
+ const entries = [];
27
+ const command = typeof tc.args.command === "string" ? tc.args.command : undefined;
28
+ const path = typeof tc.args.path === "string" ? tc.args.path : undefined;
29
+ if (tc.tool === "shell" && command) {
30
+ if (/git\s+commit\b/.test(command) && !tc.isError) {
31
+ entries.push({
32
+ turn,
33
+ type: "commit",
34
+ summary: `Committed: ${extractCommitMessage(tc.resultText)}`,
35
+ artifactRef: tc.resultArtifactId,
36
+ });
37
+ }
38
+ if (isTestCommand(command)) {
39
+ if (tc.isError) {
40
+ entries.push({
41
+ turn,
42
+ type: "test_fail",
43
+ summary: `Tests failing: ${extractFailureCount(tc.resultText)}`,
44
+ artifactRef: tc.resultArtifactId,
45
+ });
46
+ }
47
+ else if (testWasFailing) {
48
+ entries.push({
49
+ turn,
50
+ type: "test_pass",
51
+ summary: `Tests passing: ${command.split(/\s+/).slice(0, 2).join(" ")}`,
52
+ artifactRef: tc.resultArtifactId,
53
+ });
54
+ }
55
+ }
56
+ }
57
+ if ((tc.tool === "write_file" || tc.tool === "edit_file") && !tc.isError && path) {
58
+ entries.push({
59
+ turn,
60
+ type: "file_written",
61
+ summary: `Wrote: ${path}`,
62
+ artifactRef: tc.resultArtifactId,
63
+ });
64
+ }
65
+ return entries;
66
+ }
67
+ function extractCommitMessage(resultText) {
68
+ if (!resultText)
69
+ return "changes";
70
+ try {
71
+ const parsed = JSON.parse(resultText);
72
+ const stdout = parsed.stdout ?? parsed.output ?? resultText;
73
+ const firstLine = stdout.split("\n").map((l) => l.trim()).find(Boolean);
74
+ return firstLine?.slice(0, 120) ?? "changes";
75
+ }
76
+ catch {
77
+ const firstLine = resultText.split("\n").map((l) => l.trim()).find(Boolean);
78
+ return firstLine?.slice(0, 120) ?? "changes";
79
+ }
80
+ }
81
+ function extractFailureCount(resultText) {
82
+ if (!resultText)
83
+ return "unknown count";
84
+ const match = resultText.match(TEST_FAIL_RE);
85
+ if (match?.[1])
86
+ return `${match[1]} failures`;
87
+ if (/fail/i.test(resultText))
88
+ return "failures detected";
89
+ return "failures detected";
90
+ }
91
+ export class ActivityLog {
92
+ maxEntries;
93
+ entries = [];
94
+ constructor(maxEntries, initial = []) {
95
+ this.maxEntries = maxEntries;
96
+ this.entries = initial.slice(-maxEntries);
97
+ }
98
+ append(entries) {
99
+ if (entries.length === 0)
100
+ return;
101
+ this.entries.push(...entries);
102
+ if (this.entries.length > this.maxEntries) {
103
+ this.entries = this.entries.slice(-this.maxEntries);
104
+ }
105
+ }
106
+ list() {
107
+ return [...this.entries];
108
+ }
109
+ }
@@ -0,0 +1,32 @@
1
+ import type Database from "better-sqlite3";
2
+ import type { DistillerRegistry } from "./distiller.js";
3
+ import type { ContextEngineConfig } from "../types.js";
4
+ import type { ContextArtifact, IngestToolResultInput, IngestToolResultOutput, RetrieveArtifactOptions } from "./types.js";
5
+ export declare class ArtifactStore {
6
+ private readonly db;
7
+ private readonly sessionId;
8
+ private readonly config;
9
+ private readonly distillers;
10
+ private readonly fileReadIndex;
11
+ private readonly pendingBackfills;
12
+ constructor(db: Database.Database, sessionId: string, config: ContextEngineConfig, distillers: DistillerRegistry);
13
+ static open(dbPath: string, sessionId: string, config: ContextEngineConfig, distillers?: DistillerRegistry): ArtifactStore;
14
+ close(): void;
15
+ getDb(): Database.Database;
16
+ runEviction(currentTurn: number): number;
17
+ flushDeferredDistillation(): Promise<number>;
18
+ ingestToolResult(input: IngestToolResultInput): IngestToolResultOutput;
19
+ retrieve(id: string, currentTurn: number, options?: RetrieveArtifactOptions): {
20
+ ok: true;
21
+ content: string;
22
+ } | {
23
+ ok: false;
24
+ error: string;
25
+ };
26
+ getArtifact(id: string): ContextArtifact | null;
27
+ getSessionId(): string;
28
+ countArtifacts(): number;
29
+ touchAccess(id: string, currentTurn: number): void;
30
+ private recordDistillerStat;
31
+ private fileReadKey;
32
+ }