@askexenow/exe-os 0.9.7 → 0.9.9

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 (101) hide show
  1. package/dist/bin/backfill-conversations.js +953 -105
  2. package/dist/bin/backfill-responses.js +952 -104
  3. package/dist/bin/backfill-vectors.js +956 -108
  4. package/dist/bin/cleanup-stale-review-tasks.js +802 -58
  5. package/dist/bin/cli.js +2292 -1070
  6. package/dist/bin/exe-agent-config.js +157 -101
  7. package/dist/bin/exe-agent.js +55 -29
  8. package/dist/bin/exe-assign.js +940 -92
  9. package/dist/bin/exe-boot.js +1424 -442
  10. package/dist/bin/exe-call.js +240 -141
  11. package/dist/bin/exe-cloud.js +198 -70
  12. package/dist/bin/exe-dispatch.js +951 -192
  13. package/dist/bin/exe-doctor.js +791 -51
  14. package/dist/bin/exe-export-behaviors.js +790 -42
  15. package/dist/bin/exe-forget.js +771 -31
  16. package/dist/bin/exe-gateway.js +1592 -521
  17. package/dist/bin/exe-heartbeat.js +850 -109
  18. package/dist/bin/exe-kill.js +783 -35
  19. package/dist/bin/exe-launch-agent.js +1030 -107
  20. package/dist/bin/exe-link.js +916 -110
  21. package/dist/bin/exe-new-employee.js +526 -217
  22. package/dist/bin/exe-pending-messages.js +1046 -62
  23. package/dist/bin/exe-pending-notifications.js +1318 -111
  24. package/dist/bin/exe-pending-reviews.js +1040 -72
  25. package/dist/bin/exe-rename.js +772 -59
  26. package/dist/bin/exe-review.js +772 -32
  27. package/dist/bin/exe-search.js +982 -128
  28. package/dist/bin/exe-session-cleanup.js +1180 -306
  29. package/dist/bin/exe-settings.js +185 -105
  30. package/dist/bin/exe-start-codex.js +886 -132
  31. package/dist/bin/exe-start-opencode.js +873 -119
  32. package/dist/bin/exe-status.js +803 -59
  33. package/dist/bin/exe-team.js +772 -32
  34. package/dist/bin/git-sweep.js +1046 -223
  35. package/dist/bin/graph-backfill.js +779 -31
  36. package/dist/bin/graph-export.js +785 -37
  37. package/dist/bin/install.js +632 -200
  38. package/dist/bin/scan-tasks.js +1055 -232
  39. package/dist/bin/setup.js +1419 -320
  40. package/dist/bin/shard-migrate.js +783 -35
  41. package/dist/bin/update.js +138 -49
  42. package/dist/bin/wiki-sync.js +782 -34
  43. package/dist/gateway/index.js +1444 -449
  44. package/dist/hooks/bug-report-worker.js +1141 -269
  45. package/dist/hooks/codex-stop-task-finalizer.js +4678 -0
  46. package/dist/hooks/commit-complete.js +1044 -221
  47. package/dist/hooks/error-recall.js +989 -135
  48. package/dist/hooks/exe-heartbeat-hook.js +99 -75
  49. package/dist/hooks/ingest-worker.js +4176 -3226
  50. package/dist/hooks/ingest.js +920 -168
  51. package/dist/hooks/instructions-loaded.js +874 -70
  52. package/dist/hooks/notification.js +860 -56
  53. package/dist/hooks/post-compact.js +881 -73
  54. package/dist/hooks/pre-compact.js +1050 -227
  55. package/dist/hooks/pre-tool-use.js +1084 -159
  56. package/dist/hooks/prompt-ingest-worker.js +1089 -164
  57. package/dist/hooks/prompt-submit.js +1469 -515
  58. package/dist/hooks/response-ingest-worker.js +1104 -179
  59. package/dist/hooks/session-end.js +1085 -251
  60. package/dist/hooks/session-start.js +1241 -231
  61. package/dist/hooks/stop.js +935 -109
  62. package/dist/hooks/subagent-stop.js +881 -73
  63. package/dist/hooks/summary-worker.js +1323 -307
  64. package/dist/index.js +1449 -452
  65. package/dist/lib/agent-config.js +28 -6
  66. package/dist/lib/cloud-sync.js +909 -115
  67. package/dist/lib/config.js +30 -10
  68. package/dist/lib/consolidation.js +42 -9
  69. package/dist/lib/database.js +739 -33
  70. package/dist/lib/db-daemon-client.js +73 -19
  71. package/dist/lib/db.js +2359 -0
  72. package/dist/lib/device-registry.js +760 -47
  73. package/dist/lib/embedder.js +201 -73
  74. package/dist/lib/employee-templates.js +30 -4
  75. package/dist/lib/employees.js +290 -86
  76. package/dist/lib/exe-daemon-client.js +187 -83
  77. package/dist/lib/exe-daemon.js +1696 -616
  78. package/dist/lib/hybrid-search.js +982 -128
  79. package/dist/lib/identity.js +43 -13
  80. package/dist/lib/license.js +133 -48
  81. package/dist/lib/messaging.js +167 -80
  82. package/dist/lib/reminders.js +35 -5
  83. package/dist/lib/schedules.js +772 -32
  84. package/dist/lib/skill-learning.js +54 -7
  85. package/dist/lib/store.js +779 -31
  86. package/dist/lib/task-router.js +94 -73
  87. package/dist/lib/tasks.js +298 -225
  88. package/dist/lib/tmux-routing.js +246 -172
  89. package/dist/lib/token-spend.js +52 -14
  90. package/dist/mcp/server.js +2893 -850
  91. package/dist/mcp/tools/complete-reminder.js +35 -5
  92. package/dist/mcp/tools/create-reminder.js +35 -5
  93. package/dist/mcp/tools/create-task.js +507 -323
  94. package/dist/mcp/tools/deactivate-behavior.js +40 -10
  95. package/dist/mcp/tools/list-reminders.js +35 -5
  96. package/dist/mcp/tools/list-tasks.js +277 -104
  97. package/dist/mcp/tools/send-message.js +129 -56
  98. package/dist/mcp/tools/update-task.js +1864 -188
  99. package/dist/runtime/index.js +1083 -259
  100. package/dist/tui/App.js +1501 -434
  101. package/package.json +3 -2
@@ -20,9 +20,34 @@ var __copyProps = (to, from, except, desc) => {
20
20
  };
21
21
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
22
22
 
23
+ // src/lib/secure-files.ts
24
+ import { chmodSync, existsSync, mkdirSync } from "fs";
25
+ import { chmod, mkdir } from "fs/promises";
26
+ async function ensurePrivateDir(dirPath) {
27
+ await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
28
+ try {
29
+ await chmod(dirPath, PRIVATE_DIR_MODE);
30
+ } catch {
31
+ }
32
+ }
33
+ async function enforcePrivateFile(filePath) {
34
+ try {
35
+ await chmod(filePath, PRIVATE_FILE_MODE);
36
+ } catch {
37
+ }
38
+ }
39
+ var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
40
+ var init_secure_files = __esm({
41
+ "src/lib/secure-files.ts"() {
42
+ "use strict";
43
+ PRIVATE_DIR_MODE = 448;
44
+ PRIVATE_FILE_MODE = 384;
45
+ }
46
+ });
47
+
23
48
  // src/lib/config.ts
24
- import { readFile, writeFile, mkdir, chmod } from "fs/promises";
25
- import { readFileSync, existsSync, renameSync } from "fs";
49
+ import { readFile, writeFile } from "fs/promises";
50
+ import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
26
51
  import path from "path";
27
52
  import os from "os";
28
53
  function resolveDataDir() {
@@ -30,7 +55,7 @@ function resolveDataDir() {
30
55
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
31
56
  const newDir = path.join(os.homedir(), ".exe-os");
32
57
  const legacyDir = path.join(os.homedir(), ".exe-mem");
33
- if (!existsSync(newDir) && existsSync(legacyDir)) {
58
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
34
59
  try {
35
60
  renameSync(legacyDir, newDir);
36
61
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -93,9 +118,9 @@ function normalizeAutoUpdate(raw) {
93
118
  }
94
119
  async function loadConfig() {
95
120
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
96
- await mkdir(dir, { recursive: true });
121
+ await ensurePrivateDir(dir);
97
122
  const configPath = path.join(dir, "config.json");
98
- if (!existsSync(configPath)) {
123
+ if (!existsSync2(configPath)) {
99
124
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
100
125
  }
101
126
  const raw = await readFile(configPath, "utf-8");
@@ -108,6 +133,7 @@ async function loadConfig() {
108
133
  `);
109
134
  try {
110
135
  await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
136
+ await enforcePrivateFile(configPath);
111
137
  } catch {
112
138
  }
113
139
  }
@@ -127,6 +153,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
127
153
  var init_config = __esm({
128
154
  "src/lib/config.ts"() {
129
155
  "use strict";
156
+ init_secure_files();
130
157
  EXE_AI_DIR = resolveDataDir();
131
158
  DB_PATH = path.join(EXE_AI_DIR, "memories.db");
132
159
  MODELS_DIR = path.join(EXE_AI_DIR, "models");
@@ -205,7 +232,7 @@ var init_config = __esm({
205
232
 
206
233
  // src/lib/employees.ts
207
234
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
208
- import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
235
+ import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
209
236
  import { execSync } from "child_process";
210
237
  import path2 from "path";
211
238
  import os2 from "os";
@@ -222,7 +249,7 @@ function getCoordinatorName(employees = loadEmployeesSync()) {
222
249
  return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
223
250
  }
224
251
  async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
225
- if (!existsSync2(employeesPath)) {
252
+ if (!existsSync3(employeesPath)) {
226
253
  return [];
227
254
  }
228
255
  const raw = await readFile2(employeesPath, "utf-8");
@@ -233,14 +260,14 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
233
260
  }
234
261
  }
235
262
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
236
- if (!existsSync2(employeesPath)) return [];
263
+ if (!existsSync3(employeesPath)) return [];
237
264
  try {
238
265
  return JSON.parse(readFileSync2(employeesPath, "utf-8"));
239
266
  } catch {
240
267
  return [];
241
268
  }
242
269
  }
243
- var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE;
270
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, IDENTITY_DIR;
244
271
  var init_employees = __esm({
245
272
  "src/lib/employees.ts"() {
246
273
  "use strict";
@@ -248,6 +275,7 @@ var init_employees = __esm({
248
275
  EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
249
276
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
250
277
  COORDINATOR_ROLE = "COO";
278
+ IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
251
279
  }
252
280
  });
253
281
 
@@ -306,9 +334,605 @@ var init_db_retry = __esm({
306
334
  }
307
335
  });
308
336
 
337
+ // src/lib/database-adapter.ts
338
+ import os3 from "os";
339
+ import path3 from "path";
340
+ import { createRequire } from "module";
341
+ import { pathToFileURL } from "url";
342
+ function quotedIdentifier(identifier) {
343
+ return `"${identifier.replace(/"/g, '""')}"`;
344
+ }
345
+ function unqualifiedTableName(name) {
346
+ const raw = name.trim().replace(/^"|"$/g, "");
347
+ const parts = raw.split(".");
348
+ return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
349
+ }
350
+ function stripTrailingSemicolon(sql) {
351
+ return sql.trim().replace(/;+\s*$/u, "");
352
+ }
353
+ function appendClause(sql, clause) {
354
+ const trimmed = stripTrailingSemicolon(sql);
355
+ const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
356
+ if (!returningMatch) {
357
+ return `${trimmed}${clause}`;
358
+ }
359
+ const idx = returningMatch.index;
360
+ return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
361
+ }
362
+ function normalizeStatement(stmt) {
363
+ if (typeof stmt === "string") {
364
+ return { kind: "positional", sql: stmt, args: [] };
365
+ }
366
+ const sql = stmt.sql;
367
+ if (Array.isArray(stmt.args) || stmt.args === void 0) {
368
+ return { kind: "positional", sql, args: stmt.args ?? [] };
369
+ }
370
+ return { kind: "named", sql, args: stmt.args };
371
+ }
372
+ function rewriteBooleanLiterals(sql) {
373
+ let out = sql;
374
+ for (const column of BOOLEAN_COLUMN_NAMES) {
375
+ const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
376
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
377
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
378
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
379
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
380
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
381
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
382
+ }
383
+ return out;
384
+ }
385
+ function rewriteInsertOrIgnore(sql) {
386
+ if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
387
+ return sql;
388
+ }
389
+ const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
390
+ return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
391
+ }
392
+ function rewriteInsertOrReplace(sql) {
393
+ const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
394
+ if (!match) {
395
+ return sql;
396
+ }
397
+ const rawTable = match[1];
398
+ const rawColumns = match[2];
399
+ const remainder = match[3];
400
+ const tableName = unqualifiedTableName(rawTable);
401
+ const conflictKeys = UPSERT_KEYS[tableName];
402
+ if (!conflictKeys?.length) {
403
+ return sql;
404
+ }
405
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
406
+ const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
407
+ const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
408
+ const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
409
+ return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
410
+ }
411
+ function rewriteSql(sql) {
412
+ let out = sql;
413
+ out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
414
+ out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
415
+ out = rewriteBooleanLiterals(out);
416
+ out = rewriteInsertOrReplace(out);
417
+ out = rewriteInsertOrIgnore(out);
418
+ return stripTrailingSemicolon(out);
419
+ }
420
+ function toBoolean(value) {
421
+ if (value === null || value === void 0) return value;
422
+ if (typeof value === "boolean") return value;
423
+ if (typeof value === "number") return value !== 0;
424
+ if (typeof value === "bigint") return value !== 0n;
425
+ if (typeof value === "string") {
426
+ const normalized = value.trim().toLowerCase();
427
+ if (normalized === "0" || normalized === "false") return false;
428
+ if (normalized === "1" || normalized === "true") return true;
429
+ }
430
+ return Boolean(value);
431
+ }
432
+ function countQuestionMarks(sql, end) {
433
+ let count = 0;
434
+ let inSingle = false;
435
+ let inDouble = false;
436
+ let inLineComment = false;
437
+ let inBlockComment = false;
438
+ for (let i = 0; i < end; i++) {
439
+ const ch = sql[i];
440
+ const next = sql[i + 1];
441
+ if (inLineComment) {
442
+ if (ch === "\n") inLineComment = false;
443
+ continue;
444
+ }
445
+ if (inBlockComment) {
446
+ if (ch === "*" && next === "/") {
447
+ inBlockComment = false;
448
+ i += 1;
449
+ }
450
+ continue;
451
+ }
452
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
453
+ inLineComment = true;
454
+ i += 1;
455
+ continue;
456
+ }
457
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
458
+ inBlockComment = true;
459
+ i += 1;
460
+ continue;
461
+ }
462
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
463
+ inSingle = !inSingle;
464
+ continue;
465
+ }
466
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
467
+ inDouble = !inDouble;
468
+ continue;
469
+ }
470
+ if (!inSingle && !inDouble && ch === "?") {
471
+ count += 1;
472
+ }
473
+ }
474
+ return count;
475
+ }
476
+ function findBooleanPlaceholderIndexes(sql) {
477
+ const indexes = /* @__PURE__ */ new Set();
478
+ for (const column of BOOLEAN_COLUMN_NAMES) {
479
+ const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
480
+ for (const match of sql.matchAll(pattern)) {
481
+ const matchText = match[0];
482
+ const qIndex = match.index + matchText.lastIndexOf("?");
483
+ indexes.add(countQuestionMarks(sql, qIndex + 1));
484
+ }
485
+ }
486
+ return indexes;
487
+ }
488
+ function coerceInsertBooleanArgs(sql, args) {
489
+ const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
490
+ if (!match) return;
491
+ const rawTable = match[1];
492
+ const rawColumns = match[2];
493
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
494
+ if (!boolColumns?.size) return;
495
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
496
+ for (const [index, column] of columns.entries()) {
497
+ if (boolColumns.has(column) && index < args.length) {
498
+ args[index] = toBoolean(args[index]);
499
+ }
500
+ }
501
+ }
502
+ function coerceUpdateBooleanArgs(sql, args) {
503
+ const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
504
+ if (!match) return;
505
+ const rawTable = match[1];
506
+ const setClause = match[2];
507
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
508
+ if (!boolColumns?.size) return;
509
+ const assignments = setClause.split(",");
510
+ let placeholderIndex = 0;
511
+ for (const assignment of assignments) {
512
+ if (!assignment.includes("?")) continue;
513
+ placeholderIndex += 1;
514
+ const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
515
+ if (colMatch && boolColumns.has(colMatch[1])) {
516
+ args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
517
+ }
518
+ }
519
+ }
520
+ function coerceBooleanArgs(sql, args) {
521
+ const nextArgs = [...args];
522
+ coerceInsertBooleanArgs(sql, nextArgs);
523
+ coerceUpdateBooleanArgs(sql, nextArgs);
524
+ const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
525
+ for (const index of placeholderIndexes) {
526
+ if (index > 0 && index <= nextArgs.length) {
527
+ nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
528
+ }
529
+ }
530
+ return nextArgs;
531
+ }
532
+ function convertQuestionMarksToDollarParams(sql) {
533
+ let out = "";
534
+ let placeholder = 0;
535
+ let inSingle = false;
536
+ let inDouble = false;
537
+ let inLineComment = false;
538
+ let inBlockComment = false;
539
+ for (let i = 0; i < sql.length; i++) {
540
+ const ch = sql[i];
541
+ const next = sql[i + 1];
542
+ if (inLineComment) {
543
+ out += ch;
544
+ if (ch === "\n") inLineComment = false;
545
+ continue;
546
+ }
547
+ if (inBlockComment) {
548
+ out += ch;
549
+ if (ch === "*" && next === "/") {
550
+ out += next;
551
+ inBlockComment = false;
552
+ i += 1;
553
+ }
554
+ continue;
555
+ }
556
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
557
+ out += ch + next;
558
+ inLineComment = true;
559
+ i += 1;
560
+ continue;
561
+ }
562
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
563
+ out += ch + next;
564
+ inBlockComment = true;
565
+ i += 1;
566
+ continue;
567
+ }
568
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
569
+ inSingle = !inSingle;
570
+ out += ch;
571
+ continue;
572
+ }
573
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
574
+ inDouble = !inDouble;
575
+ out += ch;
576
+ continue;
577
+ }
578
+ if (!inSingle && !inDouble && ch === "?") {
579
+ placeholder += 1;
580
+ out += `$${placeholder}`;
581
+ continue;
582
+ }
583
+ out += ch;
584
+ }
585
+ return out;
586
+ }
587
+ function translateStatementForPostgres(stmt) {
588
+ const normalized = normalizeStatement(stmt);
589
+ if (normalized.kind === "named") {
590
+ throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
591
+ }
592
+ const rewrittenSql = rewriteSql(normalized.sql);
593
+ const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
594
+ return {
595
+ sql: convertQuestionMarksToDollarParams(rewrittenSql),
596
+ args: coercedArgs
597
+ };
598
+ }
599
+ function shouldBypassPostgres(stmt) {
600
+ const normalized = normalizeStatement(stmt);
601
+ if (normalized.kind === "named") {
602
+ return true;
603
+ }
604
+ return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
605
+ }
606
+ function shouldFallbackOnError(error) {
607
+ const message = error instanceof Error ? error.message : String(error);
608
+ return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
609
+ }
610
+ function isReadQuery(sql) {
611
+ const trimmed = sql.trimStart();
612
+ return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
613
+ }
614
+ function buildRow(row, columns) {
615
+ const values = columns.map((column) => row[column]);
616
+ return Object.assign(values, row);
617
+ }
618
+ function buildResultSet(rows, rowsAffected = 0) {
619
+ const columns = rows[0] ? Object.keys(rows[0]) : [];
620
+ const resultRows = rows.map((row) => buildRow(row, columns));
621
+ return {
622
+ columns,
623
+ columnTypes: columns.map(() => ""),
624
+ rows: resultRows,
625
+ rowsAffected,
626
+ lastInsertRowid: void 0,
627
+ toJSON() {
628
+ return {
629
+ columns,
630
+ columnTypes: columns.map(() => ""),
631
+ rows,
632
+ rowsAffected,
633
+ lastInsertRowid: void 0
634
+ };
635
+ }
636
+ };
637
+ }
638
+ async function loadPrismaClient() {
639
+ if (!prismaClientPromise) {
640
+ prismaClientPromise = (async () => {
641
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
642
+ if (explicitPath) {
643
+ const module2 = await import(pathToFileURL(explicitPath).href);
644
+ const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
645
+ if (!PrismaClient2) {
646
+ throw new Error(`No PrismaClient export found at ${explicitPath}`);
647
+ }
648
+ return new PrismaClient2();
649
+ }
650
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path3.join(os3.homedir(), "exe-db");
651
+ const requireFromExeDb = createRequire(path3.join(exeDbRoot, "package.json"));
652
+ const prismaEntry = requireFromExeDb.resolve("@prisma/client");
653
+ const module = await import(pathToFileURL(prismaEntry).href);
654
+ const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
655
+ if (!PrismaClient) {
656
+ throw new Error(`No PrismaClient export found in ${prismaEntry}`);
657
+ }
658
+ return new PrismaClient();
659
+ })();
660
+ }
661
+ return prismaClientPromise;
662
+ }
663
+ async function ensureCompatibilityViews(prisma) {
664
+ if (!compatibilityBootstrapPromise) {
665
+ compatibilityBootstrapPromise = (async () => {
666
+ for (const mapping of VIEW_MAPPINGS) {
667
+ const relation = mapping.source.replace(/"/g, "");
668
+ const rows = await prisma.$queryRawUnsafe(
669
+ "SELECT to_regclass($1) AS regclass",
670
+ relation
671
+ );
672
+ if (!rows[0]?.regclass) {
673
+ continue;
674
+ }
675
+ await prisma.$executeRawUnsafe(
676
+ `CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
677
+ );
678
+ }
679
+ })();
680
+ }
681
+ return compatibilityBootstrapPromise;
682
+ }
683
+ async function executeOnPrisma(executor, stmt) {
684
+ const translated = translateStatementForPostgres(stmt);
685
+ if (isReadQuery(translated.sql)) {
686
+ const rows = await executor.$queryRawUnsafe(
687
+ translated.sql,
688
+ ...translated.args
689
+ );
690
+ return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
691
+ }
692
+ const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
693
+ return buildResultSet([], rowsAffected);
694
+ }
695
+ function splitSqlStatements(sql) {
696
+ const parts = [];
697
+ let current = "";
698
+ let inSingle = false;
699
+ let inDouble = false;
700
+ let inLineComment = false;
701
+ let inBlockComment = false;
702
+ for (let i = 0; i < sql.length; i++) {
703
+ const ch = sql[i];
704
+ const next = sql[i + 1];
705
+ if (inLineComment) {
706
+ current += ch;
707
+ if (ch === "\n") inLineComment = false;
708
+ continue;
709
+ }
710
+ if (inBlockComment) {
711
+ current += ch;
712
+ if (ch === "*" && next === "/") {
713
+ current += next;
714
+ inBlockComment = false;
715
+ i += 1;
716
+ }
717
+ continue;
718
+ }
719
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
720
+ current += ch + next;
721
+ inLineComment = true;
722
+ i += 1;
723
+ continue;
724
+ }
725
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
726
+ current += ch + next;
727
+ inBlockComment = true;
728
+ i += 1;
729
+ continue;
730
+ }
731
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
732
+ inSingle = !inSingle;
733
+ current += ch;
734
+ continue;
735
+ }
736
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
737
+ inDouble = !inDouble;
738
+ current += ch;
739
+ continue;
740
+ }
741
+ if (!inSingle && !inDouble && ch === ";") {
742
+ if (current.trim()) {
743
+ parts.push(current.trim());
744
+ }
745
+ current = "";
746
+ continue;
747
+ }
748
+ current += ch;
749
+ }
750
+ if (current.trim()) {
751
+ parts.push(current.trim());
752
+ }
753
+ return parts;
754
+ }
755
+ async function createPrismaDbAdapter(fallbackClient) {
756
+ const prisma = await loadPrismaClient();
757
+ await ensureCompatibilityViews(prisma);
758
+ let closed = false;
759
+ let adapter;
760
+ const fallbackExecute = async (stmt, error) => {
761
+ if (!fallbackClient) {
762
+ if (error) throw error;
763
+ throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
764
+ }
765
+ if (error) {
766
+ process.stderr.write(
767
+ `[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
768
+ `
769
+ );
770
+ }
771
+ return fallbackClient.execute(stmt);
772
+ };
773
+ adapter = {
774
+ async execute(stmt) {
775
+ if (shouldBypassPostgres(stmt)) {
776
+ return fallbackExecute(stmt);
777
+ }
778
+ try {
779
+ return await executeOnPrisma(prisma, stmt);
780
+ } catch (error) {
781
+ if (shouldFallbackOnError(error)) {
782
+ return fallbackExecute(stmt, error);
783
+ }
784
+ throw error;
785
+ }
786
+ },
787
+ async batch(stmts, mode) {
788
+ if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
789
+ if (!fallbackClient) {
790
+ throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
791
+ }
792
+ return fallbackClient.batch(stmts, mode);
793
+ }
794
+ try {
795
+ if (prisma.$transaction) {
796
+ return await prisma.$transaction(async (tx) => {
797
+ const results2 = [];
798
+ for (const stmt of stmts) {
799
+ results2.push(await executeOnPrisma(tx, stmt));
800
+ }
801
+ return results2;
802
+ });
803
+ }
804
+ const results = [];
805
+ for (const stmt of stmts) {
806
+ results.push(await executeOnPrisma(prisma, stmt));
807
+ }
808
+ return results;
809
+ } catch (error) {
810
+ if (fallbackClient && shouldFallbackOnError(error)) {
811
+ process.stderr.write(
812
+ `[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
813
+ `
814
+ );
815
+ return fallbackClient.batch(stmts, mode);
816
+ }
817
+ throw error;
818
+ }
819
+ },
820
+ async migrate(stmts) {
821
+ if (fallbackClient) {
822
+ return fallbackClient.migrate(stmts);
823
+ }
824
+ return adapter.batch(stmts, "deferred");
825
+ },
826
+ async transaction(mode) {
827
+ if (!fallbackClient) {
828
+ throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
829
+ }
830
+ return fallbackClient.transaction(mode);
831
+ },
832
+ async executeMultiple(sql) {
833
+ if (fallbackClient && shouldBypassPostgres(sql)) {
834
+ return fallbackClient.executeMultiple(sql);
835
+ }
836
+ for (const statement of splitSqlStatements(sql)) {
837
+ await adapter.execute(statement);
838
+ }
839
+ },
840
+ async sync() {
841
+ if (fallbackClient) {
842
+ return fallbackClient.sync();
843
+ }
844
+ return { frame_no: 0, frames_synced: 0 };
845
+ },
846
+ close() {
847
+ closed = true;
848
+ prismaClientPromise = null;
849
+ compatibilityBootstrapPromise = null;
850
+ void prisma.$disconnect?.();
851
+ },
852
+ get closed() {
853
+ return closed;
854
+ },
855
+ get protocol() {
856
+ return "prisma-postgres";
857
+ }
858
+ };
859
+ return adapter;
860
+ }
861
+ var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
862
+ var init_database_adapter = __esm({
863
+ "src/lib/database-adapter.ts"() {
864
+ "use strict";
865
+ VIEW_MAPPINGS = [
866
+ { view: "memories", source: "memory.memory_records" },
867
+ { view: "tasks", source: "memory.tasks" },
868
+ { view: "behaviors", source: "memory.behaviors" },
869
+ { view: "entities", source: "memory.entities" },
870
+ { view: "relationships", source: "memory.relationships" },
871
+ { view: "entity_memories", source: "memory.entity_memories" },
872
+ { view: "entity_aliases", source: "memory.entity_aliases" },
873
+ { view: "notifications", source: "memory.notifications" },
874
+ { view: "messages", source: "memory.messages" },
875
+ { view: "users", source: "wiki.users" },
876
+ { view: "workspaces", source: "wiki.workspaces" },
877
+ { view: "workspace_users", source: "wiki.workspace_users" },
878
+ { view: "documents", source: "wiki.workspace_documents" },
879
+ { view: "chats", source: "wiki.workspace_chats" }
880
+ ];
881
+ UPSERT_KEYS = {
882
+ memories: ["id"],
883
+ tasks: ["id"],
884
+ behaviors: ["id"],
885
+ entities: ["id"],
886
+ relationships: ["id"],
887
+ entity_aliases: ["alias"],
888
+ notifications: ["id"],
889
+ messages: ["id"],
890
+ users: ["id"],
891
+ workspaces: ["id"],
892
+ workspace_users: ["id"],
893
+ documents: ["id"],
894
+ chats: ["id"]
895
+ };
896
+ BOOLEAN_COLUMNS_BY_TABLE = {
897
+ memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
898
+ behaviors: /* @__PURE__ */ new Set(["active"]),
899
+ notifications: /* @__PURE__ */ new Set(["read"]),
900
+ users: /* @__PURE__ */ new Set(["has_personal_memory"])
901
+ };
902
+ BOOLEAN_COLUMN_NAMES = new Set(
903
+ Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
904
+ );
905
+ IMMEDIATE_FALLBACK_PATTERNS = [
906
+ /\bPRAGMA\b/i,
907
+ /\bsqlite_master\b/i,
908
+ /(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
909
+ /\bMATCH\b/i,
910
+ /\bvector_distance_cos\s*\(/i,
911
+ /\bjson_extract\s*\(/i,
912
+ /\bjulianday\s*\(/i,
913
+ /\bstrftime\s*\(/i,
914
+ /\blast_insert_rowid\s*\(/i
915
+ ];
916
+ prismaClientPromise = null;
917
+ compatibilityBootstrapPromise = null;
918
+ }
919
+ });
920
+
309
921
  // src/lib/database.ts
310
922
  import { createClient } from "@libsql/client";
311
923
  async function initDatabase(config) {
924
+ if (_walCheckpointTimer) {
925
+ clearInterval(_walCheckpointTimer);
926
+ _walCheckpointTimer = null;
927
+ }
928
+ if (_daemonClient) {
929
+ _daemonClient.close();
930
+ _daemonClient = null;
931
+ }
932
+ if (_adapterClient && _adapterClient !== _resilientClient) {
933
+ _adapterClient.close();
934
+ }
935
+ _adapterClient = null;
312
936
  if (_client) {
313
937
  _client.close();
314
938
  _client = null;
@@ -322,6 +946,7 @@ async function initDatabase(config) {
322
946
  }
323
947
  _client = createClient(opts);
324
948
  _resilientClient = wrapWithRetry(_client);
949
+ _adapterClient = _resilientClient;
325
950
  _client.execute("PRAGMA busy_timeout = 30000").catch(() => {
326
951
  });
327
952
  _client.execute("PRAGMA journal_mode = WAL").catch(() => {
@@ -332,11 +957,17 @@ async function initDatabase(config) {
332
957
  });
333
958
  }, 3e4);
334
959
  _walCheckpointTimer.unref();
960
+ if (process.env.DATABASE_URL) {
961
+ _adapterClient = await createPrismaDbAdapter(_resilientClient);
962
+ }
335
963
  }
336
964
  function getClient() {
337
- if (!_resilientClient) {
965
+ if (!_adapterClient) {
338
966
  throw new Error("Database client not initialized. Call initDatabase() first.");
339
967
  }
968
+ if (process.env.DATABASE_URL) {
969
+ return _adapterClient;
970
+ }
340
971
  if (process.env.EXE_IS_DAEMON === "1") {
341
972
  return _resilientClient;
342
973
  }
@@ -629,6 +1260,7 @@ async function ensureSchema() {
629
1260
  project TEXT NOT NULL,
630
1261
  summary TEXT NOT NULL,
631
1262
  task_file TEXT,
1263
+ session_scope TEXT,
632
1264
  read INTEGER NOT NULL DEFAULT 0,
633
1265
  created_at TEXT NOT NULL
634
1266
  );
@@ -637,7 +1269,7 @@ async function ensureSchema() {
637
1269
  ON notifications(read);
638
1270
 
639
1271
  CREATE INDEX IF NOT EXISTS idx_notifications_agent
640
- ON notifications(agent_id);
1272
+ ON notifications(agent_id, session_scope);
641
1273
 
642
1274
  CREATE INDEX IF NOT EXISTS idx_notifications_task_file
643
1275
  ON notifications(task_file);
@@ -675,6 +1307,7 @@ async function ensureSchema() {
675
1307
  target_agent TEXT NOT NULL,
676
1308
  target_project TEXT,
677
1309
  target_device TEXT NOT NULL DEFAULT 'local',
1310
+ session_scope TEXT,
678
1311
  content TEXT NOT NULL,
679
1312
  priority TEXT DEFAULT 'normal',
680
1313
  status TEXT DEFAULT 'pending',
@@ -688,10 +1321,31 @@ async function ensureSchema() {
688
1321
  );
689
1322
 
690
1323
  CREATE INDEX IF NOT EXISTS idx_messages_target
691
- ON messages(target_agent, status);
1324
+ ON messages(target_agent, session_scope, status);
692
1325
 
693
1326
  CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
694
- ON messages(target_agent, from_agent, server_seq);
1327
+ ON messages(target_agent, session_scope, from_agent, server_seq);
1328
+ `);
1329
+ try {
1330
+ await client.execute({
1331
+ sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
1332
+ args: []
1333
+ });
1334
+ } catch {
1335
+ }
1336
+ try {
1337
+ await client.execute({
1338
+ sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
1339
+ args: []
1340
+ });
1341
+ } catch {
1342
+ }
1343
+ await client.executeMultiple(`
1344
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
1345
+ ON notifications(agent_id, session_scope, read, created_at);
1346
+
1347
+ CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
1348
+ ON messages(target_agent, session_scope, status, created_at);
695
1349
  `);
696
1350
  try {
697
1351
  await client.execute({
@@ -1275,17 +1929,26 @@ async function ensureSchema() {
1275
1929
  } catch {
1276
1930
  }
1277
1931
  }
1932
+ try {
1933
+ await client.execute({
1934
+ sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
1935
+ args: []
1936
+ });
1937
+ } catch {
1938
+ }
1278
1939
  }
1279
- var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso;
1940
+ var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso;
1280
1941
  var init_database = __esm({
1281
1942
  "src/lib/database.ts"() {
1282
1943
  "use strict";
1283
1944
  init_db_retry();
1284
1945
  init_employees();
1946
+ init_database_adapter();
1285
1947
  _client = null;
1286
1948
  _resilientClient = null;
1287
1949
  _walCheckpointTimer = null;
1288
1950
  _daemonClient = null;
1951
+ _adapterClient = null;
1289
1952
  initTurso = initDatabase;
1290
1953
  }
1291
1954
  });
@@ -1350,6 +2013,7 @@ var shard_manager_exports = {};
1350
2013
  __export(shard_manager_exports, {
1351
2014
  disposeShards: () => disposeShards,
1352
2015
  ensureShardSchema: () => ensureShardSchema,
2016
+ getOpenShardCount: () => getOpenShardCount,
1353
2017
  getReadyShardClient: () => getReadyShardClient,
1354
2018
  getShardClient: () => getShardClient,
1355
2019
  getShardsDir: () => getShardsDir,
@@ -1358,15 +2022,18 @@ __export(shard_manager_exports, {
1358
2022
  listShards: () => listShards,
1359
2023
  shardExists: () => shardExists
1360
2024
  });
1361
- import path4 from "path";
1362
- import { existsSync as existsSync4, mkdirSync, readdirSync } from "fs";
2025
+ import path5 from "path";
2026
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, readdirSync } from "fs";
1363
2027
  import { createClient as createClient2 } from "@libsql/client";
1364
2028
  function initShardManager(encryptionKey) {
1365
2029
  _encryptionKey = encryptionKey;
1366
- if (!existsSync4(SHARDS_DIR)) {
1367
- mkdirSync(SHARDS_DIR, { recursive: true });
2030
+ if (!existsSync5(SHARDS_DIR)) {
2031
+ mkdirSync2(SHARDS_DIR, { recursive: true });
1368
2032
  }
1369
2033
  _shardingEnabled = true;
2034
+ if (_evictionTimer) clearInterval(_evictionTimer);
2035
+ _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
2036
+ _evictionTimer.unref();
1370
2037
  }
1371
2038
  function isShardingEnabled() {
1372
2039
  return _shardingEnabled;
@@ -1383,21 +2050,28 @@ function getShardClient(projectName) {
1383
2050
  throw new Error(`Invalid project name for shard: "${projectName}"`);
1384
2051
  }
1385
2052
  const cached = _shards.get(safeName);
1386
- if (cached) return cached;
1387
- const dbPath = path4.join(SHARDS_DIR, `${safeName}.db`);
2053
+ if (cached) {
2054
+ _shardLastAccess.set(safeName, Date.now());
2055
+ return cached;
2056
+ }
2057
+ while (_shards.size >= MAX_OPEN_SHARDS) {
2058
+ evictLRU();
2059
+ }
2060
+ const dbPath = path5.join(SHARDS_DIR, `${safeName}.db`);
1388
2061
  const client = createClient2({
1389
2062
  url: `file:${dbPath}`,
1390
2063
  encryptionKey: _encryptionKey
1391
2064
  });
1392
2065
  _shards.set(safeName, client);
2066
+ _shardLastAccess.set(safeName, Date.now());
1393
2067
  return client;
1394
2068
  }
1395
2069
  function shardExists(projectName) {
1396
2070
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
1397
- return existsSync4(path4.join(SHARDS_DIR, `${safeName}.db`));
2071
+ return existsSync5(path5.join(SHARDS_DIR, `${safeName}.db`));
1398
2072
  }
1399
2073
  function listShards() {
1400
- if (!existsSync4(SHARDS_DIR)) return [];
2074
+ if (!existsSync5(SHARDS_DIR)) return [];
1401
2075
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
1402
2076
  }
1403
2077
  async function ensureShardSchema(client) {
@@ -1449,6 +2123,8 @@ async function ensureShardSchema(client) {
1449
2123
  for (const col of [
1450
2124
  "ALTER TABLE memories ADD COLUMN task_id TEXT",
1451
2125
  "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
2126
+ "ALTER TABLE memories ADD COLUMN author_device_id TEXT",
2127
+ "ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
1452
2128
  "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
1453
2129
  "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
1454
2130
  "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
@@ -1471,7 +2147,23 @@ async function ensureShardSchema(client) {
1471
2147
  // MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
1472
2148
  "ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
1473
2149
  "ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
1474
- "ALTER TABLE memories ADD COLUMN trajectory TEXT"
2150
+ "ALTER TABLE memories ADD COLUMN trajectory TEXT",
2151
+ // Metadata enrichment columns (must match database.ts)
2152
+ "ALTER TABLE memories ADD COLUMN intent TEXT",
2153
+ "ALTER TABLE memories ADD COLUMN outcome TEXT",
2154
+ "ALTER TABLE memories ADD COLUMN domain TEXT",
2155
+ "ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
2156
+ "ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
2157
+ "ALTER TABLE memories ADD COLUMN chain_position TEXT",
2158
+ "ALTER TABLE memories ADD COLUMN review_status TEXT",
2159
+ "ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
2160
+ "ALTER TABLE memories ADD COLUMN file_paths TEXT",
2161
+ "ALTER TABLE memories ADD COLUMN commit_hash TEXT",
2162
+ "ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
2163
+ "ALTER TABLE memories ADD COLUMN token_cost REAL",
2164
+ "ALTER TABLE memories ADD COLUMN audience TEXT",
2165
+ "ALTER TABLE memories ADD COLUMN language_type TEXT",
2166
+ "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
1475
2167
  ]) {
1476
2168
  try {
1477
2169
  await client.execute(col);
@@ -1570,21 +2262,69 @@ async function getReadyShardClient(projectName) {
1570
2262
  await ensureShardSchema(client);
1571
2263
  return client;
1572
2264
  }
2265
+ function evictLRU() {
2266
+ let oldest = null;
2267
+ let oldestTime = Infinity;
2268
+ for (const [name, time] of _shardLastAccess) {
2269
+ if (time < oldestTime) {
2270
+ oldestTime = time;
2271
+ oldest = name;
2272
+ }
2273
+ }
2274
+ if (oldest) {
2275
+ const client = _shards.get(oldest);
2276
+ if (client) {
2277
+ client.close();
2278
+ }
2279
+ _shards.delete(oldest);
2280
+ _shardLastAccess.delete(oldest);
2281
+ }
2282
+ }
2283
+ function evictIdleShards() {
2284
+ const now = Date.now();
2285
+ const toEvict = [];
2286
+ for (const [name, lastAccess] of _shardLastAccess) {
2287
+ if (now - lastAccess > SHARD_IDLE_MS) {
2288
+ toEvict.push(name);
2289
+ }
2290
+ }
2291
+ for (const name of toEvict) {
2292
+ const client = _shards.get(name);
2293
+ if (client) {
2294
+ client.close();
2295
+ }
2296
+ _shards.delete(name);
2297
+ _shardLastAccess.delete(name);
2298
+ }
2299
+ }
2300
+ function getOpenShardCount() {
2301
+ return _shards.size;
2302
+ }
1573
2303
  function disposeShards() {
2304
+ if (_evictionTimer) {
2305
+ clearInterval(_evictionTimer);
2306
+ _evictionTimer = null;
2307
+ }
1574
2308
  for (const [, client] of _shards) {
1575
2309
  client.close();
1576
2310
  }
1577
2311
  _shards.clear();
2312
+ _shardLastAccess.clear();
1578
2313
  _shardingEnabled = false;
1579
2314
  _encryptionKey = null;
1580
2315
  }
1581
- var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
2316
+ var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
1582
2317
  var init_shard_manager = __esm({
1583
2318
  "src/lib/shard-manager.ts"() {
1584
2319
  "use strict";
1585
2320
  init_config();
1586
- SHARDS_DIR = path4.join(EXE_AI_DIR, "shards");
2321
+ SHARDS_DIR = path5.join(EXE_AI_DIR, "shards");
2322
+ SHARD_IDLE_MS = 5 * 60 * 1e3;
2323
+ MAX_OPEN_SHARDS = 10;
2324
+ EVICTION_INTERVAL_MS = 60 * 1e3;
1587
2325
  _shards = /* @__PURE__ */ new Map();
2326
+ _shardLastAccess = /* @__PURE__ */ new Map();
2327
+ _evictionTimer = null;
1588
2328
  _encryptionKey = null;
1589
2329
  _shardingEnabled = false;
1590
2330
  }
@@ -1778,13 +2518,13 @@ ${p.content}`).join("\n\n");
1778
2518
  });
1779
2519
 
1780
2520
  // src/lib/session-registry.ts
1781
- import path5 from "path";
1782
- import os4 from "os";
2521
+ import path6 from "path";
2522
+ import os5 from "os";
1783
2523
  var REGISTRY_PATH;
1784
2524
  var init_session_registry = __esm({
1785
2525
  "src/lib/session-registry.ts"() {
1786
2526
  "use strict";
1787
- REGISTRY_PATH = path5.join(os4.homedir(), ".exe-os", "session-registry.json");
2527
+ REGISTRY_PATH = path6.join(os5.homedir(), ".exe-os", "session-registry.json");
1788
2528
  }
1789
2529
  });
1790
2530
 
@@ -2019,15 +2759,16 @@ var init_runtime_table = __esm({
2019
2759
  });
2020
2760
 
2021
2761
  // src/lib/agent-config.ts
2022
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync5, mkdirSync as mkdirSync2 } from "fs";
2023
- import path6 from "path";
2762
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync6 } from "fs";
2763
+ import path7 from "path";
2024
2764
  var AGENT_CONFIG_PATH, DEFAULT_MODELS;
2025
2765
  var init_agent_config = __esm({
2026
2766
  "src/lib/agent-config.ts"() {
2027
2767
  "use strict";
2028
2768
  init_config();
2029
2769
  init_runtime_table();
2030
- AGENT_CONFIG_PATH = path6.join(EXE_AI_DIR, "agent-config.json");
2770
+ init_secure_files();
2771
+ AGENT_CONFIG_PATH = path7.join(EXE_AI_DIR, "agent-config.json");
2031
2772
  DEFAULT_MODELS = {
2032
2773
  claude: "claude-opus-4",
2033
2774
  codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
@@ -2037,38 +2778,41 @@ var init_agent_config = __esm({
2037
2778
  });
2038
2779
 
2039
2780
  // src/lib/intercom-queue.ts
2040
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync3, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
2041
- import path7 from "path";
2042
- import os5 from "os";
2781
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync3, existsSync as existsSync7, mkdirSync as mkdirSync3 } from "fs";
2782
+ import path8 from "path";
2783
+ import os6 from "os";
2043
2784
  var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
2044
2785
  var init_intercom_queue = __esm({
2045
2786
  "src/lib/intercom-queue.ts"() {
2046
2787
  "use strict";
2047
- QUEUE_PATH = path7.join(os5.homedir(), ".exe-os", "intercom-queue.json");
2788
+ QUEUE_PATH = path8.join(os6.homedir(), ".exe-os", "intercom-queue.json");
2048
2789
  TTL_MS = 60 * 60 * 1e3;
2049
- INTERCOM_LOG = path7.join(os5.homedir(), ".exe-os", "intercom.log");
2790
+ INTERCOM_LOG = path8.join(os6.homedir(), ".exe-os", "intercom.log");
2050
2791
  }
2051
2792
  });
2052
2793
 
2053
2794
  // src/lib/license.ts
2054
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
2795
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync8, mkdirSync as mkdirSync4 } from "fs";
2055
2796
  import { randomUUID as randomUUID2 } from "crypto";
2056
- import path8 from "path";
2797
+ import { createRequire as createRequire2 } from "module";
2798
+ import { pathToFileURL as pathToFileURL2 } from "url";
2799
+ import os7 from "os";
2800
+ import path9 from "path";
2057
2801
  import { jwtVerify, importSPKI } from "jose";
2058
2802
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH;
2059
2803
  var init_license = __esm({
2060
2804
  "src/lib/license.ts"() {
2061
2805
  "use strict";
2062
2806
  init_config();
2063
- LICENSE_PATH = path8.join(EXE_AI_DIR, "license.key");
2064
- CACHE_PATH = path8.join(EXE_AI_DIR, "license-cache.json");
2065
- DEVICE_ID_PATH = path8.join(EXE_AI_DIR, "device-id");
2807
+ LICENSE_PATH = path9.join(EXE_AI_DIR, "license.key");
2808
+ CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
2809
+ DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
2066
2810
  }
2067
2811
  });
2068
2812
 
2069
2813
  // src/lib/plan-limits.ts
2070
- import { readFileSync as readFileSync6, existsSync as existsSync8 } from "fs";
2071
- import path9 from "path";
2814
+ import { readFileSync as readFileSync6, existsSync as existsSync9 } from "fs";
2815
+ import path10 from "path";
2072
2816
  var CACHE_PATH2;
2073
2817
  var init_plan_limits = __esm({
2074
2818
  "src/lib/plan-limits.ts"() {
@@ -2077,14 +2821,14 @@ var init_plan_limits = __esm({
2077
2821
  init_employees();
2078
2822
  init_license();
2079
2823
  init_config();
2080
- CACHE_PATH2 = path9.join(EXE_AI_DIR, "license-cache.json");
2824
+ CACHE_PATH2 = path10.join(EXE_AI_DIR, "license-cache.json");
2081
2825
  }
2082
2826
  });
2083
2827
 
2084
2828
  // src/lib/tmux-routing.ts
2085
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, existsSync as existsSync9, appendFileSync, readdirSync as readdirSync2 } from "fs";
2086
- import path10 from "path";
2087
- import os6 from "os";
2829
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, existsSync as existsSync10, appendFileSync, readdirSync as readdirSync2 } from "fs";
2830
+ import path11 from "path";
2831
+ import os8 from "os";
2088
2832
  import { fileURLToPath as fileURLToPath2 } from "url";
2089
2833
  function getMySession() {
2090
2834
  return getTransport().getMySession();
@@ -2097,7 +2841,7 @@ function extractRootExe(name) {
2097
2841
  }
2098
2842
  function getParentExe(sessionKey) {
2099
2843
  try {
2100
- const data = JSON.parse(readFileSync7(path10.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
2844
+ const data = JSON.parse(readFileSync7(path11.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
2101
2845
  return data.parentExe || null;
2102
2846
  } catch {
2103
2847
  return null;
@@ -2140,10 +2884,10 @@ var init_tmux_routing = __esm({
2140
2884
  init_intercom_queue();
2141
2885
  init_plan_limits();
2142
2886
  init_employees();
2143
- SPAWN_LOCK_DIR = path10.join(os6.homedir(), ".exe-os", "spawn-locks");
2144
- SESSION_CACHE = path10.join(os6.homedir(), ".exe-os", "session-cache");
2145
- INTERCOM_LOG2 = path10.join(os6.homedir(), ".exe-os", "intercom.log");
2146
- DEBOUNCE_FILE = path10.join(SESSION_CACHE, "intercom-debounce.json");
2887
+ SPAWN_LOCK_DIR = path11.join(os8.homedir(), ".exe-os", "spawn-locks");
2888
+ SESSION_CACHE = path11.join(os8.homedir(), ".exe-os", "session-cache");
2889
+ INTERCOM_LOG2 = path11.join(os8.homedir(), ".exe-os", "intercom.log");
2890
+ DEBOUNCE_FILE = path11.join(SESSION_CACHE, "intercom-debounce.json");
2147
2891
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
2148
2892
  }
2149
2893
  });
@@ -2181,16 +2925,16 @@ init_database();
2181
2925
 
2182
2926
  // src/lib/keychain.ts
2183
2927
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
2184
- import { existsSync as existsSync3 } from "fs";
2185
- import path3 from "path";
2186
- import os3 from "os";
2928
+ import { existsSync as existsSync4 } from "fs";
2929
+ import path4 from "path";
2930
+ import os4 from "os";
2187
2931
  var SERVICE = "exe-mem";
2188
2932
  var ACCOUNT = "master-key";
2189
2933
  function getKeyDir() {
2190
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path3.join(os3.homedir(), ".exe-os");
2934
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path4.join(os4.homedir(), ".exe-os");
2191
2935
  }
2192
2936
  function getKeyPath() {
2193
- return path3.join(getKeyDir(), "master.key");
2937
+ return path4.join(getKeyDir(), "master.key");
2194
2938
  }
2195
2939
  async function tryKeytar() {
2196
2940
  try {
@@ -2211,9 +2955,9 @@ async function getMasterKey() {
2211
2955
  }
2212
2956
  }
2213
2957
  const keyPath = getKeyPath();
2214
- if (!existsSync3(keyPath)) {
2958
+ if (!existsSync4(keyPath)) {
2215
2959
  process.stderr.write(
2216
- `[keychain] Key not found at ${keyPath} (HOME=${os3.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
2960
+ `[keychain] Key not found at ${keyPath} (HOME=${os4.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
2217
2961
  `
2218
2962
  );
2219
2963
  return null;