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.
- package/LICENSE +21 -0
- package/README.md +124 -0
- package/bin/praana.js +17 -0
- package/bin/pran.js +17 -0
- package/dist/app-banner.d.ts +11 -0
- package/dist/app-banner.js +161 -0
- package/dist/app-controller.d.ts +44 -0
- package/dist/app-controller.js +143 -0
- package/dist/app-identity.d.ts +18 -0
- package/dist/app-identity.js +52 -0
- package/dist/auto-compact.d.ts +16 -0
- package/dist/auto-compact.js +101 -0
- package/dist/cli-args.d.ts +14 -0
- package/dist/cli-args.js +69 -0
- package/dist/compile-classic.d.ts +21 -0
- package/dist/compile-classic.js +106 -0
- package/dist/compiler.d.ts +75 -0
- package/dist/compiler.js +406 -0
- package/dist/config.d.ts +3 -0
- package/dist/config.js +433 -0
- package/dist/context-engine/activity-log.d.ts +9 -0
- package/dist/context-engine/activity-log.js +109 -0
- package/dist/context-engine/artifact-store.d.ts +32 -0
- package/dist/context-engine/artifact-store.js +272 -0
- package/dist/context-engine/bm25.d.ts +3 -0
- package/dist/context-engine/bm25.js +32 -0
- package/dist/context-engine/checkpoint.d.ts +34 -0
- package/dist/context-engine/checkpoint.js +430 -0
- package/dist/context-engine/classify.d.ts +3 -0
- package/dist/context-engine/classify.js +60 -0
- package/dist/context-engine/db.d.ts +73 -0
- package/dist/context-engine/db.js +505 -0
- package/dist/context-engine/distiller.d.ts +30 -0
- package/dist/context-engine/distiller.js +67 -0
- package/dist/context-engine/engine-compiler.d.ts +23 -0
- package/dist/context-engine/engine-compiler.js +297 -0
- package/dist/context-engine/error-tracker.d.ts +21 -0
- package/dist/context-engine/error-tracker.js +74 -0
- package/dist/context-engine/event-lineage.d.ts +26 -0
- package/dist/context-engine/event-lineage.js +120 -0
- package/dist/context-engine/extraction.d.ts +26 -0
- package/dist/context-engine/extraction.js +83 -0
- package/dist/context-engine/index.d.ts +82 -0
- package/dist/context-engine/index.js +238 -0
- package/dist/context-engine/scoring.d.ts +13 -0
- package/dist/context-engine/scoring.js +47 -0
- package/dist/context-engine/state-snapshot.d.ts +8 -0
- package/dist/context-engine/state-snapshot.js +50 -0
- package/dist/context-engine/summarize.d.ts +6 -0
- package/dist/context-engine/summarize.js +32 -0
- package/dist/context-engine/telemetry.d.ts +25 -0
- package/dist/context-engine/telemetry.js +64 -0
- package/dist/context-engine/turn-digest.d.ts +50 -0
- package/dist/context-engine/turn-digest.js +250 -0
- package/dist/context-engine/turn-ledger.d.ts +18 -0
- package/dist/context-engine/turn-ledger.js +184 -0
- package/dist/context-engine/turn-recorder.d.ts +24 -0
- package/dist/context-engine/turn-recorder.js +88 -0
- package/dist/context-engine/types.d.ts +201 -0
- package/dist/context-engine/types.js +4 -0
- package/dist/context-pressure.d.ts +19 -0
- package/dist/context-pressure.js +36 -0
- package/dist/distillers/generic.d.ts +14 -0
- package/dist/distillers/generic.js +93 -0
- package/dist/distillers/git-diff.d.ts +8 -0
- package/dist/distillers/git-diff.js +119 -0
- package/dist/distillers/index.d.ts +2 -0
- package/dist/distillers/index.js +16 -0
- package/dist/distillers/npm-test.d.ts +8 -0
- package/dist/distillers/npm-test.js +50 -0
- package/dist/distillers/rg-results.d.ts +8 -0
- package/dist/distillers/rg-results.js +28 -0
- package/dist/distillers/tsc-errors.d.ts +8 -0
- package/dist/distillers/tsc-errors.js +52 -0
- package/dist/event-log.d.ts +56 -0
- package/dist/event-log.js +214 -0
- package/dist/llm.d.ts +29 -0
- package/dist/llm.js +155 -0
- package/dist/logger.d.ts +94 -0
- package/dist/logger.js +287 -0
- package/dist/main.d.ts +1 -0
- package/dist/main.js +54 -0
- package/dist/memory/confidence.d.ts +7 -0
- package/dist/memory/confidence.js +37 -0
- package/dist/memory/consolidation.d.ts +26 -0
- package/dist/memory/consolidation.js +166 -0
- package/dist/memory/db.d.ts +40 -0
- package/dist/memory/db.js +283 -0
- package/dist/memory/dedup.d.ts +6 -0
- package/dist/memory/dedup.js +50 -0
- package/dist/memory/embedder-factory.d.ts +3 -0
- package/dist/memory/embedder-factory.js +81 -0
- package/dist/memory/embeddings.d.ts +15 -0
- package/dist/memory/embeddings.js +67 -0
- package/dist/memory/index.d.ts +9 -0
- package/dist/memory/index.js +11 -0
- package/dist/memory/ollama-summarizer.d.ts +19 -0
- package/dist/memory/ollama-summarizer.js +72 -0
- package/dist/memory/openai-summarizer.d.ts +21 -0
- package/dist/memory/openai-summarizer.js +51 -0
- package/dist/memory/store.d.ts +61 -0
- package/dist/memory/store.js +502 -0
- package/dist/memory/summarizer-factory.d.ts +3 -0
- package/dist/memory/summarizer-factory.js +69 -0
- package/dist/memory/summarizer.d.ts +4 -0
- package/dist/memory/summarizer.js +112 -0
- package/dist/memory/types.d.ts +87 -0
- package/dist/memory/types.js +17 -0
- package/dist/model-context.d.ts +15 -0
- package/dist/model-context.js +212 -0
- package/dist/project-detector.d.ts +37 -0
- package/dist/project-detector.js +604 -0
- package/dist/render.d.ts +15 -0
- package/dist/render.js +46 -0
- package/dist/session.d.ts +118 -0
- package/dist/session.js +809 -0
- package/dist/skills/index.d.ts +69 -0
- package/dist/skills/index.js +885 -0
- package/dist/skills/types.d.ts +93 -0
- package/dist/skills/types.js +8 -0
- package/dist/slash-commands.d.ts +14 -0
- package/dist/slash-commands.js +301 -0
- package/dist/state-graph.d.ts +38 -0
- package/dist/state-graph.js +255 -0
- package/dist/status-bar.d.ts +54 -0
- package/dist/status-bar.js +184 -0
- package/dist/thinking-display.d.ts +21 -0
- package/dist/thinking-display.js +37 -0
- package/dist/tool-summary.d.ts +4 -0
- package/dist/tool-summary.js +67 -0
- package/dist/tools/index.d.ts +925 -0
- package/dist/tools/index.js +86 -0
- package/dist/tools/knowledge.d.ts +140 -0
- package/dist/tools/knowledge.js +260 -0
- package/dist/tools/memory.d.ts +39 -0
- package/dist/tools/memory.js +300 -0
- package/dist/tools/search-code.d.ts +134 -0
- package/dist/tools/search-code.js +390 -0
- package/dist/tools/system.d.ts +16 -0
- package/dist/tools/system.js +499 -0
- package/dist/tools/tool-def.d.ts +6 -0
- package/dist/tools/tool-def.js +3 -0
- package/dist/turn-control.d.ts +51 -0
- package/dist/turn-control.js +210 -0
- package/dist/turn.d.ts +20 -0
- package/dist/turn.js +624 -0
- package/dist/types.d.ts +233 -0
- package/dist/types.js +4 -0
- package/dist/ui/readline-ui.d.ts +2 -0
- package/dist/ui/readline-ui.js +176 -0
- package/dist/ui/tui/app.d.ts +13 -0
- package/dist/ui/tui/app.js +270 -0
- package/dist/ui/tui/busy-indicator.d.ts +2 -0
- package/dist/ui/tui/busy-indicator.js +13 -0
- package/dist/ui/tui/components/gutter-rule.d.ts +5 -0
- package/dist/ui/tui/components/gutter-rule.js +9 -0
- package/dist/ui/tui/components/inline-tool-row.d.ts +10 -0
- package/dist/ui/tui/components/inline-tool-row.js +8 -0
- package/dist/ui/tui/components/prompt-input.d.ts +20 -0
- package/dist/ui/tui/components/prompt-input.js +120 -0
- package/dist/ui/tui/components/system-line.d.ts +5 -0
- package/dist/ui/tui/components/system-line.js +6 -0
- package/dist/ui/tui/components/thinking-block.d.ts +11 -0
- package/dist/ui/tui/components/thinking-block.js +31 -0
- package/dist/ui/tui/components/toast-line.d.ts +4 -0
- package/dist/ui/tui/components/toast-line.js +8 -0
- package/dist/ui/tui/components/tool-result-line.d.ts +5 -0
- package/dist/ui/tui/components/tool-result-line.js +6 -0
- package/dist/ui/tui/components/turn-footer.d.ts +5 -0
- package/dist/ui/tui/components/turn-footer.js +7 -0
- package/dist/ui/tui/components/user-block.d.ts +6 -0
- package/dist/ui/tui/components/user-block.js +6 -0
- package/dist/ui/tui/logo-banner.d.ts +5 -0
- package/dist/ui/tui/logo-banner.js +8 -0
- package/dist/ui/tui/markdown-render.d.ts +16 -0
- package/dist/ui/tui/markdown-render.js +218 -0
- package/dist/ui/tui/palette.d.ts +12 -0
- package/dist/ui/tui/palette.js +13 -0
- package/dist/ui/tui/reasoning-summary.d.ts +12 -0
- package/dist/ui/tui/reasoning-summary.js +27 -0
- package/dist/ui/tui/reducer.d.ts +92 -0
- package/dist/ui/tui/reducer.js +260 -0
- package/dist/ui/tui/run.d.ts +3 -0
- package/dist/ui/tui/run.js +40 -0
- package/dist/ui/tui/sink.d.ts +4 -0
- package/dist/ui/tui/sink.js +89 -0
- package/dist/ui/tui/status-bar-view.d.ts +5 -0
- package/dist/ui/tui/status-bar-view.js +44 -0
- package/dist/ui/tui/terminal-height.d.ts +12 -0
- package/dist/ui/tui/terminal-height.js +20 -0
- package/dist/ui/tui/terminal-width.d.ts +2 -0
- package/dist/ui/tui/terminal-width.js +5 -0
- package/dist/ui/tui/tool-display.d.ts +23 -0
- package/dist/ui/tui/tool-display.js +217 -0
- package/dist/ui/tui/transcript-line.d.ts +12 -0
- package/dist/ui/tui/transcript-line.js +43 -0
- package/dist/ui/tui/transcript-replay.d.ts +12 -0
- package/dist/ui/tui/transcript-replay.js +117 -0
- package/dist/ui-events.d.ts +39 -0
- package/dist/ui-events.js +33 -0
- package/dist/ui.d.ts +77 -0
- package/dist/ui.js +179 -0
- package/package.json +73 -0
- 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
|
+
}
|