@askexenow/exe-os 0.8.0 → 0.8.1

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 (90) hide show
  1. package/README.md +178 -79
  2. package/dist/bin/backfill-responses.js +160 -8
  3. package/dist/bin/backfill-vectors.js +130 -1
  4. package/dist/bin/cleanup-stale-review-tasks.js +130 -1
  5. package/dist/bin/cli.js +10111 -7540
  6. package/dist/bin/exe-agent.js +159 -1
  7. package/dist/bin/exe-assign.js +235 -16
  8. package/dist/bin/exe-boot.js +344 -472
  9. package/dist/bin/exe-call.js +145 -1
  10. package/dist/bin/exe-cloud.js +11 -0
  11. package/dist/bin/exe-dispatch.js +37 -24
  12. package/dist/bin/exe-doctor.js +130 -1
  13. package/dist/bin/exe-export-behaviors.js +150 -7
  14. package/dist/bin/exe-forget.js +822 -665
  15. package/dist/bin/exe-gateway.js +470 -62
  16. package/dist/bin/exe-heartbeat.js +133 -2
  17. package/dist/bin/exe-kill.js +150 -7
  18. package/dist/bin/exe-launch-agent.js +150 -7
  19. package/dist/bin/exe-new-employee.js +756 -224
  20. package/dist/bin/exe-pending-messages.js +132 -2
  21. package/dist/bin/exe-pending-notifications.js +130 -1
  22. package/dist/bin/exe-pending-reviews.js +132 -2
  23. package/dist/bin/exe-review.js +160 -8
  24. package/dist/bin/exe-search.js +2473 -2008
  25. package/dist/bin/exe-session-cleanup.js +238 -51
  26. package/dist/bin/exe-settings.js +11 -0
  27. package/dist/bin/exe-status.js +130 -1
  28. package/dist/bin/exe-team.js +130 -1
  29. package/dist/bin/git-sweep.js +272 -16
  30. package/dist/bin/graph-backfill.js +150 -7
  31. package/dist/bin/graph-export.js +150 -7
  32. package/dist/bin/install.js +5 -0
  33. package/dist/bin/scan-tasks.js +238 -19
  34. package/dist/bin/setup.js +1776 -10
  35. package/dist/bin/shard-migrate.js +150 -7
  36. package/dist/bin/update.js +9 -6
  37. package/dist/bin/wiki-sync.js +150 -7
  38. package/dist/gateway/index.js +470 -62
  39. package/dist/hooks/bug-report-worker.js +195 -35
  40. package/dist/hooks/commit-complete.js +272 -16
  41. package/dist/hooks/error-recall.js +2313 -1847
  42. package/dist/hooks/exe-heartbeat-hook.js +5 -0
  43. package/dist/hooks/ingest-worker.js +330 -58
  44. package/dist/hooks/ingest.js +11 -0
  45. package/dist/hooks/instructions-loaded.js +199 -10
  46. package/dist/hooks/notification.js +199 -10
  47. package/dist/hooks/post-compact.js +199 -10
  48. package/dist/hooks/pre-compact.js +199 -10
  49. package/dist/hooks/pre-tool-use.js +199 -10
  50. package/dist/hooks/prompt-ingest-worker.js +179 -14
  51. package/dist/hooks/prompt-submit.js +781 -285
  52. package/dist/hooks/response-ingest-worker.js +1900 -1405
  53. package/dist/hooks/session-end.js +456 -12
  54. package/dist/hooks/session-start.js +2188 -1724
  55. package/dist/hooks/stop.js +200 -10
  56. package/dist/hooks/subagent-stop.js +199 -10
  57. package/dist/hooks/summary-worker.js +604 -334
  58. package/dist/index.js +554 -61
  59. package/dist/lib/cloud-sync.js +5 -0
  60. package/dist/lib/config.js +13 -0
  61. package/dist/lib/consolidation.js +5 -0
  62. package/dist/lib/database.js +104 -0
  63. package/dist/lib/device-registry.js +109 -0
  64. package/dist/lib/embedder.js +13 -0
  65. package/dist/lib/employee-templates.js +53 -26
  66. package/dist/lib/employees.js +5 -0
  67. package/dist/lib/exe-daemon-client.js +5 -0
  68. package/dist/lib/exe-daemon.js +493 -79
  69. package/dist/lib/file-grep.js +20 -4
  70. package/dist/lib/hybrid-search.js +1435 -190
  71. package/dist/lib/identity-templates.js +126 -5
  72. package/dist/lib/identity.js +5 -0
  73. package/dist/lib/license.js +5 -0
  74. package/dist/lib/messaging.js +37 -24
  75. package/dist/lib/schedules.js +130 -1
  76. package/dist/lib/skill-learning.js +11 -0
  77. package/dist/lib/status-brief.js +5 -0
  78. package/dist/lib/store.js +199 -10
  79. package/dist/lib/task-router.js +72 -6
  80. package/dist/lib/tasks.js +179 -50
  81. package/dist/lib/tmux-routing.js +179 -46
  82. package/dist/mcp/server.js +2129 -1855
  83. package/dist/mcp/tools/create-task.js +86 -36
  84. package/dist/mcp/tools/deactivate-behavior.js +5 -0
  85. package/dist/mcp/tools/list-tasks.js +39 -11
  86. package/dist/mcp/tools/send-message.js +37 -24
  87. package/dist/mcp/tools/update-task.js +153 -38
  88. package/dist/runtime/index.js +451 -59
  89. package/dist/tui/App.js +454 -59
  90. package/package.json +1 -1
@@ -22,443 +22,29 @@ var init_memory = __esm({
22
22
  }
23
23
  });
24
24
 
25
- // src/lib/config.ts
26
- import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
27
- import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
28
- import path2 from "path";
29
- import os from "os";
30
- function resolveDataDir() {
31
- if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
32
- if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
33
- const newDir = path2.join(os.homedir(), ".exe-os");
34
- const legacyDir = path2.join(os.homedir(), ".exe-mem");
35
- if (!existsSync2(newDir) && existsSync2(legacyDir)) {
36
- try {
37
- renameSync(legacyDir, newDir);
38
- process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
39
- `);
40
- } catch {
41
- return legacyDir;
42
- }
43
- }
44
- return newDir;
45
- }
46
- function migrateLegacyConfig(raw) {
47
- if ("r2" in raw) {
48
- process.stderr.write(
49
- "[exe-os] Warning: config.json contains deprecated 'r2' field from v1.0. R2 sync has been replaced in v1.1. The 'r2' field will be ignored.\n"
50
- );
51
- delete raw.r2;
52
- }
53
- if ("syncIntervalMs" in raw) {
54
- delete raw.syncIntervalMs;
55
- }
56
- return raw;
57
- }
58
- function migrateConfig(raw) {
59
- const fromVersion = typeof raw.config_version === "number" ? raw.config_version : 0;
60
- let currentVersion = fromVersion;
61
- let migrated = false;
62
- if (currentVersion > CURRENT_CONFIG_VERSION) {
63
- return { config: raw, migrated: false, fromVersion };
64
- }
65
- for (const migration of CONFIG_MIGRATIONS) {
66
- if (currentVersion === migration.from && migration.to <= CURRENT_CONFIG_VERSION) {
67
- raw = migration.migrate(raw);
68
- currentVersion = migration.to;
69
- migrated = true;
70
- }
71
- }
72
- return { config: raw, migrated, fromVersion };
73
- }
74
- function normalizeScalingRoadmap(raw) {
75
- const defaultAuto = DEFAULT_CONFIG.scalingRoadmap.rerankerAutoTrigger;
76
- const userRoadmap = raw.scalingRoadmap ?? {};
77
- const userAuto = userRoadmap.rerankerAutoTrigger ?? {};
78
- if (userAuto.enabled === void 0 && raw.rerankerEnabled !== void 0) {
79
- userAuto.enabled = raw.rerankerEnabled;
25
+ // src/lib/database.ts
26
+ import { createClient } from "@libsql/client";
27
+ async function initDatabase(config) {
28
+ if (_client) {
29
+ _client.close();
30
+ _client = null;
80
31
  }
81
- raw.scalingRoadmap = {
82
- ...userRoadmap,
83
- rerankerAutoTrigger: { ...defaultAuto, ...userAuto }
32
+ const opts = {
33
+ url: `file:${config.dbPath}`
84
34
  };
85
- }
86
- function normalizeSessionLifecycle(raw) {
87
- const defaultSL = DEFAULT_CONFIG.sessionLifecycle;
88
- const userSL = raw.sessionLifecycle ?? {};
89
- raw.sessionLifecycle = { ...defaultSL, ...userSL };
90
- }
91
- async function loadConfig() {
92
- const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
93
- await mkdir2(dir, { recursive: true });
94
- const configPath = path2.join(dir, "config.json");
95
- if (!existsSync2(configPath)) {
96
- return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
97
- }
98
- const raw = await readFile2(configPath, "utf-8");
99
- try {
100
- let parsed = JSON.parse(raw);
101
- parsed = migrateLegacyConfig(parsed);
102
- const { config: migratedCfg, migrated, fromVersion } = migrateConfig(parsed);
103
- if (migrated) {
104
- process.stderr.write(`[exe-os] Config migrated from v${fromVersion} to v${migratedCfg.config_version}
105
- `);
106
- try {
107
- await writeFile2(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
108
- } catch {
109
- }
110
- }
111
- normalizeScalingRoadmap(migratedCfg);
112
- normalizeSessionLifecycle(migratedCfg);
113
- const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
114
- if (config.dbPath.startsWith("~")) {
115
- config.dbPath = config.dbPath.replace(/^~/, os.homedir());
116
- }
117
- return config;
118
- } catch {
119
- return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
120
- }
121
- }
122
- var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
123
- var init_config = __esm({
124
- "src/lib/config.ts"() {
125
- "use strict";
126
- EXE_AI_DIR = resolveDataDir();
127
- DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
128
- MODELS_DIR = path2.join(EXE_AI_DIR, "models");
129
- CONFIG_PATH = path2.join(EXE_AI_DIR, "config.json");
130
- LEGACY_LANCE_PATH = path2.join(EXE_AI_DIR, "local.lance");
131
- CURRENT_CONFIG_VERSION = 1;
132
- DEFAULT_CONFIG = {
133
- config_version: CURRENT_CONFIG_VERSION,
134
- dbPath: DB_PATH,
135
- modelFile: "jina-embeddings-v5-small-q4_k_m.gguf",
136
- embeddingDim: 1024,
137
- batchSize: 20,
138
- flushIntervalMs: 1e4,
139
- autoIngestion: true,
140
- autoRetrieval: true,
141
- searchMode: "hybrid",
142
- hookSearchMode: "hybrid",
143
- fileGrepEnabled: true,
144
- splashEffect: true,
145
- consolidationEnabled: true,
146
- consolidationIntervalMs: 6 * 60 * 60 * 1e3,
147
- consolidationModel: "claude-haiku-4-5-20251001",
148
- consolidationMaxCallsPerRun: 20,
149
- selfQueryRouter: true,
150
- selfQueryModel: "claude-haiku-4-5-20251001",
151
- rerankerEnabled: true,
152
- scalingRoadmap: {
153
- rerankerAutoTrigger: {
154
- enabled: true,
155
- broadQueryMinCardinality: 5e4,
156
- fetchTopK: 150,
157
- returnTopK: 5
158
- }
159
- },
160
- graphRagEnabled: true,
161
- wikiEnabled: false,
162
- wikiUrl: "",
163
- wikiApiKey: "",
164
- wikiSyncIntervalMs: 30 * 60 * 1e3,
165
- wikiWorkspaceMapping: {
166
- exe: "Executive",
167
- yoshi: "Engineering",
168
- mari: "Marketing",
169
- tom: "Engineering",
170
- sasha: "Production"
171
- },
172
- wikiAutoUpdate: true,
173
- wikiAutoUpdateThreshold: 0.5,
174
- wikiAutoUpdateCreateNew: true,
175
- skillLearning: true,
176
- skillThreshold: 3,
177
- skillModel: "claude-haiku-4-5-20251001",
178
- exeHeartbeat: {
179
- enabled: true,
180
- intervalSeconds: 60,
181
- staleInProgressThresholdHours: 2
182
- },
183
- sessionLifecycle: {
184
- idleKillEnabled: true,
185
- idleKillTicksRequired: 3,
186
- idleKillIntercomAckWindowMs: 1e4,
187
- maxAutoInstances: 10
188
- }
189
- };
190
- CONFIG_MIGRATIONS = [
191
- {
192
- from: 0,
193
- to: 1,
194
- migrate: (cfg) => {
195
- cfg.config_version = 1;
196
- return cfg;
197
- }
198
- }
199
- ];
200
- }
201
- });
202
-
203
- // src/lib/shard-manager.ts
204
- var shard_manager_exports = {};
205
- __export(shard_manager_exports, {
206
- disposeShards: () => disposeShards,
207
- ensureShardSchema: () => ensureShardSchema,
208
- getReadyShardClient: () => getReadyShardClient,
209
- getShardClient: () => getShardClient,
210
- getShardsDir: () => getShardsDir,
211
- initShardManager: () => initShardManager,
212
- isShardingEnabled: () => isShardingEnabled,
213
- listShards: () => listShards,
214
- shardExists: () => shardExists
215
- });
216
- import path3 from "path";
217
- import { existsSync as existsSync3, mkdirSync } from "fs";
218
- import { createClient as createClient2 } from "@libsql/client";
219
- function initShardManager(encryptionKey) {
220
- _encryptionKey = encryptionKey;
221
- if (!existsSync3(SHARDS_DIR)) {
222
- mkdirSync(SHARDS_DIR, { recursive: true });
35
+ if (config.encryptionKey) {
36
+ opts.encryptionKey = config.encryptionKey;
223
37
  }
224
- _shardingEnabled = true;
225
- }
226
- function isShardingEnabled() {
227
- return _shardingEnabled;
228
- }
229
- function getShardsDir() {
230
- return SHARDS_DIR;
38
+ _client = createClient(opts);
231
39
  }
232
- function getShardClient(projectName) {
233
- if (!_encryptionKey) {
234
- throw new Error("Shard manager not initialized. Call initShardManager() first.");
235
- }
236
- const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
237
- if (!safeName) {
238
- throw new Error(`Invalid project name for shard: "${projectName}"`);
40
+ function getClient() {
41
+ if (!_client) {
42
+ throw new Error("Database client not initialized. Call initDatabase() first.");
239
43
  }
240
- const cached = _shards.get(safeName);
241
- if (cached) return cached;
242
- const dbPath = path3.join(SHARDS_DIR, `${safeName}.db`);
243
- const client = createClient2({
244
- url: `file:${dbPath}`,
245
- encryptionKey: _encryptionKey
246
- });
247
- _shards.set(safeName, client);
248
- return client;
249
- }
250
- function shardExists(projectName) {
251
- const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
252
- return existsSync3(path3.join(SHARDS_DIR, `${safeName}.db`));
253
- }
254
- function listShards() {
255
- if (!existsSync3(SHARDS_DIR)) return [];
256
- const { readdirSync } = __require("fs");
257
- return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
44
+ return _client;
258
45
  }
259
- async function ensureShardSchema(client) {
260
- await client.execute("PRAGMA journal_mode = WAL");
261
- await client.execute("PRAGMA busy_timeout = 5000");
262
- try {
263
- await client.execute("PRAGMA libsql_vector_search_ef = 128");
264
- } catch {
265
- }
266
- await client.executeMultiple(`
267
- CREATE TABLE IF NOT EXISTS memories (
268
- id TEXT PRIMARY KEY,
269
- agent_id TEXT NOT NULL,
270
- agent_role TEXT NOT NULL,
271
- session_id TEXT NOT NULL,
272
- timestamp TEXT NOT NULL,
273
- tool_name TEXT NOT NULL,
274
- project_name TEXT NOT NULL,
275
- has_error INTEGER NOT NULL DEFAULT 0,
276
- raw_text TEXT NOT NULL,
277
- vector F32_BLOB(1024),
278
- version INTEGER NOT NULL DEFAULT 0
279
- );
280
-
281
- CREATE INDEX IF NOT EXISTS idx_memories_agent ON memories(agent_id);
282
- CREATE INDEX IF NOT EXISTS idx_memories_timestamp ON memories(timestamp);
283
- CREATE INDEX IF NOT EXISTS idx_memories_agent_project ON memories(agent_id, project_name);
284
- `);
285
- await client.executeMultiple(`
286
- CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
287
- raw_text,
288
- content='memories',
289
- content_rowid='rowid'
290
- );
291
-
292
- CREATE TRIGGER IF NOT EXISTS memories_fts_ai AFTER INSERT ON memories BEGIN
293
- INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
294
- END;
295
-
296
- CREATE TRIGGER IF NOT EXISTS memories_fts_ad AFTER DELETE ON memories BEGIN
297
- INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
298
- END;
299
-
300
- CREATE TRIGGER IF NOT EXISTS memories_fts_au AFTER UPDATE ON memories BEGIN
301
- INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
302
- INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
303
- END;
304
- `);
305
- for (const col of [
306
- "ALTER TABLE memories ADD COLUMN task_id TEXT",
307
- "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
308
- "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
309
- "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
310
- "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
311
- "ALTER TABLE memories ADD COLUMN graph_extracted INTEGER DEFAULT 0",
312
- "ALTER TABLE memories ADD COLUMN content_hash TEXT",
313
- "ALTER TABLE memories ADD COLUMN graph_extracted_hash TEXT",
314
- "ALTER TABLE memories ADD COLUMN confidence REAL DEFAULT 0.7",
315
- "ALTER TABLE memories ADD COLUMN last_accessed TEXT",
316
- // Wiki linkage columns (must match database.ts)
317
- "ALTER TABLE memories ADD COLUMN workspace_id TEXT",
318
- "ALTER TABLE memories ADD COLUMN document_id TEXT",
319
- "ALTER TABLE memories ADD COLUMN user_id TEXT",
320
- "ALTER TABLE memories ADD COLUMN char_offset INTEGER",
321
- "ALTER TABLE memories ADD COLUMN page_number INTEGER"
322
- ]) {
323
- try {
324
- await client.execute(col);
325
- } catch {
326
- }
327
- }
328
- try {
329
- await client.execute("CREATE INDEX IF NOT EXISTS idx_memories_status ON memories(status)");
330
- } catch {
331
- }
332
- for (const idx of [
333
- "CREATE INDEX IF NOT EXISTS idx_memories_workspace ON memories(workspace_id)",
334
- "CREATE INDEX IF NOT EXISTS idx_memories_document ON memories(document_id)",
335
- "CREATE INDEX IF NOT EXISTS idx_memories_user ON memories(user_id)"
336
- ]) {
337
- try {
338
- await client.execute(idx);
339
- } catch {
340
- }
341
- }
342
- await client.executeMultiple(`
343
- CREATE TABLE IF NOT EXISTS entities (
344
- id TEXT PRIMARY KEY,
345
- name TEXT NOT NULL,
346
- type TEXT NOT NULL,
347
- first_seen TEXT NOT NULL,
348
- last_seen TEXT NOT NULL,
349
- properties TEXT DEFAULT '{}',
350
- UNIQUE(name, type)
351
- );
352
-
353
- CREATE TABLE IF NOT EXISTS relationships (
354
- id TEXT PRIMARY KEY,
355
- source_entity_id TEXT NOT NULL,
356
- target_entity_id TEXT NOT NULL,
357
- type TEXT NOT NULL,
358
- weight REAL DEFAULT 1.0,
359
- timestamp TEXT NOT NULL,
360
- properties TEXT DEFAULT '{}',
361
- UNIQUE(source_entity_id, target_entity_id, type)
362
- );
363
-
364
- CREATE TABLE IF NOT EXISTS entity_memories (
365
- entity_id TEXT NOT NULL,
366
- memory_id TEXT NOT NULL,
367
- PRIMARY KEY (entity_id, memory_id)
368
- );
369
-
370
- CREATE TABLE IF NOT EXISTS relationship_memories (
371
- relationship_id TEXT NOT NULL,
372
- memory_id TEXT NOT NULL,
373
- PRIMARY KEY (relationship_id, memory_id)
374
- );
375
-
376
- CREATE INDEX IF NOT EXISTS idx_entities_name ON entities(name);
377
- CREATE INDEX IF NOT EXISTS idx_entities_type ON entities(type);
378
- CREATE INDEX IF NOT EXISTS idx_relationships_source ON relationships(source_entity_id);
379
- CREATE INDEX IF NOT EXISTS idx_relationships_target ON relationships(target_entity_id);
380
- CREATE INDEX IF NOT EXISTS idx_relationships_type ON relationships(type);
381
-
382
- CREATE TABLE IF NOT EXISTS hyperedges (
383
- id TEXT PRIMARY KEY,
384
- label TEXT NOT NULL,
385
- relation TEXT NOT NULL,
386
- confidence REAL DEFAULT 1.0,
387
- timestamp TEXT NOT NULL
388
- );
389
-
390
- CREATE TABLE IF NOT EXISTS hyperedge_nodes (
391
- hyperedge_id TEXT NOT NULL,
392
- entity_id TEXT NOT NULL,
393
- PRIMARY KEY (hyperedge_id, entity_id)
394
- );
395
- `);
396
- for (const col of [
397
- "ALTER TABLE relationships ADD COLUMN confidence REAL DEFAULT 1.0",
398
- "ALTER TABLE relationships ADD COLUMN confidence_label TEXT DEFAULT 'extracted'"
399
- ]) {
400
- try {
401
- await client.execute(col);
402
- } catch {
403
- }
404
- }
405
- }
406
- async function getReadyShardClient(projectName) {
407
- const client = getShardClient(projectName);
408
- await ensureShardSchema(client);
409
- return client;
410
- }
411
- function disposeShards() {
412
- for (const [, client] of _shards) {
413
- client.close();
414
- }
415
- _shards.clear();
416
- _shardingEnabled = false;
417
- _encryptionKey = null;
418
- }
419
- var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
420
- var init_shard_manager = __esm({
421
- "src/lib/shard-manager.ts"() {
422
- "use strict";
423
- init_config();
424
- SHARDS_DIR = path3.join(EXE_AI_DIR, "shards");
425
- _shards = /* @__PURE__ */ new Map();
426
- _encryptionKey = null;
427
- _shardingEnabled = false;
428
- }
429
- });
430
-
431
- // src/bin/exe-forget.ts
432
- import { createInterface } from "readline";
433
-
434
- // src/lib/store.ts
435
- init_memory();
436
-
437
- // src/lib/database.ts
438
- import { createClient } from "@libsql/client";
439
- var _client = null;
440
- var initTurso = initDatabase;
441
- async function initDatabase(config) {
442
- if (_client) {
443
- _client.close();
444
- _client = null;
445
- }
446
- const opts = {
447
- url: `file:${config.dbPath}`
448
- };
449
- if (config.encryptionKey) {
450
- opts.encryptionKey = config.encryptionKey;
451
- }
452
- _client = createClient(opts);
453
- }
454
- function getClient() {
455
- if (!_client) {
456
- throw new Error("Database client not initialized. Call initDatabase() first.");
457
- }
458
- return _client;
459
- }
460
- async function ensureSchema() {
461
- const client = getClient();
46
+ async function ensureSchema() {
47
+ const client = getClient();
462
48
  await client.execute("PRAGMA journal_mode = WAL");
463
49
  await client.execute("PRAGMA busy_timeout = 5000");
464
50
  try {
@@ -644,6 +230,27 @@ async function ensureSchema() {
644
230
  });
645
231
  } catch {
646
232
  }
233
+ try {
234
+ await client.execute({
235
+ sql: `ALTER TABLE tasks ADD COLUMN checkpoint TEXT`,
236
+ args: []
237
+ });
238
+ } catch {
239
+ }
240
+ try {
241
+ await client.execute({
242
+ sql: `ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER NOT NULL DEFAULT 0`,
243
+ args: []
244
+ });
245
+ } catch {
246
+ }
247
+ try {
248
+ await client.execute({
249
+ sql: `ALTER TABLE tasks ADD COLUMN complexity TEXT NOT NULL DEFAULT 'standard'`,
250
+ args: []
251
+ });
252
+ } catch {
253
+ }
647
254
  try {
648
255
  await client.execute({
649
256
  sql: `ALTER TABLE memories ADD COLUMN task_id TEXT`,
@@ -891,18 +498,740 @@ async function ensureSchema() {
891
498
  } catch {
892
499
  }
893
500
  try {
894
- await client.execute({
895
- sql: `ALTER TABLE memories ADD COLUMN graph_extracted INTEGER DEFAULT 0`,
896
- args: []
897
- });
501
+ await client.execute({
502
+ sql: `ALTER TABLE memories ADD COLUMN graph_extracted INTEGER DEFAULT 0`,
503
+ args: []
504
+ });
505
+ } catch {
506
+ }
507
+ for (const col of [
508
+ "ALTER TABLE memories ADD COLUMN content_hash TEXT",
509
+ "ALTER TABLE memories ADD COLUMN graph_extracted_hash TEXT"
510
+ ]) {
511
+ try {
512
+ await client.execute(col);
513
+ } catch {
514
+ }
515
+ }
516
+ await client.executeMultiple(`
517
+ CREATE TABLE IF NOT EXISTS entities (
518
+ id TEXT PRIMARY KEY,
519
+ name TEXT NOT NULL,
520
+ type TEXT NOT NULL,
521
+ first_seen TEXT NOT NULL,
522
+ last_seen TEXT NOT NULL,
523
+ properties TEXT DEFAULT '{}',
524
+ UNIQUE(name, type)
525
+ );
526
+
527
+ CREATE TABLE IF NOT EXISTS relationships (
528
+ id TEXT PRIMARY KEY,
529
+ source_entity_id TEXT NOT NULL,
530
+ target_entity_id TEXT NOT NULL,
531
+ type TEXT NOT NULL,
532
+ weight REAL DEFAULT 1.0,
533
+ timestamp TEXT NOT NULL,
534
+ properties TEXT DEFAULT '{}',
535
+ UNIQUE(source_entity_id, target_entity_id, type)
536
+ );
537
+
538
+ CREATE TABLE IF NOT EXISTS entity_memories (
539
+ entity_id TEXT NOT NULL,
540
+ memory_id TEXT NOT NULL,
541
+ PRIMARY KEY (entity_id, memory_id)
542
+ );
543
+
544
+ CREATE TABLE IF NOT EXISTS relationship_memories (
545
+ relationship_id TEXT NOT NULL,
546
+ memory_id TEXT NOT NULL,
547
+ PRIMARY KEY (relationship_id, memory_id)
548
+ );
549
+
550
+ CREATE INDEX IF NOT EXISTS idx_entities_name ON entities(name);
551
+ CREATE INDEX IF NOT EXISTS idx_entities_type ON entities(type);
552
+ CREATE INDEX IF NOT EXISTS idx_relationships_source ON relationships(source_entity_id);
553
+ CREATE INDEX IF NOT EXISTS idx_relationships_target ON relationships(target_entity_id);
554
+
555
+ CREATE TABLE IF NOT EXISTS hyperedges (
556
+ id TEXT PRIMARY KEY,
557
+ label TEXT NOT NULL,
558
+ relation TEXT NOT NULL,
559
+ confidence REAL DEFAULT 1.0,
560
+ timestamp TEXT NOT NULL
561
+ );
562
+
563
+ CREATE TABLE IF NOT EXISTS hyperedge_nodes (
564
+ hyperedge_id TEXT NOT NULL,
565
+ entity_id TEXT NOT NULL,
566
+ PRIMARY KEY (hyperedge_id, entity_id)
567
+ );
568
+ `);
569
+ await client.executeMultiple(`
570
+ CREATE TABLE IF NOT EXISTS entity_aliases (
571
+ alias TEXT NOT NULL PRIMARY KEY,
572
+ canonical_entity_id TEXT NOT NULL
573
+ );
574
+ CREATE INDEX IF NOT EXISTS idx_entity_aliases_canonical ON entity_aliases(canonical_entity_id);
575
+ `);
576
+ for (const col of [
577
+ "ALTER TABLE relationships ADD COLUMN confidence REAL DEFAULT 1.0",
578
+ "ALTER TABLE relationships ADD COLUMN confidence_label TEXT DEFAULT 'extracted'"
579
+ ]) {
580
+ try {
581
+ await client.execute(col);
582
+ } catch {
583
+ }
584
+ }
585
+ try {
586
+ await client.execute(
587
+ `CREATE INDEX IF NOT EXISTS idx_memories_status ON memories(status)`
588
+ );
589
+ } catch {
590
+ }
591
+ await client.executeMultiple(`
592
+ CREATE TABLE IF NOT EXISTS identity (
593
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
594
+ agent_id TEXT NOT NULL UNIQUE,
595
+ content_hash TEXT NOT NULL,
596
+ updated_at TEXT NOT NULL,
597
+ updated_by TEXT NOT NULL
598
+ );
599
+
600
+ CREATE INDEX IF NOT EXISTS idx_identity_agent ON identity(agent_id);
601
+ `);
602
+ await client.executeMultiple(`
603
+ CREATE TABLE IF NOT EXISTS chat_history (
604
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
605
+ session_id TEXT NOT NULL,
606
+ role TEXT NOT NULL,
607
+ content TEXT NOT NULL,
608
+ tool_name TEXT,
609
+ tool_id TEXT,
610
+ is_error INTEGER NOT NULL DEFAULT 0,
611
+ timestamp INTEGER NOT NULL
612
+ );
613
+
614
+ CREATE INDEX IF NOT EXISTS idx_chat_history_session
615
+ ON chat_history(session_id, id);
616
+ `);
617
+ await client.executeMultiple(`
618
+ CREATE TABLE IF NOT EXISTS workspaces (
619
+ id TEXT PRIMARY KEY,
620
+ slug TEXT NOT NULL UNIQUE,
621
+ name TEXT NOT NULL,
622
+ owner_agent_id TEXT,
623
+ created_at TEXT NOT NULL,
624
+ metadata TEXT
625
+ );
626
+
627
+ CREATE INDEX IF NOT EXISTS idx_workspaces_slug
628
+ ON workspaces(slug);
629
+ `);
630
+ await client.executeMultiple(`
631
+ CREATE TABLE IF NOT EXISTS documents (
632
+ id TEXT PRIMARY KEY,
633
+ workspace_id TEXT NOT NULL,
634
+ filename TEXT NOT NULL,
635
+ mime TEXT,
636
+ source_type TEXT,
637
+ user_id TEXT,
638
+ uploaded_at TEXT NOT NULL,
639
+ metadata TEXT,
640
+ FOREIGN KEY (workspace_id) REFERENCES workspaces(id)
641
+ );
642
+
643
+ CREATE INDEX IF NOT EXISTS idx_documents_workspace
644
+ ON documents(workspace_id);
645
+
646
+ CREATE INDEX IF NOT EXISTS idx_documents_user
647
+ ON documents(user_id);
648
+ `);
649
+ for (const column of [
650
+ "workspace_id TEXT",
651
+ "document_id TEXT",
652
+ "user_id TEXT",
653
+ "char_offset INTEGER",
654
+ "page_number INTEGER"
655
+ ]) {
656
+ try {
657
+ await client.execute({
658
+ sql: `ALTER TABLE memories ADD COLUMN ${column}`,
659
+ args: []
660
+ });
661
+ } catch {
662
+ }
663
+ }
664
+ for (const col of [
665
+ "ALTER TABLE memories ADD COLUMN source_path TEXT",
666
+ "ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'"
667
+ ]) {
668
+ try {
669
+ await client.execute(col);
670
+ } catch {
671
+ }
672
+ }
673
+ await client.executeMultiple(`
674
+ CREATE INDEX IF NOT EXISTS idx_memories_workspace
675
+ ON memories(workspace_id);
676
+
677
+ CREATE INDEX IF NOT EXISTS idx_memories_document
678
+ ON memories(document_id);
679
+
680
+ CREATE INDEX IF NOT EXISTS idx_memories_user
681
+ ON memories(user_id);
682
+ `);
683
+ await client.executeMultiple(`
684
+ CREATE TABLE IF NOT EXISTS session_kills (
685
+ id TEXT PRIMARY KEY,
686
+ session_name TEXT NOT NULL,
687
+ agent_id TEXT NOT NULL,
688
+ killed_at TIMESTAMP NOT NULL,
689
+ reason TEXT NOT NULL,
690
+ ticks_idle INTEGER,
691
+ estimated_tokens_saved INTEGER
692
+ );
693
+
694
+ CREATE INDEX IF NOT EXISTS idx_session_kills_killed_at
695
+ ON session_kills(killed_at);
696
+
697
+ CREATE INDEX IF NOT EXISTS idx_session_kills_agent
698
+ ON session_kills(agent_id);
699
+ `);
700
+ await client.executeMultiple(`
701
+ CREATE TABLE IF NOT EXISTS conversations (
702
+ id TEXT PRIMARY KEY,
703
+ platform TEXT NOT NULL,
704
+ external_id TEXT,
705
+ sender_id TEXT NOT NULL,
706
+ sender_name TEXT,
707
+ sender_phone TEXT,
708
+ sender_email TEXT,
709
+ recipient_id TEXT,
710
+ channel_id TEXT NOT NULL,
711
+ thread_id TEXT,
712
+ reply_to_id TEXT,
713
+ content_text TEXT,
714
+ content_media TEXT,
715
+ content_metadata TEXT,
716
+ agent_response TEXT,
717
+ agent_name TEXT,
718
+ timestamp TEXT NOT NULL,
719
+ ingested_at TEXT NOT NULL
720
+ );
721
+
722
+ CREATE INDEX IF NOT EXISTS idx_conversations_platform
723
+ ON conversations(platform);
724
+
725
+ CREATE INDEX IF NOT EXISTS idx_conversations_sender
726
+ ON conversations(sender_id);
727
+
728
+ CREATE INDEX IF NOT EXISTS idx_conversations_timestamp
729
+ ON conversations(timestamp);
730
+
731
+ CREATE INDEX IF NOT EXISTS idx_conversations_thread
732
+ ON conversations(thread_id);
733
+
734
+ CREATE INDEX IF NOT EXISTS idx_conversations_channel
735
+ ON conversations(channel_id);
736
+ `);
737
+ try {
738
+ await client.execute({
739
+ sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
740
+ args: []
741
+ });
742
+ } catch {
743
+ }
744
+ try {
745
+ await client.execute({
746
+ sql: `ALTER TABLE tasks ADD COLUMN budget_fallback_model TEXT`,
747
+ args: []
748
+ });
749
+ } catch {
750
+ }
751
+ try {
752
+ await client.execute({
753
+ sql: `ALTER TABLE tasks ADD COLUMN tokens_used INTEGER DEFAULT 0`,
754
+ args: []
755
+ });
756
+ } catch {
757
+ }
758
+ try {
759
+ await client.execute({
760
+ sql: `ALTER TABLE tasks ADD COLUMN tokens_warned_at INTEGER`,
761
+ args: []
762
+ });
763
+ } catch {
764
+ }
765
+ await client.executeMultiple(`
766
+ CREATE VIRTUAL TABLE IF NOT EXISTS conversations_fts USING fts5(
767
+ content_text,
768
+ sender_name,
769
+ agent_response,
770
+ content='conversations',
771
+ content_rowid='rowid'
772
+ );
773
+
774
+ CREATE TRIGGER IF NOT EXISTS conversations_fts_ai AFTER INSERT ON conversations BEGIN
775
+ INSERT INTO conversations_fts(rowid, content_text, sender_name, agent_response)
776
+ VALUES (new.rowid, new.content_text, new.sender_name, new.agent_response);
777
+ END;
778
+
779
+ CREATE TRIGGER IF NOT EXISTS conversations_fts_ad AFTER DELETE ON conversations BEGIN
780
+ INSERT INTO conversations_fts(conversations_fts, rowid, content_text, sender_name, agent_response)
781
+ VALUES('delete', old.rowid, old.content_text, old.sender_name, old.agent_response);
782
+ END;
783
+
784
+ CREATE TRIGGER IF NOT EXISTS conversations_fts_au AFTER UPDATE ON conversations BEGIN
785
+ INSERT INTO conversations_fts(conversations_fts, rowid, content_text, sender_name, agent_response)
786
+ VALUES('delete', old.rowid, old.content_text, old.sender_name, old.agent_response);
787
+ INSERT INTO conversations_fts(rowid, content_text, sender_name, agent_response)
788
+ VALUES (new.rowid, new.content_text, new.sender_name, new.agent_response);
789
+ END;
790
+ `);
791
+ try {
792
+ await client.execute({
793
+ sql: `ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3`,
794
+ args: []
795
+ });
796
+ } catch {
797
+ }
798
+ try {
799
+ await client.execute(
800
+ `CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)`
801
+ );
802
+ } catch {
803
+ }
804
+ try {
805
+ await client.execute({
806
+ sql: `UPDATE memories SET tier = 1 WHERE tool_name = 'commit_to_long_term_memory' AND importance >= 8 AND tier = 3`,
807
+ args: []
808
+ });
809
+ await client.execute({
810
+ sql: `UPDATE memories SET tier = 2 WHERE tool_name IN ('store_memory', 'manual') AND importance >= 5 AND tier = 3`,
811
+ args: []
812
+ });
813
+ } catch {
814
+ }
815
+ try {
816
+ await client.execute({
817
+ sql: `ALTER TABLE memories ADD COLUMN supersedes_id TEXT`,
818
+ args: []
819
+ });
820
+ } catch {
821
+ }
822
+ try {
823
+ await client.execute(
824
+ `CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL`
825
+ );
826
+ } catch {
827
+ }
828
+ for (const col of [
829
+ "ALTER TABLE tasks ADD COLUMN checkpoint TEXT",
830
+ "ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER DEFAULT 0"
831
+ ]) {
832
+ try {
833
+ await client.execute(col);
834
+ } catch {
835
+ }
836
+ }
837
+ }
838
+ var _client, initTurso;
839
+ var init_database = __esm({
840
+ "src/lib/database.ts"() {
841
+ "use strict";
842
+ _client = null;
843
+ initTurso = initDatabase;
844
+ }
845
+ });
846
+
847
+ // src/lib/keychain.ts
848
+ import { readFile, writeFile, unlink, mkdir, chmod } from "fs/promises";
849
+ import { existsSync } from "fs";
850
+ import path from "path";
851
+ import crypto from "crypto";
852
+ function getKeyDir() {
853
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path.join(process.env.HOME ?? "/tmp", ".exe-os");
854
+ }
855
+ function getKeyPath() {
856
+ return path.join(getKeyDir(), "master.key");
857
+ }
858
+ async function tryKeytar() {
859
+ try {
860
+ return await import("keytar");
861
+ } catch {
862
+ return null;
863
+ }
864
+ }
865
+ async function getMasterKey() {
866
+ const keytar = await tryKeytar();
867
+ if (keytar) {
868
+ try {
869
+ const stored = await keytar.getPassword(SERVICE, ACCOUNT);
870
+ if (stored) {
871
+ return Buffer.from(stored, "base64");
872
+ }
873
+ } catch {
874
+ }
875
+ }
876
+ const keyPath = getKeyPath();
877
+ if (!existsSync(keyPath)) {
878
+ return null;
879
+ }
880
+ try {
881
+ const content = await readFile(keyPath, "utf-8");
882
+ return Buffer.from(content.trim(), "base64");
883
+ } catch {
884
+ return null;
885
+ }
886
+ }
887
+ var SERVICE, ACCOUNT;
888
+ var init_keychain = __esm({
889
+ "src/lib/keychain.ts"() {
890
+ "use strict";
891
+ SERVICE = "exe-mem";
892
+ ACCOUNT = "master-key";
893
+ }
894
+ });
895
+
896
+ // src/lib/config.ts
897
+ import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
898
+ import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
899
+ import path2 from "path";
900
+ import os from "os";
901
+ function resolveDataDir() {
902
+ if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
903
+ if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
904
+ const newDir = path2.join(os.homedir(), ".exe-os");
905
+ const legacyDir = path2.join(os.homedir(), ".exe-mem");
906
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
907
+ try {
908
+ renameSync(legacyDir, newDir);
909
+ process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
910
+ `);
911
+ } catch {
912
+ return legacyDir;
913
+ }
914
+ }
915
+ return newDir;
916
+ }
917
+ function migrateLegacyConfig(raw) {
918
+ if ("r2" in raw) {
919
+ process.stderr.write(
920
+ "[exe-os] Warning: config.json contains deprecated 'r2' field from v1.0. R2 sync has been replaced in v1.1. The 'r2' field will be ignored.\n"
921
+ );
922
+ delete raw.r2;
923
+ }
924
+ if ("syncIntervalMs" in raw) {
925
+ delete raw.syncIntervalMs;
926
+ }
927
+ return raw;
928
+ }
929
+ function migrateConfig(raw) {
930
+ const fromVersion = typeof raw.config_version === "number" ? raw.config_version : 0;
931
+ let currentVersion = fromVersion;
932
+ let migrated = false;
933
+ if (currentVersion > CURRENT_CONFIG_VERSION) {
934
+ return { config: raw, migrated: false, fromVersion };
935
+ }
936
+ for (const migration of CONFIG_MIGRATIONS) {
937
+ if (currentVersion === migration.from && migration.to <= CURRENT_CONFIG_VERSION) {
938
+ raw = migration.migrate(raw);
939
+ currentVersion = migration.to;
940
+ migrated = true;
941
+ }
942
+ }
943
+ return { config: raw, migrated, fromVersion };
944
+ }
945
+ function normalizeScalingRoadmap(raw) {
946
+ const defaultAuto = DEFAULT_CONFIG.scalingRoadmap.rerankerAutoTrigger;
947
+ const userRoadmap = raw.scalingRoadmap ?? {};
948
+ const userAuto = userRoadmap.rerankerAutoTrigger ?? {};
949
+ if (userAuto.enabled === void 0 && raw.rerankerEnabled !== void 0) {
950
+ userAuto.enabled = raw.rerankerEnabled;
951
+ }
952
+ raw.scalingRoadmap = {
953
+ ...userRoadmap,
954
+ rerankerAutoTrigger: { ...defaultAuto, ...userAuto }
955
+ };
956
+ }
957
+ function normalizeSessionLifecycle(raw) {
958
+ const defaultSL = DEFAULT_CONFIG.sessionLifecycle;
959
+ const userSL = raw.sessionLifecycle ?? {};
960
+ raw.sessionLifecycle = { ...defaultSL, ...userSL };
961
+ }
962
+ function normalizeAutoUpdate(raw) {
963
+ const defaultAU = DEFAULT_CONFIG.autoUpdate;
964
+ const userAU = raw.autoUpdate ?? {};
965
+ raw.autoUpdate = { ...defaultAU, ...userAU };
966
+ }
967
+ async function loadConfig() {
968
+ const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
969
+ await mkdir2(dir, { recursive: true });
970
+ const configPath = path2.join(dir, "config.json");
971
+ if (!existsSync2(configPath)) {
972
+ return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
973
+ }
974
+ const raw = await readFile2(configPath, "utf-8");
975
+ try {
976
+ let parsed = JSON.parse(raw);
977
+ parsed = migrateLegacyConfig(parsed);
978
+ const { config: migratedCfg, migrated, fromVersion } = migrateConfig(parsed);
979
+ if (migrated) {
980
+ process.stderr.write(`[exe-os] Config migrated from v${fromVersion} to v${migratedCfg.config_version}
981
+ `);
982
+ try {
983
+ await writeFile2(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
984
+ } catch {
985
+ }
986
+ }
987
+ normalizeScalingRoadmap(migratedCfg);
988
+ normalizeSessionLifecycle(migratedCfg);
989
+ normalizeAutoUpdate(migratedCfg);
990
+ const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
991
+ if (config.dbPath.startsWith("~")) {
992
+ config.dbPath = config.dbPath.replace(/^~/, os.homedir());
993
+ }
994
+ return config;
995
+ } catch {
996
+ return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
997
+ }
998
+ }
999
+ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
1000
+ var init_config = __esm({
1001
+ "src/lib/config.ts"() {
1002
+ "use strict";
1003
+ EXE_AI_DIR = resolveDataDir();
1004
+ DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
1005
+ MODELS_DIR = path2.join(EXE_AI_DIR, "models");
1006
+ CONFIG_PATH = path2.join(EXE_AI_DIR, "config.json");
1007
+ LEGACY_LANCE_PATH = path2.join(EXE_AI_DIR, "local.lance");
1008
+ CURRENT_CONFIG_VERSION = 1;
1009
+ DEFAULT_CONFIG = {
1010
+ config_version: CURRENT_CONFIG_VERSION,
1011
+ dbPath: DB_PATH,
1012
+ modelFile: "jina-embeddings-v5-small-q4_k_m.gguf",
1013
+ embeddingDim: 1024,
1014
+ batchSize: 20,
1015
+ flushIntervalMs: 1e4,
1016
+ autoIngestion: true,
1017
+ autoRetrieval: true,
1018
+ searchMode: "hybrid",
1019
+ hookSearchMode: "hybrid",
1020
+ fileGrepEnabled: true,
1021
+ splashEffect: true,
1022
+ consolidationEnabled: true,
1023
+ consolidationIntervalMs: 6 * 60 * 60 * 1e3,
1024
+ consolidationModel: "claude-haiku-4-5-20251001",
1025
+ consolidationMaxCallsPerRun: 20,
1026
+ selfQueryRouter: true,
1027
+ selfQueryModel: "claude-haiku-4-5-20251001",
1028
+ rerankerEnabled: true,
1029
+ scalingRoadmap: {
1030
+ rerankerAutoTrigger: {
1031
+ enabled: true,
1032
+ broadQueryMinCardinality: 5e4,
1033
+ fetchTopK: 150,
1034
+ returnTopK: 5
1035
+ }
1036
+ },
1037
+ graphRagEnabled: true,
1038
+ wikiEnabled: false,
1039
+ wikiUrl: "",
1040
+ wikiApiKey: "",
1041
+ wikiSyncIntervalMs: 30 * 60 * 1e3,
1042
+ wikiWorkspaceMapping: {
1043
+ exe: "Executive",
1044
+ yoshi: "Engineering",
1045
+ mari: "Marketing",
1046
+ tom: "Engineering",
1047
+ sasha: "Production"
1048
+ },
1049
+ wikiAutoUpdate: true,
1050
+ wikiAutoUpdateThreshold: 0.5,
1051
+ wikiAutoUpdateCreateNew: true,
1052
+ skillLearning: true,
1053
+ skillThreshold: 3,
1054
+ skillModel: "claude-haiku-4-5-20251001",
1055
+ exeHeartbeat: {
1056
+ enabled: true,
1057
+ intervalSeconds: 60,
1058
+ staleInProgressThresholdHours: 2
1059
+ },
1060
+ sessionLifecycle: {
1061
+ idleKillEnabled: true,
1062
+ idleKillTicksRequired: 3,
1063
+ idleKillIntercomAckWindowMs: 1e4,
1064
+ maxAutoInstances: 10
1065
+ },
1066
+ autoUpdate: {
1067
+ checkOnBoot: true,
1068
+ autoInstall: false,
1069
+ checkIntervalMs: 24 * 60 * 60 * 1e3
1070
+ }
1071
+ };
1072
+ CONFIG_MIGRATIONS = [
1073
+ {
1074
+ from: 0,
1075
+ to: 1,
1076
+ migrate: (cfg) => {
1077
+ cfg.config_version = 1;
1078
+ return cfg;
1079
+ }
1080
+ }
1081
+ ];
1082
+ }
1083
+ });
1084
+
1085
+ // src/lib/shard-manager.ts
1086
+ var shard_manager_exports = {};
1087
+ __export(shard_manager_exports, {
1088
+ disposeShards: () => disposeShards,
1089
+ ensureShardSchema: () => ensureShardSchema,
1090
+ getReadyShardClient: () => getReadyShardClient,
1091
+ getShardClient: () => getShardClient,
1092
+ getShardsDir: () => getShardsDir,
1093
+ initShardManager: () => initShardManager,
1094
+ isShardingEnabled: () => isShardingEnabled,
1095
+ listShards: () => listShards,
1096
+ shardExists: () => shardExists
1097
+ });
1098
+ import path3 from "path";
1099
+ import { existsSync as existsSync3, mkdirSync } from "fs";
1100
+ import { createClient as createClient2 } from "@libsql/client";
1101
+ function initShardManager(encryptionKey) {
1102
+ _encryptionKey = encryptionKey;
1103
+ if (!existsSync3(SHARDS_DIR)) {
1104
+ mkdirSync(SHARDS_DIR, { recursive: true });
1105
+ }
1106
+ _shardingEnabled = true;
1107
+ }
1108
+ function isShardingEnabled() {
1109
+ return _shardingEnabled;
1110
+ }
1111
+ function getShardsDir() {
1112
+ return SHARDS_DIR;
1113
+ }
1114
+ function getShardClient(projectName) {
1115
+ if (!_encryptionKey) {
1116
+ throw new Error("Shard manager not initialized. Call initShardManager() first.");
1117
+ }
1118
+ const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
1119
+ if (!safeName) {
1120
+ throw new Error(`Invalid project name for shard: "${projectName}"`);
1121
+ }
1122
+ const cached = _shards.get(safeName);
1123
+ if (cached) return cached;
1124
+ const dbPath = path3.join(SHARDS_DIR, `${safeName}.db`);
1125
+ const client = createClient2({
1126
+ url: `file:${dbPath}`,
1127
+ encryptionKey: _encryptionKey
1128
+ });
1129
+ _shards.set(safeName, client);
1130
+ return client;
1131
+ }
1132
+ function shardExists(projectName) {
1133
+ const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
1134
+ return existsSync3(path3.join(SHARDS_DIR, `${safeName}.db`));
1135
+ }
1136
+ function listShards() {
1137
+ if (!existsSync3(SHARDS_DIR)) return [];
1138
+ const { readdirSync } = __require("fs");
1139
+ return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
1140
+ }
1141
+ async function ensureShardSchema(client) {
1142
+ await client.execute("PRAGMA journal_mode = WAL");
1143
+ await client.execute("PRAGMA busy_timeout = 5000");
1144
+ try {
1145
+ await client.execute("PRAGMA libsql_vector_search_ef = 128");
1146
+ } catch {
1147
+ }
1148
+ await client.executeMultiple(`
1149
+ CREATE TABLE IF NOT EXISTS memories (
1150
+ id TEXT PRIMARY KEY,
1151
+ agent_id TEXT NOT NULL,
1152
+ agent_role TEXT NOT NULL,
1153
+ session_id TEXT NOT NULL,
1154
+ timestamp TEXT NOT NULL,
1155
+ tool_name TEXT NOT NULL,
1156
+ project_name TEXT NOT NULL,
1157
+ has_error INTEGER NOT NULL DEFAULT 0,
1158
+ raw_text TEXT NOT NULL,
1159
+ vector F32_BLOB(1024),
1160
+ version INTEGER NOT NULL DEFAULT 0
1161
+ );
1162
+
1163
+ CREATE INDEX IF NOT EXISTS idx_memories_agent ON memories(agent_id);
1164
+ CREATE INDEX IF NOT EXISTS idx_memories_timestamp ON memories(timestamp);
1165
+ CREATE INDEX IF NOT EXISTS idx_memories_agent_project ON memories(agent_id, project_name);
1166
+ `);
1167
+ await client.executeMultiple(`
1168
+ CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
1169
+ raw_text,
1170
+ content='memories',
1171
+ content_rowid='rowid'
1172
+ );
1173
+
1174
+ CREATE TRIGGER IF NOT EXISTS memories_fts_ai AFTER INSERT ON memories BEGIN
1175
+ INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
1176
+ END;
1177
+
1178
+ CREATE TRIGGER IF NOT EXISTS memories_fts_ad AFTER DELETE ON memories BEGIN
1179
+ INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
1180
+ END;
1181
+
1182
+ CREATE TRIGGER IF NOT EXISTS memories_fts_au AFTER UPDATE ON memories BEGIN
1183
+ INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
1184
+ INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
1185
+ END;
1186
+ `);
1187
+ for (const col of [
1188
+ "ALTER TABLE memories ADD COLUMN task_id TEXT",
1189
+ "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
1190
+ "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
1191
+ "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
1192
+ "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
1193
+ "ALTER TABLE memories ADD COLUMN graph_extracted INTEGER DEFAULT 0",
1194
+ "ALTER TABLE memories ADD COLUMN content_hash TEXT",
1195
+ "ALTER TABLE memories ADD COLUMN graph_extracted_hash TEXT",
1196
+ "ALTER TABLE memories ADD COLUMN confidence REAL DEFAULT 0.7",
1197
+ "ALTER TABLE memories ADD COLUMN last_accessed TEXT",
1198
+ // Wiki linkage columns (must match database.ts)
1199
+ "ALTER TABLE memories ADD COLUMN workspace_id TEXT",
1200
+ "ALTER TABLE memories ADD COLUMN document_id TEXT",
1201
+ "ALTER TABLE memories ADD COLUMN user_id TEXT",
1202
+ "ALTER TABLE memories ADD COLUMN char_offset INTEGER",
1203
+ "ALTER TABLE memories ADD COLUMN page_number INTEGER",
1204
+ // Source provenance columns (must match database.ts)
1205
+ "ALTER TABLE memories ADD COLUMN source_path TEXT",
1206
+ "ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'",
1207
+ "ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3",
1208
+ "ALTER TABLE memories ADD COLUMN supersedes_id TEXT"
1209
+ ]) {
1210
+ try {
1211
+ await client.execute(col);
1212
+ } catch {
1213
+ }
1214
+ }
1215
+ for (const idx of [
1216
+ "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
1217
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
1218
+ ]) {
1219
+ try {
1220
+ await client.execute(idx);
1221
+ } catch {
1222
+ }
1223
+ }
1224
+ try {
1225
+ await client.execute("CREATE INDEX IF NOT EXISTS idx_memories_status ON memories(status)");
898
1226
  } catch {
899
1227
  }
900
- for (const col of [
901
- "ALTER TABLE memories ADD COLUMN content_hash TEXT",
902
- "ALTER TABLE memories ADD COLUMN graph_extracted_hash TEXT"
1228
+ for (const idx of [
1229
+ "CREATE INDEX IF NOT EXISTS idx_memories_workspace ON memories(workspace_id)",
1230
+ "CREATE INDEX IF NOT EXISTS idx_memories_document ON memories(document_id)",
1231
+ "CREATE INDEX IF NOT EXISTS idx_memories_user ON memories(user_id)"
903
1232
  ]) {
904
1233
  try {
905
- await client.execute(col);
1234
+ await client.execute(idx);
906
1235
  } catch {
907
1236
  }
908
1237
  }
@@ -944,6 +1273,7 @@ async function ensureSchema() {
944
1273
  CREATE INDEX IF NOT EXISTS idx_entities_type ON entities(type);
945
1274
  CREATE INDEX IF NOT EXISTS idx_relationships_source ON relationships(source_entity_id);
946
1275
  CREATE INDEX IF NOT EXISTS idx_relationships_target ON relationships(target_entity_id);
1276
+ CREATE INDEX IF NOT EXISTS idx_relationships_type ON relationships(type);
947
1277
 
948
1278
  CREATE TABLE IF NOT EXISTS hyperedges (
949
1279
  id TEXT PRIMARY KEY,
@@ -959,13 +1289,6 @@ async function ensureSchema() {
959
1289
  PRIMARY KEY (hyperedge_id, entity_id)
960
1290
  );
961
1291
  `);
962
- await client.executeMultiple(`
963
- CREATE TABLE IF NOT EXISTS entity_aliases (
964
- alias TEXT NOT NULL PRIMARY KEY,
965
- canonical_entity_id TEXT NOT NULL
966
- );
967
- CREATE INDEX IF NOT EXISTS idx_entity_aliases_canonical ON entity_aliases(canonical_entity_id);
968
- `);
969
1292
  for (const col of [
970
1293
  "ALTER TABLE relationships ADD COLUMN confidence REAL DEFAULT 1.0",
971
1294
  "ALTER TABLE relationships ADD COLUMN confidence_label TEXT DEFAULT 'extracted'"
@@ -975,228 +1298,33 @@ async function ensureSchema() {
975
1298
  } catch {
976
1299
  }
977
1300
  }
978
- try {
979
- await client.execute(
980
- `CREATE INDEX IF NOT EXISTS idx_memories_status ON memories(status)`
981
- );
982
- } catch {
983
- }
984
- await client.executeMultiple(`
985
- CREATE TABLE IF NOT EXISTS identity (
986
- id INTEGER PRIMARY KEY AUTOINCREMENT,
987
- agent_id TEXT NOT NULL UNIQUE,
988
- content_hash TEXT NOT NULL,
989
- updated_at TEXT NOT NULL,
990
- updated_by TEXT NOT NULL
991
- );
992
-
993
- CREATE INDEX IF NOT EXISTS idx_identity_agent ON identity(agent_id);
994
- `);
995
- await client.executeMultiple(`
996
- CREATE TABLE IF NOT EXISTS chat_history (
997
- id INTEGER PRIMARY KEY AUTOINCREMENT,
998
- session_id TEXT NOT NULL,
999
- role TEXT NOT NULL,
1000
- content TEXT NOT NULL,
1001
- tool_name TEXT,
1002
- tool_id TEXT,
1003
- is_error INTEGER NOT NULL DEFAULT 0,
1004
- timestamp INTEGER NOT NULL
1005
- );
1006
-
1007
- CREATE INDEX IF NOT EXISTS idx_chat_history_session
1008
- ON chat_history(session_id, id);
1009
- `);
1010
- await client.executeMultiple(`
1011
- CREATE TABLE IF NOT EXISTS workspaces (
1012
- id TEXT PRIMARY KEY,
1013
- slug TEXT NOT NULL UNIQUE,
1014
- name TEXT NOT NULL,
1015
- owner_agent_id TEXT,
1016
- created_at TEXT NOT NULL,
1017
- metadata TEXT
1018
- );
1019
-
1020
- CREATE INDEX IF NOT EXISTS idx_workspaces_slug
1021
- ON workspaces(slug);
1022
- `);
1023
- await client.executeMultiple(`
1024
- CREATE TABLE IF NOT EXISTS documents (
1025
- id TEXT PRIMARY KEY,
1026
- workspace_id TEXT NOT NULL,
1027
- filename TEXT NOT NULL,
1028
- mime TEXT,
1029
- source_type TEXT,
1030
- user_id TEXT,
1031
- uploaded_at TEXT NOT NULL,
1032
- metadata TEXT,
1033
- FOREIGN KEY (workspace_id) REFERENCES workspaces(id)
1034
- );
1035
-
1036
- CREATE INDEX IF NOT EXISTS idx_documents_workspace
1037
- ON documents(workspace_id);
1038
-
1039
- CREATE INDEX IF NOT EXISTS idx_documents_user
1040
- ON documents(user_id);
1041
- `);
1042
- for (const column of [
1043
- "workspace_id TEXT",
1044
- "document_id TEXT",
1045
- "user_id TEXT",
1046
- "char_offset INTEGER",
1047
- "page_number INTEGER"
1048
- ]) {
1049
- try {
1050
- await client.execute({
1051
- sql: `ALTER TABLE memories ADD COLUMN ${column}`,
1052
- args: []
1053
- });
1054
- } catch {
1055
- }
1056
- }
1057
- await client.executeMultiple(`
1058
- CREATE INDEX IF NOT EXISTS idx_memories_workspace
1059
- ON memories(workspace_id);
1060
-
1061
- CREATE INDEX IF NOT EXISTS idx_memories_document
1062
- ON memories(document_id);
1063
-
1064
- CREATE INDEX IF NOT EXISTS idx_memories_user
1065
- ON memories(user_id);
1066
- `);
1067
- await client.executeMultiple(`
1068
- CREATE TABLE IF NOT EXISTS session_kills (
1069
- id TEXT PRIMARY KEY,
1070
- session_name TEXT NOT NULL,
1071
- agent_id TEXT NOT NULL,
1072
- killed_at TIMESTAMP NOT NULL,
1073
- reason TEXT NOT NULL,
1074
- ticks_idle INTEGER,
1075
- estimated_tokens_saved INTEGER
1076
- );
1077
-
1078
- CREATE INDEX IF NOT EXISTS idx_session_kills_killed_at
1079
- ON session_kills(killed_at);
1080
-
1081
- CREATE INDEX IF NOT EXISTS idx_session_kills_agent
1082
- ON session_kills(agent_id);
1083
- `);
1084
- await client.executeMultiple(`
1085
- CREATE TABLE IF NOT EXISTS conversations (
1086
- id TEXT PRIMARY KEY,
1087
- platform TEXT NOT NULL,
1088
- external_id TEXT,
1089
- sender_id TEXT NOT NULL,
1090
- sender_name TEXT,
1091
- sender_phone TEXT,
1092
- sender_email TEXT,
1093
- recipient_id TEXT,
1094
- channel_id TEXT NOT NULL,
1095
- thread_id TEXT,
1096
- reply_to_id TEXT,
1097
- content_text TEXT,
1098
- content_media TEXT,
1099
- content_metadata TEXT,
1100
- agent_response TEXT,
1101
- agent_name TEXT,
1102
- timestamp TEXT NOT NULL,
1103
- ingested_at TEXT NOT NULL
1104
- );
1105
-
1106
- CREATE INDEX IF NOT EXISTS idx_conversations_platform
1107
- ON conversations(platform);
1108
-
1109
- CREATE INDEX IF NOT EXISTS idx_conversations_sender
1110
- ON conversations(sender_id);
1111
-
1112
- CREATE INDEX IF NOT EXISTS idx_conversations_timestamp
1113
- ON conversations(timestamp);
1114
-
1115
- CREATE INDEX IF NOT EXISTS idx_conversations_thread
1116
- ON conversations(thread_id);
1117
-
1118
- CREATE INDEX IF NOT EXISTS idx_conversations_channel
1119
- ON conversations(channel_id);
1120
- `);
1121
- await client.executeMultiple(`
1122
- CREATE VIRTUAL TABLE IF NOT EXISTS conversations_fts USING fts5(
1123
- content_text,
1124
- sender_name,
1125
- agent_response,
1126
- content='conversations',
1127
- content_rowid='rowid'
1128
- );
1129
-
1130
- CREATE TRIGGER IF NOT EXISTS conversations_fts_ai AFTER INSERT ON conversations BEGIN
1131
- INSERT INTO conversations_fts(rowid, content_text, sender_name, agent_response)
1132
- VALUES (new.rowid, new.content_text, new.sender_name, new.agent_response);
1133
- END;
1134
-
1135
- CREATE TRIGGER IF NOT EXISTS conversations_fts_ad AFTER DELETE ON conversations BEGIN
1136
- INSERT INTO conversations_fts(conversations_fts, rowid, content_text, sender_name, agent_response)
1137
- VALUES('delete', old.rowid, old.content_text, old.sender_name, old.agent_response);
1138
- END;
1139
-
1140
- CREATE TRIGGER IF NOT EXISTS conversations_fts_au AFTER UPDATE ON conversations BEGIN
1141
- INSERT INTO conversations_fts(conversations_fts, rowid, content_text, sender_name, agent_response)
1142
- VALUES('delete', old.rowid, old.content_text, old.sender_name, old.agent_response);
1143
- INSERT INTO conversations_fts(rowid, content_text, sender_name, agent_response)
1144
- VALUES (new.rowid, new.content_text, new.sender_name, new.agent_response);
1145
- END;
1146
- `);
1147
- }
1148
-
1149
- // src/lib/keychain.ts
1150
- import { readFile, writeFile, unlink, mkdir, chmod } from "fs/promises";
1151
- import { existsSync } from "fs";
1152
- import path from "path";
1153
- import crypto from "crypto";
1154
- var SERVICE = "exe-mem";
1155
- var ACCOUNT = "master-key";
1156
- function getKeyDir() {
1157
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path.join(process.env.HOME ?? "/tmp", ".exe-os");
1158
1301
  }
1159
- function getKeyPath() {
1160
- return path.join(getKeyDir(), "master.key");
1302
+ async function getReadyShardClient(projectName) {
1303
+ const client = getShardClient(projectName);
1304
+ await ensureShardSchema(client);
1305
+ return client;
1161
1306
  }
1162
- async function tryKeytar() {
1163
- try {
1164
- return await import("keytar");
1165
- } catch {
1166
- return null;
1307
+ function disposeShards() {
1308
+ for (const [, client] of _shards) {
1309
+ client.close();
1167
1310
  }
1311
+ _shards.clear();
1312
+ _shardingEnabled = false;
1313
+ _encryptionKey = null;
1168
1314
  }
1169
- async function getMasterKey() {
1170
- const keytar = await tryKeytar();
1171
- if (keytar) {
1172
- try {
1173
- const stored = await keytar.getPassword(SERVICE, ACCOUNT);
1174
- if (stored) {
1175
- return Buffer.from(stored, "base64");
1176
- }
1177
- } catch {
1178
- }
1179
- }
1180
- const keyPath = getKeyPath();
1181
- if (!existsSync(keyPath)) {
1182
- return null;
1183
- }
1184
- try {
1185
- const content = await readFile(keyPath, "utf-8");
1186
- return Buffer.from(content.trim(), "base64");
1187
- } catch {
1188
- return null;
1315
+ var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
1316
+ var init_shard_manager = __esm({
1317
+ "src/lib/shard-manager.ts"() {
1318
+ "use strict";
1319
+ init_config();
1320
+ SHARDS_DIR = path3.join(EXE_AI_DIR, "shards");
1321
+ _shards = /* @__PURE__ */ new Map();
1322
+ _encryptionKey = null;
1323
+ _shardingEnabled = false;
1189
1324
  }
1190
- }
1325
+ });
1191
1326
 
1192
1327
  // src/lib/store.ts
1193
- init_config();
1194
- var _pendingRecords = [];
1195
- var _batchSize = 20;
1196
- var _flushIntervalMs = 1e4;
1197
- var _flushTimer = null;
1198
- var _flushing = false;
1199
- var _nextVersion = 1;
1200
1328
  async function initStore(options) {
1201
1329
  if (_flushTimer !== null) {
1202
1330
  clearInterval(_flushTimer);
@@ -1287,8 +1415,31 @@ async function attachDocumentMetadata(records) {
1287
1415
  }
1288
1416
  return records;
1289
1417
  }
1418
+ var _pendingRecords, _batchSize, _flushIntervalMs, _flushTimer, _flushing, _nextVersion;
1419
+ var init_store = __esm({
1420
+ "src/lib/store.ts"() {
1421
+ "use strict";
1422
+ init_memory();
1423
+ init_database();
1424
+ init_keychain();
1425
+ init_config();
1426
+ _pendingRecords = [];
1427
+ _batchSize = 20;
1428
+ _flushIntervalMs = 1e4;
1429
+ _flushTimer = null;
1430
+ _flushing = false;
1431
+ _nextVersion = 1;
1432
+ }
1433
+ });
1434
+
1435
+ // src/bin/exe-forget.ts
1436
+ init_store();
1437
+ init_database();
1438
+ import { createInterface } from "readline";
1290
1439
 
1291
1440
  // src/lib/hybrid-search.ts
1441
+ init_store();
1442
+ init_database();
1292
1443
  async function lightweightSearch(queryText, agentId, options) {
1293
1444
  const client = getClient();
1294
1445
  const limit = options?.limit ?? 5;
@@ -1324,7 +1475,8 @@ async function ftsQuery(client, matchExpr, agentId, options, limit) {
1324
1475
  m.has_error, m.raw_text, m.vector, m.task_id,
1325
1476
  m.importance, m.status, m.confidence, m.last_accessed,
1326
1477
  m.workspace_id, m.document_id, m.user_id,
1327
- m.char_offset, m.page_number
1478
+ m.char_offset, m.page_number,
1479
+ m.source_path, m.source_type
1328
1480
  FROM memories m
1329
1481
  JOIN memories_fts fts ON m.rowid = fts.rowid
1330
1482
  WHERE memories_fts MATCH ?
@@ -1373,7 +1525,9 @@ async function ftsQuery(client, matchExpr, agentId, options, limit) {
1373
1525
  document_id: row.document_id ?? null,
1374
1526
  user_id: row.user_id ?? null,
1375
1527
  char_offset: row.char_offset ?? null,
1376
- page_number: row.page_number ?? null
1528
+ page_number: row.page_number ?? null,
1529
+ source_path: row.source_path ?? null,
1530
+ source_type: row.source_type ?? null
1377
1531
  }));
1378
1532
  }
1379
1533
  async function recentRecords(agentId, options, limit) {
@@ -1385,7 +1539,8 @@ async function recentRecords(agentId, options, limit) {
1385
1539
  has_error, raw_text, vector, task_id,
1386
1540
  importance, status, confidence, last_accessed,
1387
1541
  workspace_id, document_id, user_id,
1388
- char_offset, page_number
1542
+ char_offset, page_number,
1543
+ source_path, source_type
1389
1544
  FROM memories
1390
1545
  WHERE agent_id = ?${statusFilter}
1391
1546
  AND COALESCE(confidence, 0.7) >= 0.3`;
@@ -1432,7 +1587,9 @@ async function recentRecords(agentId, options, limit) {
1432
1587
  document_id: row.document_id ?? null,
1433
1588
  user_id: row.user_id ?? null,
1434
1589
  char_offset: row.char_offset ?? null,
1435
- page_number: row.page_number ?? null
1590
+ page_number: row.page_number ?? null,
1591
+ source_path: row.source_path ?? null,
1592
+ source_type: row.source_type ?? null
1436
1593
  }));
1437
1594
  }
1438
1595