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/logger.js ADDED
@@ -0,0 +1,287 @@
1
+ import { existsSync, mkdirSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { dirname, join } from "node:path";
4
+ import { APP_HOME_DIR, APP_NAME, resolveAppHomePath, } from "./app-identity.js";
5
+ import pino from "pino";
6
+ import pretty from "pino-pretty";
7
+ import { Writable } from "node:stream";
8
+ /** Daily rotated logs; symlink at `current.log` points to today's file. */
9
+ export const LOG_SYMLINK_FILENAME = "current.log";
10
+ export const LOG_RETENTION_DAYS = 15;
11
+ /** pino-roll retains `count` rotated files plus the active file. */
12
+ export const LOG_RETENTION_COUNT = LOG_RETENTION_DAYS - 1;
13
+ const PINO_LEVEL = {
14
+ error: "error",
15
+ warn: "warn",
16
+ info: "info",
17
+ debug: "debug",
18
+ };
19
+ function expandHome(p) {
20
+ return p.startsWith("~/") ? join(homedir(), p.slice(2)) : p;
21
+ }
22
+ export function getAppLogDir() {
23
+ return resolveAppHomePath("logs");
24
+ }
25
+ /** Base path for pino-roll (extension added by rotator). */
26
+ export function getAppLogBase() {
27
+ return join(getAppLogDir(), APP_NAME.toLowerCase());
28
+ }
29
+ /** Stable symlink path for tailing the active app log. */
30
+ export function getAppLogPath() {
31
+ return join(getAppLogDir(), LOG_SYMLINK_FILENAME);
32
+ }
33
+ export function getSessionLogBase(sessionLogDir, sessionId) {
34
+ return join(expandHome(sessionLogDir), sessionId, "system");
35
+ }
36
+ /** Stable symlink path for tailing the active session system log. */
37
+ export function getSessionSystemLogPath(sessionLogDir, sessionId) {
38
+ return join(expandHome(sessionLogDir), sessionId, LOG_SYMLINK_FILENAME);
39
+ }
40
+ function ensureParentDir(filePath) {
41
+ const dir = dirname(filePath);
42
+ if (!existsSync(dir)) {
43
+ mkdirSync(dir, { recursive: true });
44
+ }
45
+ }
46
+ function isTestEnv() {
47
+ return process.env.VITEST === "true";
48
+ }
49
+ const rollingStreams = new Map();
50
+ async function createRollingFileDestination(basePath) {
51
+ const cached = rollingStreams.get(basePath);
52
+ if (cached)
53
+ return cached;
54
+ ensureParentDir(`${basePath}.log`);
55
+ const pinoRoll = (await import("pino-roll")).default;
56
+ const stream = await pinoRoll({
57
+ file: basePath,
58
+ frequency: "daily",
59
+ dateFormat: "yyyy-MM-dd",
60
+ mkdir: true,
61
+ sync: true,
62
+ symlink: true,
63
+ limit: { count: LOG_RETENTION_COUNT, removeOtherLogFiles: true },
64
+ });
65
+ rollingStreams.set(basePath, stream);
66
+ return stream;
67
+ }
68
+ function createStderrDestination(options) {
69
+ if (options.writeLine) {
70
+ return new Writable({
71
+ write(chunk, _encoding, callback) {
72
+ options.writeLine(String(chunk).trimEnd());
73
+ callback();
74
+ },
75
+ });
76
+ }
77
+ if (isTestEnv()) {
78
+ return new Writable({
79
+ write(_chunk, _encoding, callback) {
80
+ callback();
81
+ },
82
+ });
83
+ }
84
+ if (process.stderr.isTTY) {
85
+ return pretty({
86
+ destination: 2,
87
+ colorize: true,
88
+ ignore: "pid,hostname,sessionId,domain,code,details,notice",
89
+ messageFormat: (log, messageKey) => {
90
+ const domain = typeof log.domain === "string" ? `[${log.domain}] ` : "";
91
+ const code = typeof log.code === "string" ? `(${log.code}) ` : "";
92
+ return `${domain}${code}${log[messageKey]}`;
93
+ },
94
+ });
95
+ }
96
+ return pino.destination({ dest: 2, sync: true });
97
+ }
98
+ function createPinoLogger(options) {
99
+ const debug = options.debug ?? false;
100
+ const stderrLevel = debug ? "debug" : "warn";
101
+ const fileLevel = debug ? "debug" : "info";
102
+ const streams = [
103
+ { level: stderrLevel, stream: createStderrDestination(options) },
104
+ ];
105
+ if (!options.writeLine && !isTestEnv()) {
106
+ if (options.appFileStream) {
107
+ streams.push({ level: fileLevel, stream: options.appFileStream });
108
+ }
109
+ if (options.sessionFileStream) {
110
+ streams.push({ level: fileLevel, stream: options.sessionFileStream });
111
+ }
112
+ }
113
+ const destination = streams.length === 1 ? streams[0].stream : pino.multistream(streams);
114
+ return pino({
115
+ level: debug ? "debug" : "info",
116
+ base: options.sessionId ? { sessionId: options.sessionId } : undefined,
117
+ }, destination);
118
+ }
119
+ export class PraanaLogger {
120
+ pino;
121
+ options;
122
+ constructor(options = {}) {
123
+ this.options = {
124
+ domain: options.domain ?? "app",
125
+ debug: options.debug ?? false,
126
+ sessionId: options.sessionId,
127
+ sessionLogDir: options.sessionLogDir,
128
+ appFileStream: options.appFileStream,
129
+ sessionFileStream: options.sessionFileStream,
130
+ writeLine: options.writeLine,
131
+ captureNotice: options.captureNotice,
132
+ };
133
+ this.pino = createPinoLogger(this.options).child({ domain: this.options.domain });
134
+ }
135
+ child(domain) {
136
+ return new PraanaLogger({ ...this.options, domain });
137
+ }
138
+ log(entry) {
139
+ const fields = {
140
+ ...(entry.code ? { code: entry.code } : {}),
141
+ ...(entry.details ? { details: entry.details } : {}),
142
+ ...(entry.cause ? { err: entry.cause } : {}),
143
+ };
144
+ const target = entry.domain && entry.domain !== this.options.domain
145
+ ? this.pino.child({ domain: entry.domain })
146
+ : this.pino;
147
+ target[PINO_LEVEL[entry.level]](fields, entry.message);
148
+ }
149
+ error(message, opts) {
150
+ this.log({
151
+ level: "error",
152
+ domain: opts?.domain ?? this.options.domain,
153
+ message,
154
+ code: opts?.code,
155
+ details: opts?.details,
156
+ cause: opts?.cause,
157
+ });
158
+ }
159
+ warn(message, opts) {
160
+ this.log({
161
+ level: "warn",
162
+ domain: opts?.domain ?? this.options.domain,
163
+ message,
164
+ code: opts?.code,
165
+ details: opts?.details,
166
+ cause: opts?.cause,
167
+ });
168
+ }
169
+ info(message, opts) {
170
+ this.log({
171
+ level: "info",
172
+ domain: opts?.domain ?? this.options.domain,
173
+ message,
174
+ details: opts?.details,
175
+ });
176
+ }
177
+ debug(message, opts) {
178
+ this.log({
179
+ level: "debug",
180
+ domain: opts?.domain ?? this.options.domain,
181
+ message,
182
+ details: opts?.details,
183
+ });
184
+ }
185
+ /** User-visible status — always on stderr; also written to system log files at info level. */
186
+ notice(message, opts) {
187
+ const domain = opts?.domain ?? this.options.domain;
188
+ const line = `[${domain}] ${message}`;
189
+ if (this.options.captureNotice) {
190
+ this.options.captureNotice(line);
191
+ }
192
+ else if (this.options.writeLine) {
193
+ this.options.writeLine(line);
194
+ }
195
+ else if (!isTestEnv()) {
196
+ process.stderr.write(line + "\n");
197
+ }
198
+ const target = domain !== this.options.domain ? this.pino.child({ domain }) : this.pino;
199
+ target.info({
200
+ ...(opts?.details ? { details: opts.details } : {}),
201
+ notice: true,
202
+ }, message);
203
+ }
204
+ }
205
+ let appLogger = new PraanaLogger({ domain: "app" });
206
+ let appLogInitPromise = null;
207
+ export function getAppLogger() {
208
+ return appLogger;
209
+ }
210
+ export function setAppLogger(logger) {
211
+ appLogger = logger;
212
+ }
213
+ /** Initialise daily-rotating app log under ~/.praana/logs (no-op in tests). */
214
+ export async function initAppLogFile() {
215
+ if (isTestEnv())
216
+ return;
217
+ if (appLogInitPromise)
218
+ return appLogInitPromise;
219
+ appLogInitPromise = (async () => {
220
+ const stream = await createRollingFileDestination(getAppLogBase());
221
+ appLogger = new PraanaLogger({ domain: "app", appFileStream: stream });
222
+ })();
223
+ return appLogInitPromise;
224
+ }
225
+ export async function createSessionLogger(opts) {
226
+ if (isTestEnv()) {
227
+ return new PraanaLogger({
228
+ domain: "session",
229
+ debug: opts.debug ?? false,
230
+ sessionId: opts.sessionId,
231
+ sessionLogDir: opts.sessionLogDir,
232
+ });
233
+ }
234
+ await initAppLogFile();
235
+ const appStream = rollingStreams.get(getAppLogBase());
236
+ const sessionStream = await createRollingFileDestination(getSessionLogBase(opts.sessionLogDir, opts.sessionId));
237
+ return new PraanaLogger({
238
+ domain: "session",
239
+ debug: opts.debug ?? false,
240
+ sessionId: opts.sessionId,
241
+ sessionLogDir: opts.sessionLogDir,
242
+ appFileStream: appStream,
243
+ sessionFileStream: sessionStream,
244
+ captureNotice: opts.captureNotice,
245
+ });
246
+ }
247
+ export function createTestLogger(writeLine, opts) {
248
+ return new PraanaLogger({
249
+ domain: "app",
250
+ debug: opts?.debug ?? false,
251
+ writeLine,
252
+ });
253
+ }
254
+ /** Extract a human-readable message from a pi-ai assistant/error message object. */
255
+ export function extractLlmErrorMessage(message) {
256
+ if (!message || typeof message !== "object")
257
+ return undefined;
258
+ const msg = message;
259
+ if (typeof msg.errorMessage === "string" && msg.errorMessage.trim()) {
260
+ return msg.errorMessage.trim();
261
+ }
262
+ const content = msg.content;
263
+ if (Array.isArray(content)) {
264
+ for (const block of content) {
265
+ if (!block || typeof block !== "object")
266
+ continue;
267
+ const b = block;
268
+ if (b.type === "text" && typeof b.text === "string" && b.text.trim()) {
269
+ return b.text.trim();
270
+ }
271
+ }
272
+ }
273
+ return undefined;
274
+ }
275
+ export function formatUserFacingLlmError(opts) {
276
+ const detail = opts.llmMessage?.trim();
277
+ if (detail) {
278
+ return `[LLM error: ${detail}]`;
279
+ }
280
+ if (opts.reason === "error") {
281
+ return `[LLM request failed — see ~/.${APP_HOME_DIR}/logs/current.log or the session current.log (model: ${opts.model}, provider: ${opts.provider})]`;
282
+ }
283
+ return "[no response from model — try again or switch models with /model]";
284
+ }
285
+ export function isLogLevel(value) {
286
+ return value === "error" || value === "warn" || value === "info" || value === "debug";
287
+ }
package/dist/main.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare function main(): Promise<void>;
package/dist/main.js ADDED
@@ -0,0 +1,54 @@
1
+ import { resolve } from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import { getMissingKeyMessage } from "./llm.js";
4
+ import { loadConfig } from "./config.js";
5
+ import { getAppLogger, initAppLogFile } from "./logger.js";
6
+ import { parseCliArgs, resolveUiMode, resolveScreenMode, } from "./cli-args.js";
7
+ import { printHelp } from "./app-banner.js";
8
+ import { AppController } from "./app-controller.js";
9
+ import { runReadlineUi } from "./ui/readline-ui.js";
10
+ import { runTui } from "./ui/tui/run.js";
11
+ export async function main() {
12
+ const parsed = parseCliArgs(process.argv.slice(2));
13
+ if (parsed.showHelp) {
14
+ printHelp();
15
+ process.exit(0);
16
+ }
17
+ await initAppLogFile();
18
+ const cwd = resolve(process.cwd());
19
+ const config = loadConfig(parsed.configPath);
20
+ const keyError = getMissingKeyMessage(config.llm.provider);
21
+ if (keyError) {
22
+ getAppLogger().error(keyError, { code: "SESSION_START_FAILED" });
23
+ process.exit(1);
24
+ }
25
+ const isInteractive = !!(process.stdin.isTTY && process.stdout.isTTY);
26
+ const uiMode = resolveUiMode(config.ui.mode, parsed.uiMode, isInteractive);
27
+ const screenMode = resolveScreenMode(config.ui.screen, parsed.screenMode);
28
+ const controller = new AppController({ cwd, config, parsed });
29
+ try {
30
+ const info = await controller.start({ uiMode });
31
+ if (uiMode === "tui") {
32
+ await runTui(controller, info, screenMode);
33
+ }
34
+ else {
35
+ await runReadlineUi(controller, info);
36
+ }
37
+ }
38
+ catch (err) {
39
+ getAppLogger().error("Failed to start session", {
40
+ code: "SESSION_START_FAILED",
41
+ cause: err,
42
+ });
43
+ process.exit(1);
44
+ }
45
+ }
46
+ const isDirectRun = process.argv[1]
47
+ ? resolve(process.argv[1]) === fileURLToPath(import.meta.url)
48
+ : false;
49
+ if (isDirectRun) {
50
+ main().catch((err) => {
51
+ getAppLogger().error("Fatal error", { cause: err });
52
+ process.exit(1);
53
+ });
54
+ }
@@ -0,0 +1,7 @@
1
+ import type { MemoryEntry, MemoryKind } from "./types.js";
2
+ /** Per-kind half-life in days. null = never decays. Layer 2 entries use 4× effective half-life. */
3
+ export declare const HALF_LIFE_DAYS: Record<MemoryKind, number | null>;
4
+ /** Per-kind weights when ranking digest entries. */
5
+ export declare const KIND_WEIGHTS: Record<MemoryKind, number>;
6
+ export declare function digestScore(entry: MemoryEntry, now: number): number;
7
+ export declare function effectiveConfidence(entry: MemoryEntry, now: number): number;
@@ -0,0 +1,37 @@
1
+ /** Per-kind half-life in days. null = never decays. Layer 2 entries use 4× effective half-life. */
2
+ export const HALF_LIFE_DAYS = {
3
+ constraint: null,
4
+ preference: 180,
5
+ fact: 90,
6
+ decision: 365,
7
+ mistake: 60,
8
+ pattern: 365,
9
+ };
10
+ const LAYER2_HALF_LIFE_MULTIPLIER = 4;
11
+ const MS_PER_DAY = 86_400_000;
12
+ /** Per-kind weights when ranking digest entries. */
13
+ export const KIND_WEIGHTS = {
14
+ constraint: 1.3,
15
+ fact: 1.0,
16
+ preference: 1.2,
17
+ pattern: 1.1,
18
+ decision: 0.9,
19
+ mistake: 0.7,
20
+ };
21
+ export function digestScore(entry, now) {
22
+ return effectiveConfidence(entry, now) * (KIND_WEIGHTS[entry.kind] ?? 1.0);
23
+ }
24
+ export function effectiveConfidence(entry, now) {
25
+ if (entry.pinned)
26
+ return entry.confidence;
27
+ const halfLifeDays = HALF_LIFE_DAYS[entry.kind];
28
+ if (halfLifeDays === null)
29
+ return entry.confidence;
30
+ const ageDays = (now - entry.created_at) / MS_PER_DAY;
31
+ let effectiveHalfLife = halfLifeDays;
32
+ if (entry.layer === 2) {
33
+ effectiveHalfLife *= LAYER2_HALF_LIFE_MULTIPLIER;
34
+ }
35
+ const decay = Math.pow(0.5, ageDays / effectiveHalfLife);
36
+ return entry.confidence * decay;
37
+ }
@@ -0,0 +1,26 @@
1
+ import type { SessionEvent, SummarizerLLM } from "./types.js";
2
+ import type { MemoryStore } from "./store.js";
3
+ export interface ConsolidationConfig {
4
+ enabled: boolean;
5
+ model?: string;
6
+ promotion_threshold: number;
7
+ run_delay_seconds: number;
8
+ }
9
+ export interface ConsolidationResult {
10
+ promotions: number;
11
+ confirmations: number;
12
+ contradictions: number;
13
+ newEntries: number;
14
+ duration_ms: number;
15
+ }
16
+ /**
17
+ * Run the consolidation processor.
18
+ * Re-evaluates session learnings against existing memory and promotes confirmed entries.
19
+ */
20
+ export declare function runConsolidation(opts: {
21
+ store: MemoryStore;
22
+ llm: SummarizerLLM;
23
+ sessionId: string;
24
+ events: SessionEvent[];
25
+ config: ConsolidationConfig;
26
+ }): Promise<ConsolidationResult>;
@@ -0,0 +1,166 @@
1
+ // ============================================================
2
+ // ARIA Memory — Consolidation Processor
3
+ //
4
+ // Background learning loop: re-evaluates session learnings,
5
+ // cross-checks against existing Layer 1 entries, and promotes
6
+ // confirmed patterns to Layer 2 (deep memory).
7
+ // ============================================================
8
+ import { effectiveConfidence } from "./confidence.js";
9
+ import { getAppLogger } from "../logger.js";
10
+ const SYSTEM_PROMPT = `You are a memory consolidation processor for a coding agent.
11
+ Given a session transcript and existing Layer 1 memory entries, you must:
12
+ 1. Identify which existing entries are confirmed by the session
13
+ 2. Identify which entries are contradicted by the session
14
+ 3. Extract new facts, patterns, or decisions not yet in memory
15
+ 4. Recommend entries for promotion to Layer 2 (deep memory)
16
+
17
+ Output ONLY a JSON object with this structure:
18
+ {
19
+ "confirmations": ["entry_id_1", ...],
20
+ "contradictions": ["entry_id_2", ...],
21
+ "new_entries": [
22
+ { "kind": "fact|preference|decision|pattern|mistake|constraint", "content": "...", "certainty": "high|medium|low" }
23
+ ],
24
+ "promotions": ["entry_id_3", ...]
25
+ }
26
+
27
+ Rules:
28
+ - confirmations: entry IDs that the session reinforces (mentions, uses, or validates)
29
+ - contradictions: entry IDs that the session explicitly contradicts or disproves
30
+ - new_entries: NEW facts not already covered by existing entries (max 5)
31
+ - promotions: entry IDs ready for Layer 2 (high confidence, confirmed multiple times)
32
+ - Be conservative: only promote entries with confirmation_count >= threshold AND confidence >= 0.6
33
+ - Output ONLY the JSON object. No prose.`;
34
+ function buildConsolidationPrompt(transcript, layer1Entries, promotionThreshold) {
35
+ const lines = [];
36
+ lines.push("## Session Transcript");
37
+ lines.push(truncateText(transcript, 4000));
38
+ lines.push("");
39
+ lines.push("## Existing Layer 1 Entries");
40
+ if (layer1Entries.length === 0) {
41
+ lines.push("(none)");
42
+ }
43
+ else {
44
+ for (const entry of layer1Entries) {
45
+ lines.push(`- [${entry.id}] (${entry.kind}, conf=${entry.confidence.toFixed(2)}, confirms=${entry.confirmation_count}) ${entry.content}`);
46
+ }
47
+ }
48
+ lines.push("");
49
+ lines.push(`## Promotion Threshold`);
50
+ lines.push(`- confirmation_count >= ${promotionThreshold}`);
51
+ lines.push(`- confidence >= 0.6`);
52
+ lines.push("");
53
+ return lines.join("\n");
54
+ }
55
+ function transcriptToText(events) {
56
+ const lines = [];
57
+ for (const e of events) {
58
+ if (e.type === "user_message")
59
+ lines.push(`User: ${e.content}`);
60
+ else if (e.type === "agent_message")
61
+ lines.push(`Agent: ${e.content}`);
62
+ else if (e.type === "tool_use")
63
+ lines.push(`[tool] ${e.tool_name}(${JSON.stringify(e.args ?? {})})`);
64
+ else if (e.type === "tool_result")
65
+ lines.push(`[result] ${truncateText(JSON.stringify(e.result), 200)}`);
66
+ }
67
+ return lines.join("\n");
68
+ }
69
+ function truncateText(text, maxLen) {
70
+ if (text.length <= maxLen)
71
+ return text;
72
+ return text.slice(0, maxLen - 3) + "...";
73
+ }
74
+ /**
75
+ * Run the consolidation processor.
76
+ * Re-evaluates session learnings against existing memory and promotes confirmed entries.
77
+ */
78
+ export async function runConsolidation(opts) {
79
+ const startTime = Date.now();
80
+ const result = {
81
+ promotions: 0,
82
+ confirmations: 0,
83
+ contradictions: 0,
84
+ newEntries: 0,
85
+ duration_ms: 0,
86
+ };
87
+ if (!opts.config.enabled)
88
+ return result;
89
+ if (opts.events.length === 0)
90
+ return result;
91
+ if (!(await opts.llm.available()))
92
+ return result;
93
+ try {
94
+ // Get Layer 1 entries for the current scope
95
+ const allEntries = opts.store.getAllEntries();
96
+ const layer1Entries = allEntries.filter((e) => e.layer === 1);
97
+ // Build the prompt
98
+ const transcript = transcriptToText(opts.events);
99
+ const prompt = buildConsolidationPrompt(transcript, layer1Entries, opts.config.promotion_threshold);
100
+ // Single LLM call
101
+ const raw = await opts.llm.complete({
102
+ system: SYSTEM_PROMPT,
103
+ prompt,
104
+ temperature: 0.2,
105
+ maxTokens: 2000,
106
+ json: true,
107
+ timeoutMs: 60_000,
108
+ });
109
+ const parsed = JSON.parse(raw);
110
+ // Process confirmations
111
+ if (Array.isArray(parsed.confirmations)) {
112
+ for (const id of parsed.confirmations) {
113
+ const entry = allEntries.find((e) => e.id === id);
114
+ if (entry && entry.layer === 1) {
115
+ opts.store.reinforceFromSuccessfulToolOutcome([id], 0.1);
116
+ result.confirmations++;
117
+ }
118
+ }
119
+ }
120
+ // Process contradictions
121
+ if (Array.isArray(parsed.contradictions)) {
122
+ for (const id of parsed.contradictions) {
123
+ const entry = allEntries.find((e) => e.id === id);
124
+ if (entry && entry.layer === 1) {
125
+ // Weaken contradicted entries
126
+ opts.store.weakenEntry(id, 0.2);
127
+ result.contradictions++;
128
+ }
129
+ }
130
+ }
131
+ // Process new entries
132
+ if (Array.isArray(parsed.new_entries)) {
133
+ for (const newEntry of parsed.new_entries.slice(0, 5)) {
134
+ if (["fact", "preference", "decision", "pattern", "mistake", "constraint"].includes(newEntry.kind)) {
135
+ await opts.store.remember(newEntry.content, {
136
+ kind: newEntry.kind,
137
+ certainty: (newEntry.certainty === "high" || newEntry.certainty === "medium" || newEntry.certainty === "low")
138
+ ? newEntry.certainty
139
+ : "medium",
140
+ });
141
+ result.newEntries++;
142
+ }
143
+ }
144
+ }
145
+ // Process promotions to Layer 2
146
+ if (Array.isArray(parsed.promotions)) {
147
+ for (const id of parsed.promotions) {
148
+ const entry = allEntries.find((e) => e.id === id);
149
+ if (entry &&
150
+ entry.layer === 1 &&
151
+ entry.confirmation_count >= opts.config.promotion_threshold &&
152
+ effectiveConfidence(entry, Date.now()) >= 0.6) {
153
+ opts.store.promoteToLayer2(id);
154
+ result.promotions++;
155
+ }
156
+ }
157
+ }
158
+ }
159
+ catch (err) {
160
+ getAppLogger().child("memory").warn("Error during consolidation", {
161
+ cause: err,
162
+ });
163
+ }
164
+ result.duration_ms = Date.now() - startTime;
165
+ return result;
166
+ }
@@ -0,0 +1,40 @@
1
+ import Database from "better-sqlite3";
2
+ import type { MemoryEntry, MemoryKind } from "./types.js";
3
+ export interface OpenMemoryDbResult {
4
+ db: Database.Database;
5
+ needsReembed: boolean;
6
+ }
7
+ export declare function openMemoryDb(path: string, embeddingDim?: number): OpenMemoryDbResult;
8
+ export declare function markReembedNeeded(db: Database.Database): void;
9
+ export declare function clearReembedNeeded(db: Database.Database): void;
10
+ export declare function insertEntry(db: Database.Database, e: MemoryEntry): void;
11
+ export declare function touchEntry(db: Database.Database, id: string, now: number): void;
12
+ export declare function reinforceEntry(db: Database.Database, id: string, alpha?: number): void;
13
+ export declare function weakenEntry(db: Database.Database, id: string, beta?: number): void;
14
+ export declare function stampReinforcement(db: Database.Database, entryId: string, sessionId: string): void;
15
+ export declare function flushReinforcements(db: Database.Database, sessionId: string): void;
16
+ export declare function getEntryById(db: Database.Database, id: string): MemoryEntry | undefined;
17
+ export declare function getAllEntries(db: Database.Database): MemoryEntry[];
18
+ export declare function getEntriesByScope(db: Database.Database, scopes: string[]): MemoryEntry[];
19
+ export declare function deleteEntry(db: Database.Database, id: string): void;
20
+ export declare function retractMemory(db: Database.Database, id: string): void;
21
+ export declare function startSessionRow(db: Database.Database, s: {
22
+ id: string;
23
+ agent: string;
24
+ user_id: string;
25
+ context_id: string;
26
+ started_at: number;
27
+ }): void;
28
+ export declare function endSessionRow(db: Database.Database, id: string, endedAt: number, reason: string): void;
29
+ export declare function upsertEmbedding(db: Database.Database, entryId: string, embedding: Float32Array): void;
30
+ export declare function searchByVector(db: Database.Database, query: Float32Array, k: number): Array<{
31
+ entry_id: string;
32
+ distance: number;
33
+ }>;
34
+ export declare function searchByFts(db: Database.Database, query: string, k: number, filters?: {
35
+ scopes?: string[];
36
+ kinds?: MemoryKind[];
37
+ }): Array<{
38
+ entry_id: string;
39
+ rank: number;
40
+ }>;