@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
@@ -8,9 +8,47 @@ var __export = (target, all) => {
8
8
  __defProp(target, name, { get: all[name], enumerable: true });
9
9
  };
10
10
 
11
+ // src/lib/secure-files.ts
12
+ import { chmodSync, existsSync, mkdirSync } from "fs";
13
+ import { chmod, mkdir } from "fs/promises";
14
+ async function ensurePrivateDir(dirPath) {
15
+ await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
16
+ try {
17
+ await chmod(dirPath, PRIVATE_DIR_MODE);
18
+ } catch {
19
+ }
20
+ }
21
+ function ensurePrivateDirSync(dirPath) {
22
+ mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
23
+ try {
24
+ chmodSync(dirPath, PRIVATE_DIR_MODE);
25
+ } catch {
26
+ }
27
+ }
28
+ async function enforcePrivateFile(filePath) {
29
+ try {
30
+ await chmod(filePath, PRIVATE_FILE_MODE);
31
+ } catch {
32
+ }
33
+ }
34
+ function enforcePrivateFileSync(filePath) {
35
+ try {
36
+ if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
37
+ } catch {
38
+ }
39
+ }
40
+ var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
41
+ var init_secure_files = __esm({
42
+ "src/lib/secure-files.ts"() {
43
+ "use strict";
44
+ PRIVATE_DIR_MODE = 448;
45
+ PRIVATE_FILE_MODE = 384;
46
+ }
47
+ });
48
+
11
49
  // src/lib/config.ts
12
- import { readFile, writeFile, mkdir, chmod } from "fs/promises";
13
- import { readFileSync, existsSync, renameSync } from "fs";
50
+ import { readFile, writeFile } from "fs/promises";
51
+ import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
14
52
  import path from "path";
15
53
  import os from "os";
16
54
  function resolveDataDir() {
@@ -18,7 +56,7 @@ function resolveDataDir() {
18
56
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
19
57
  const newDir = path.join(os.homedir(), ".exe-os");
20
58
  const legacyDir = path.join(os.homedir(), ".exe-mem");
21
- if (!existsSync(newDir) && existsSync(legacyDir)) {
59
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
22
60
  try {
23
61
  renameSync(legacyDir, newDir);
24
62
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -81,9 +119,9 @@ function normalizeAutoUpdate(raw) {
81
119
  }
82
120
  async function loadConfig() {
83
121
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
84
- await mkdir(dir, { recursive: true });
122
+ await ensurePrivateDir(dir);
85
123
  const configPath = path.join(dir, "config.json");
86
- if (!existsSync(configPath)) {
124
+ if (!existsSync2(configPath)) {
87
125
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
88
126
  }
89
127
  const raw = await readFile(configPath, "utf-8");
@@ -96,6 +134,7 @@ async function loadConfig() {
96
134
  `);
97
135
  try {
98
136
  await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
137
+ await enforcePrivateFile(configPath);
99
138
  } catch {
100
139
  }
101
140
  }
@@ -115,6 +154,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
115
154
  var init_config = __esm({
116
155
  "src/lib/config.ts"() {
117
156
  "use strict";
157
+ init_secure_files();
118
158
  EXE_AI_DIR = resolveDataDir();
119
159
  DB_PATH = path.join(EXE_AI_DIR, "memories.db");
120
160
  MODELS_DIR = path.join(EXE_AI_DIR, "models");
@@ -193,7 +233,7 @@ var init_config = __esm({
193
233
 
194
234
  // src/lib/employees.ts
195
235
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
196
- import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
236
+ import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
197
237
  import { execSync as execSync2 } from "child_process";
198
238
  import path2 from "path";
199
239
  import os2 from "os";
@@ -210,7 +250,7 @@ function getCoordinatorName(employees = loadEmployeesSync()) {
210
250
  return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
211
251
  }
212
252
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
213
- if (!existsSync2(employeesPath)) return [];
253
+ if (!existsSync3(employeesPath)) return [];
214
254
  try {
215
255
  return JSON.parse(readFileSync2(employeesPath, "utf-8"));
216
256
  } catch {
@@ -220,7 +260,7 @@ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
220
260
  function getEmployee(employees, name) {
221
261
  return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
222
262
  }
223
- var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE;
263
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, IDENTITY_DIR;
224
264
  var init_employees = __esm({
225
265
  "src/lib/employees.ts"() {
226
266
  "use strict";
@@ -228,6 +268,7 @@ var init_employees = __esm({
228
268
  EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
229
269
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
230
270
  COORDINATOR_ROLE = "COO";
271
+ IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
231
272
  }
232
273
  });
233
274
 
@@ -295,13 +336,634 @@ var init_db_retry = __esm({
295
336
  }
296
337
  });
297
338
 
339
+ // src/lib/database-adapter.ts
340
+ import os3 from "os";
341
+ import path4 from "path";
342
+ import { createRequire } from "module";
343
+ import { pathToFileURL } from "url";
344
+ function quotedIdentifier(identifier) {
345
+ return `"${identifier.replace(/"/g, '""')}"`;
346
+ }
347
+ function unqualifiedTableName(name) {
348
+ const raw = name.trim().replace(/^"|"$/g, "");
349
+ const parts = raw.split(".");
350
+ return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
351
+ }
352
+ function stripTrailingSemicolon(sql) {
353
+ return sql.trim().replace(/;+\s*$/u, "");
354
+ }
355
+ function appendClause(sql, clause) {
356
+ const trimmed = stripTrailingSemicolon(sql);
357
+ const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
358
+ if (!returningMatch) {
359
+ return `${trimmed}${clause}`;
360
+ }
361
+ const idx = returningMatch.index;
362
+ return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
363
+ }
364
+ function normalizeStatement(stmt) {
365
+ if (typeof stmt === "string") {
366
+ return { kind: "positional", sql: stmt, args: [] };
367
+ }
368
+ const sql = stmt.sql;
369
+ if (Array.isArray(stmt.args) || stmt.args === void 0) {
370
+ return { kind: "positional", sql, args: stmt.args ?? [] };
371
+ }
372
+ return { kind: "named", sql, args: stmt.args };
373
+ }
374
+ function rewriteBooleanLiterals(sql) {
375
+ let out = sql;
376
+ for (const column of BOOLEAN_COLUMN_NAMES) {
377
+ const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
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
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
383
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
384
+ }
385
+ return out;
386
+ }
387
+ function rewriteInsertOrIgnore(sql) {
388
+ if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
389
+ return sql;
390
+ }
391
+ const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
392
+ return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
393
+ }
394
+ function rewriteInsertOrReplace(sql) {
395
+ const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
396
+ if (!match) {
397
+ return sql;
398
+ }
399
+ const rawTable = match[1];
400
+ const rawColumns = match[2];
401
+ const remainder = match[3];
402
+ const tableName = unqualifiedTableName(rawTable);
403
+ const conflictKeys = UPSERT_KEYS[tableName];
404
+ if (!conflictKeys?.length) {
405
+ return sql;
406
+ }
407
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
408
+ const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
409
+ const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
410
+ const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
411
+ return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
412
+ }
413
+ function rewriteSql(sql) {
414
+ let out = sql;
415
+ out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
416
+ out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
417
+ out = rewriteBooleanLiterals(out);
418
+ out = rewriteInsertOrReplace(out);
419
+ out = rewriteInsertOrIgnore(out);
420
+ return stripTrailingSemicolon(out);
421
+ }
422
+ function toBoolean(value) {
423
+ if (value === null || value === void 0) return value;
424
+ if (typeof value === "boolean") return value;
425
+ if (typeof value === "number") return value !== 0;
426
+ if (typeof value === "bigint") return value !== 0n;
427
+ if (typeof value === "string") {
428
+ const normalized = value.trim().toLowerCase();
429
+ if (normalized === "0" || normalized === "false") return false;
430
+ if (normalized === "1" || normalized === "true") return true;
431
+ }
432
+ return Boolean(value);
433
+ }
434
+ function countQuestionMarks(sql, end) {
435
+ let count = 0;
436
+ let inSingle = false;
437
+ let inDouble = false;
438
+ let inLineComment = false;
439
+ let inBlockComment = false;
440
+ for (let i = 0; i < end; i++) {
441
+ const ch = sql[i];
442
+ const next = sql[i + 1];
443
+ if (inLineComment) {
444
+ if (ch === "\n") inLineComment = false;
445
+ continue;
446
+ }
447
+ if (inBlockComment) {
448
+ if (ch === "*" && next === "/") {
449
+ inBlockComment = false;
450
+ i += 1;
451
+ }
452
+ continue;
453
+ }
454
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
455
+ inLineComment = true;
456
+ i += 1;
457
+ continue;
458
+ }
459
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
460
+ inBlockComment = true;
461
+ i += 1;
462
+ continue;
463
+ }
464
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
465
+ inSingle = !inSingle;
466
+ continue;
467
+ }
468
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
469
+ inDouble = !inDouble;
470
+ continue;
471
+ }
472
+ if (!inSingle && !inDouble && ch === "?") {
473
+ count += 1;
474
+ }
475
+ }
476
+ return count;
477
+ }
478
+ function findBooleanPlaceholderIndexes(sql) {
479
+ const indexes = /* @__PURE__ */ new Set();
480
+ for (const column of BOOLEAN_COLUMN_NAMES) {
481
+ const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
482
+ for (const match of sql.matchAll(pattern)) {
483
+ const matchText = match[0];
484
+ const qIndex = match.index + matchText.lastIndexOf("?");
485
+ indexes.add(countQuestionMarks(sql, qIndex + 1));
486
+ }
487
+ }
488
+ return indexes;
489
+ }
490
+ function coerceInsertBooleanArgs(sql, args) {
491
+ const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
492
+ if (!match) return;
493
+ const rawTable = match[1];
494
+ const rawColumns = match[2];
495
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
496
+ if (!boolColumns?.size) return;
497
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
498
+ for (const [index, column] of columns.entries()) {
499
+ if (boolColumns.has(column) && index < args.length) {
500
+ args[index] = toBoolean(args[index]);
501
+ }
502
+ }
503
+ }
504
+ function coerceUpdateBooleanArgs(sql, args) {
505
+ const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
506
+ if (!match) return;
507
+ const rawTable = match[1];
508
+ const setClause = match[2];
509
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
510
+ if (!boolColumns?.size) return;
511
+ const assignments = setClause.split(",");
512
+ let placeholderIndex = 0;
513
+ for (const assignment of assignments) {
514
+ if (!assignment.includes("?")) continue;
515
+ placeholderIndex += 1;
516
+ const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
517
+ if (colMatch && boolColumns.has(colMatch[1])) {
518
+ args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
519
+ }
520
+ }
521
+ }
522
+ function coerceBooleanArgs(sql, args) {
523
+ const nextArgs = [...args];
524
+ coerceInsertBooleanArgs(sql, nextArgs);
525
+ coerceUpdateBooleanArgs(sql, nextArgs);
526
+ const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
527
+ for (const index of placeholderIndexes) {
528
+ if (index > 0 && index <= nextArgs.length) {
529
+ nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
530
+ }
531
+ }
532
+ return nextArgs;
533
+ }
534
+ function convertQuestionMarksToDollarParams(sql) {
535
+ let out = "";
536
+ let placeholder = 0;
537
+ let inSingle = false;
538
+ let inDouble = false;
539
+ let inLineComment = false;
540
+ let inBlockComment = false;
541
+ for (let i = 0; i < sql.length; i++) {
542
+ const ch = sql[i];
543
+ const next = sql[i + 1];
544
+ if (inLineComment) {
545
+ out += ch;
546
+ if (ch === "\n") inLineComment = false;
547
+ continue;
548
+ }
549
+ if (inBlockComment) {
550
+ out += ch;
551
+ if (ch === "*" && next === "/") {
552
+ out += next;
553
+ inBlockComment = false;
554
+ i += 1;
555
+ }
556
+ continue;
557
+ }
558
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
559
+ out += ch + next;
560
+ inLineComment = true;
561
+ i += 1;
562
+ continue;
563
+ }
564
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
565
+ out += ch + next;
566
+ inBlockComment = true;
567
+ i += 1;
568
+ continue;
569
+ }
570
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
571
+ inSingle = !inSingle;
572
+ out += ch;
573
+ continue;
574
+ }
575
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
576
+ inDouble = !inDouble;
577
+ out += ch;
578
+ continue;
579
+ }
580
+ if (!inSingle && !inDouble && ch === "?") {
581
+ placeholder += 1;
582
+ out += `$${placeholder}`;
583
+ continue;
584
+ }
585
+ out += ch;
586
+ }
587
+ return out;
588
+ }
589
+ function translateStatementForPostgres(stmt) {
590
+ const normalized = normalizeStatement(stmt);
591
+ if (normalized.kind === "named") {
592
+ throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
593
+ }
594
+ const rewrittenSql = rewriteSql(normalized.sql);
595
+ const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
596
+ return {
597
+ sql: convertQuestionMarksToDollarParams(rewrittenSql),
598
+ args: coercedArgs
599
+ };
600
+ }
601
+ function shouldBypassPostgres(stmt) {
602
+ const normalized = normalizeStatement(stmt);
603
+ if (normalized.kind === "named") {
604
+ return true;
605
+ }
606
+ return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
607
+ }
608
+ function shouldFallbackOnError(error) {
609
+ const message = error instanceof Error ? error.message : String(error);
610
+ return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
611
+ }
612
+ function isReadQuery(sql) {
613
+ const trimmed = sql.trimStart();
614
+ return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
615
+ }
616
+ function buildRow(row, columns) {
617
+ const values = columns.map((column) => row[column]);
618
+ return Object.assign(values, row);
619
+ }
620
+ function buildResultSet(rows, rowsAffected = 0) {
621
+ const columns = rows[0] ? Object.keys(rows[0]) : [];
622
+ const resultRows = rows.map((row) => buildRow(row, columns));
623
+ return {
624
+ columns,
625
+ columnTypes: columns.map(() => ""),
626
+ rows: resultRows,
627
+ rowsAffected,
628
+ lastInsertRowid: void 0,
629
+ toJSON() {
630
+ return {
631
+ columns,
632
+ columnTypes: columns.map(() => ""),
633
+ rows,
634
+ rowsAffected,
635
+ lastInsertRowid: void 0
636
+ };
637
+ }
638
+ };
639
+ }
640
+ async function loadPrismaClient() {
641
+ if (!prismaClientPromise) {
642
+ prismaClientPromise = (async () => {
643
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
644
+ if (explicitPath) {
645
+ const module2 = await import(pathToFileURL(explicitPath).href);
646
+ const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
647
+ if (!PrismaClient2) {
648
+ throw new Error(`No PrismaClient export found at ${explicitPath}`);
649
+ }
650
+ return new PrismaClient2();
651
+ }
652
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path4.join(os3.homedir(), "exe-db");
653
+ const requireFromExeDb = createRequire(path4.join(exeDbRoot, "package.json"));
654
+ const prismaEntry = requireFromExeDb.resolve("@prisma/client");
655
+ const module = await import(pathToFileURL(prismaEntry).href);
656
+ const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
657
+ if (!PrismaClient) {
658
+ throw new Error(`No PrismaClient export found in ${prismaEntry}`);
659
+ }
660
+ return new PrismaClient();
661
+ })();
662
+ }
663
+ return prismaClientPromise;
664
+ }
665
+ async function ensureCompatibilityViews(prisma) {
666
+ if (!compatibilityBootstrapPromise) {
667
+ compatibilityBootstrapPromise = (async () => {
668
+ for (const mapping of VIEW_MAPPINGS) {
669
+ const relation = mapping.source.replace(/"/g, "");
670
+ const rows = await prisma.$queryRawUnsafe(
671
+ "SELECT to_regclass($1) AS regclass",
672
+ relation
673
+ );
674
+ if (!rows[0]?.regclass) {
675
+ continue;
676
+ }
677
+ await prisma.$executeRawUnsafe(
678
+ `CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
679
+ );
680
+ }
681
+ })();
682
+ }
683
+ return compatibilityBootstrapPromise;
684
+ }
685
+ async function executeOnPrisma(executor, stmt) {
686
+ const translated = translateStatementForPostgres(stmt);
687
+ if (isReadQuery(translated.sql)) {
688
+ const rows = await executor.$queryRawUnsafe(
689
+ translated.sql,
690
+ ...translated.args
691
+ );
692
+ return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
693
+ }
694
+ const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
695
+ return buildResultSet([], rowsAffected);
696
+ }
697
+ function splitSqlStatements(sql) {
698
+ const parts = [];
699
+ let current = "";
700
+ let inSingle = false;
701
+ let inDouble = false;
702
+ let inLineComment = false;
703
+ let inBlockComment = false;
704
+ for (let i = 0; i < sql.length; i++) {
705
+ const ch = sql[i];
706
+ const next = sql[i + 1];
707
+ if (inLineComment) {
708
+ current += ch;
709
+ if (ch === "\n") inLineComment = false;
710
+ continue;
711
+ }
712
+ if (inBlockComment) {
713
+ current += ch;
714
+ if (ch === "*" && next === "/") {
715
+ current += next;
716
+ inBlockComment = false;
717
+ i += 1;
718
+ }
719
+ continue;
720
+ }
721
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
722
+ current += ch + next;
723
+ inLineComment = true;
724
+ i += 1;
725
+ continue;
726
+ }
727
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
728
+ current += ch + next;
729
+ inBlockComment = true;
730
+ i += 1;
731
+ continue;
732
+ }
733
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
734
+ inSingle = !inSingle;
735
+ current += ch;
736
+ continue;
737
+ }
738
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
739
+ inDouble = !inDouble;
740
+ current += ch;
741
+ continue;
742
+ }
743
+ if (!inSingle && !inDouble && ch === ";") {
744
+ if (current.trim()) {
745
+ parts.push(current.trim());
746
+ }
747
+ current = "";
748
+ continue;
749
+ }
750
+ current += ch;
751
+ }
752
+ if (current.trim()) {
753
+ parts.push(current.trim());
754
+ }
755
+ return parts;
756
+ }
757
+ async function createPrismaDbAdapter(fallbackClient) {
758
+ const prisma = await loadPrismaClient();
759
+ await ensureCompatibilityViews(prisma);
760
+ let closed = false;
761
+ let adapter;
762
+ const fallbackExecute = async (stmt, error) => {
763
+ if (!fallbackClient) {
764
+ if (error) throw error;
765
+ throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
766
+ }
767
+ if (error) {
768
+ process.stderr.write(
769
+ `[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
770
+ `
771
+ );
772
+ }
773
+ return fallbackClient.execute(stmt);
774
+ };
775
+ adapter = {
776
+ async execute(stmt) {
777
+ if (shouldBypassPostgres(stmt)) {
778
+ return fallbackExecute(stmt);
779
+ }
780
+ try {
781
+ return await executeOnPrisma(prisma, stmt);
782
+ } catch (error) {
783
+ if (shouldFallbackOnError(error)) {
784
+ return fallbackExecute(stmt, error);
785
+ }
786
+ throw error;
787
+ }
788
+ },
789
+ async batch(stmts, mode) {
790
+ if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
791
+ if (!fallbackClient) {
792
+ throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
793
+ }
794
+ return fallbackClient.batch(stmts, mode);
795
+ }
796
+ try {
797
+ if (prisma.$transaction) {
798
+ return await prisma.$transaction(async (tx) => {
799
+ const results2 = [];
800
+ for (const stmt of stmts) {
801
+ results2.push(await executeOnPrisma(tx, stmt));
802
+ }
803
+ return results2;
804
+ });
805
+ }
806
+ const results = [];
807
+ for (const stmt of stmts) {
808
+ results.push(await executeOnPrisma(prisma, stmt));
809
+ }
810
+ return results;
811
+ } catch (error) {
812
+ if (fallbackClient && shouldFallbackOnError(error)) {
813
+ process.stderr.write(
814
+ `[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
815
+ `
816
+ );
817
+ return fallbackClient.batch(stmts, mode);
818
+ }
819
+ throw error;
820
+ }
821
+ },
822
+ async migrate(stmts) {
823
+ if (fallbackClient) {
824
+ return fallbackClient.migrate(stmts);
825
+ }
826
+ return adapter.batch(stmts, "deferred");
827
+ },
828
+ async transaction(mode) {
829
+ if (!fallbackClient) {
830
+ throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
831
+ }
832
+ return fallbackClient.transaction(mode);
833
+ },
834
+ async executeMultiple(sql) {
835
+ if (fallbackClient && shouldBypassPostgres(sql)) {
836
+ return fallbackClient.executeMultiple(sql);
837
+ }
838
+ for (const statement of splitSqlStatements(sql)) {
839
+ await adapter.execute(statement);
840
+ }
841
+ },
842
+ async sync() {
843
+ if (fallbackClient) {
844
+ return fallbackClient.sync();
845
+ }
846
+ return { frame_no: 0, frames_synced: 0 };
847
+ },
848
+ close() {
849
+ closed = true;
850
+ prismaClientPromise = null;
851
+ compatibilityBootstrapPromise = null;
852
+ void prisma.$disconnect?.();
853
+ },
854
+ get closed() {
855
+ return closed;
856
+ },
857
+ get protocol() {
858
+ return "prisma-postgres";
859
+ }
860
+ };
861
+ return adapter;
862
+ }
863
+ var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
864
+ var init_database_adapter = __esm({
865
+ "src/lib/database-adapter.ts"() {
866
+ "use strict";
867
+ VIEW_MAPPINGS = [
868
+ { view: "memories", source: "memory.memory_records" },
869
+ { view: "tasks", source: "memory.tasks" },
870
+ { view: "behaviors", source: "memory.behaviors" },
871
+ { view: "entities", source: "memory.entities" },
872
+ { view: "relationships", source: "memory.relationships" },
873
+ { view: "entity_memories", source: "memory.entity_memories" },
874
+ { view: "entity_aliases", source: "memory.entity_aliases" },
875
+ { view: "notifications", source: "memory.notifications" },
876
+ { view: "messages", source: "memory.messages" },
877
+ { view: "users", source: "wiki.users" },
878
+ { view: "workspaces", source: "wiki.workspaces" },
879
+ { view: "workspace_users", source: "wiki.workspace_users" },
880
+ { view: "documents", source: "wiki.workspace_documents" },
881
+ { view: "chats", source: "wiki.workspace_chats" }
882
+ ];
883
+ UPSERT_KEYS = {
884
+ memories: ["id"],
885
+ tasks: ["id"],
886
+ behaviors: ["id"],
887
+ entities: ["id"],
888
+ relationships: ["id"],
889
+ entity_aliases: ["alias"],
890
+ notifications: ["id"],
891
+ messages: ["id"],
892
+ users: ["id"],
893
+ workspaces: ["id"],
894
+ workspace_users: ["id"],
895
+ documents: ["id"],
896
+ chats: ["id"]
897
+ };
898
+ BOOLEAN_COLUMNS_BY_TABLE = {
899
+ memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
900
+ behaviors: /* @__PURE__ */ new Set(["active"]),
901
+ notifications: /* @__PURE__ */ new Set(["read"]),
902
+ users: /* @__PURE__ */ new Set(["has_personal_memory"])
903
+ };
904
+ BOOLEAN_COLUMN_NAMES = new Set(
905
+ Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
906
+ );
907
+ IMMEDIATE_FALLBACK_PATTERNS = [
908
+ /\bPRAGMA\b/i,
909
+ /\bsqlite_master\b/i,
910
+ /(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
911
+ /\bMATCH\b/i,
912
+ /\bvector_distance_cos\s*\(/i,
913
+ /\bjson_extract\s*\(/i,
914
+ /\bjulianday\s*\(/i,
915
+ /\bstrftime\s*\(/i,
916
+ /\blast_insert_rowid\s*\(/i
917
+ ];
918
+ prismaClientPromise = null;
919
+ compatibilityBootstrapPromise = null;
920
+ }
921
+ });
922
+
923
+ // src/lib/daemon-auth.ts
924
+ import crypto from "crypto";
925
+ import path5 from "path";
926
+ import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
927
+ function normalizeToken(token) {
928
+ if (!token) return null;
929
+ const trimmed = token.trim();
930
+ return trimmed.length > 0 ? trimmed : null;
931
+ }
932
+ function readDaemonToken() {
933
+ try {
934
+ if (!existsSync4(DAEMON_TOKEN_PATH)) return null;
935
+ return normalizeToken(readFileSync4(DAEMON_TOKEN_PATH, "utf8"));
936
+ } catch {
937
+ return null;
938
+ }
939
+ }
940
+ function ensureDaemonToken(seed) {
941
+ const existing = readDaemonToken();
942
+ if (existing) return existing;
943
+ const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
944
+ ensurePrivateDirSync(EXE_AI_DIR);
945
+ writeFileSync3(DAEMON_TOKEN_PATH, `${token}
946
+ `, "utf8");
947
+ enforcePrivateFileSync(DAEMON_TOKEN_PATH);
948
+ return token;
949
+ }
950
+ var DAEMON_TOKEN_PATH;
951
+ var init_daemon_auth = __esm({
952
+ "src/lib/daemon-auth.ts"() {
953
+ "use strict";
954
+ init_config();
955
+ init_secure_files();
956
+ DAEMON_TOKEN_PATH = path5.join(EXE_AI_DIR, "exed.token");
957
+ }
958
+ });
959
+
298
960
  // src/lib/exe-daemon-client.ts
299
961
  import net from "net";
300
- import os3 from "os";
962
+ import os4 from "os";
301
963
  import { spawn } from "child_process";
302
964
  import { randomUUID } from "crypto";
303
- import { existsSync as existsSync3, unlinkSync as unlinkSync3, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
304
- import path4 from "path";
965
+ import { existsSync as existsSync5, unlinkSync as unlinkSync3, readFileSync as readFileSync5, openSync, closeSync, statSync } from "fs";
966
+ import path6 from "path";
305
967
  import { fileURLToPath } from "url";
306
968
  function handleData(chunk) {
307
969
  _buffer += chunk.toString();
@@ -329,9 +991,9 @@ function handleData(chunk) {
329
991
  }
330
992
  }
331
993
  function cleanupStaleFiles() {
332
- if (existsSync3(PID_PATH)) {
994
+ if (existsSync5(PID_PATH)) {
333
995
  try {
334
- const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
996
+ const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
335
997
  if (pid > 0) {
336
998
  try {
337
999
  process.kill(pid, 0);
@@ -352,17 +1014,17 @@ function cleanupStaleFiles() {
352
1014
  }
353
1015
  }
354
1016
  function findPackageRoot() {
355
- let dir = path4.dirname(fileURLToPath(import.meta.url));
356
- const { root } = path4.parse(dir);
1017
+ let dir = path6.dirname(fileURLToPath(import.meta.url));
1018
+ const { root } = path6.parse(dir);
357
1019
  while (dir !== root) {
358
- if (existsSync3(path4.join(dir, "package.json"))) return dir;
359
- dir = path4.dirname(dir);
1020
+ if (existsSync5(path6.join(dir, "package.json"))) return dir;
1021
+ dir = path6.dirname(dir);
360
1022
  }
361
1023
  return null;
362
1024
  }
363
1025
  function spawnDaemon() {
364
- const freeGB = os3.freemem() / (1024 * 1024 * 1024);
365
- const totalGB = os3.totalmem() / (1024 * 1024 * 1024);
1026
+ const freeGB = os4.freemem() / (1024 * 1024 * 1024);
1027
+ const totalGB = os4.totalmem() / (1024 * 1024 * 1024);
366
1028
  if (totalGB <= 8) {
367
1029
  process.stderr.write(
368
1030
  `[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
@@ -382,16 +1044,17 @@ function spawnDaemon() {
382
1044
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
383
1045
  return;
384
1046
  }
385
- const daemonPath = path4.join(pkgRoot, "dist", "lib", "exe-daemon.js");
386
- if (!existsSync3(daemonPath)) {
1047
+ const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1048
+ if (!existsSync5(daemonPath)) {
387
1049
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
388
1050
  `);
389
1051
  return;
390
1052
  }
391
1053
  const resolvedPath = daemonPath;
1054
+ const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
392
1055
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
393
1056
  `);
394
- const logPath = path4.join(path4.dirname(SOCKET_PATH), "exed.log");
1057
+ const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
395
1058
  let stderrFd = "ignore";
396
1059
  try {
397
1060
  stderrFd = openSync(logPath, "a");
@@ -409,7 +1072,8 @@ function spawnDaemon() {
409
1072
  TMUX_PANE: void 0,
410
1073
  // Prevents resolveExeSession() from scoping to one session
411
1074
  EXE_DAEMON_SOCK: SOCKET_PATH,
412
- EXE_DAEMON_PID: PID_PATH
1075
+ EXE_DAEMON_PID: PID_PATH,
1076
+ [DAEMON_TOKEN_ENV]: daemonToken
413
1077
  }
414
1078
  });
415
1079
  child.unref();
@@ -516,13 +1180,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
516
1180
  return;
517
1181
  }
518
1182
  const id = randomUUID();
1183
+ const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
519
1184
  const timer = setTimeout(() => {
520
1185
  _pending.delete(id);
521
1186
  resolve({ error: "Request timeout" });
522
1187
  }, timeoutMs);
523
1188
  _pending.set(id, { resolve, timer });
524
1189
  try {
525
- _socket.write(JSON.stringify({ id, ...payload }) + "\n");
1190
+ _socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
526
1191
  } catch {
527
1192
  clearTimeout(timer);
528
1193
  _pending.delete(id);
@@ -533,17 +1198,19 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
533
1198
  function isClientConnected() {
534
1199
  return _connected;
535
1200
  }
536
- var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
1201
+ var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, DAEMON_TOKEN_ENV, _socket, _connected, _buffer, _pending, MAX_BUFFER;
537
1202
  var init_exe_daemon_client = __esm({
538
1203
  "src/lib/exe-daemon-client.ts"() {
539
1204
  "use strict";
540
1205
  init_config();
541
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path4.join(EXE_AI_DIR, "exed.sock");
542
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path4.join(EXE_AI_DIR, "exed.pid");
543
- SPAWN_LOCK_PATH = path4.join(EXE_AI_DIR, "exed-spawn.lock");
1206
+ init_daemon_auth();
1207
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
1208
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
1209
+ SPAWN_LOCK_PATH = path6.join(EXE_AI_DIR, "exed-spawn.lock");
544
1210
  SPAWN_LOCK_STALE_MS = 3e4;
545
1211
  CONNECT_TIMEOUT_MS = 15e3;
546
1212
  REQUEST_TIMEOUT_MS = 3e4;
1213
+ DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
547
1214
  _socket = null;
548
1215
  _connected = false;
549
1216
  _buffer = "";
@@ -622,7 +1289,7 @@ __export(db_daemon_client_exports, {
622
1289
  createDaemonDbClient: () => createDaemonDbClient,
623
1290
  initDaemonDbClient: () => initDaemonDbClient
624
1291
  });
625
- function normalizeStatement(stmt) {
1292
+ function normalizeStatement2(stmt) {
626
1293
  if (typeof stmt === "string") {
627
1294
  return { sql: stmt, args: [] };
628
1295
  }
@@ -646,7 +1313,7 @@ function createDaemonDbClient(fallbackClient) {
646
1313
  if (!_useDaemon || !isClientConnected()) {
647
1314
  return fallbackClient.execute(stmt);
648
1315
  }
649
- const { sql, args } = normalizeStatement(stmt);
1316
+ const { sql, args } = normalizeStatement2(stmt);
650
1317
  const response = await sendDaemonRequest({
651
1318
  type: "db-execute",
652
1319
  sql,
@@ -671,7 +1338,7 @@ function createDaemonDbClient(fallbackClient) {
671
1338
  if (!_useDaemon || !isClientConnected()) {
672
1339
  return fallbackClient.batch(stmts, mode);
673
1340
  }
674
- const statements = stmts.map(normalizeStatement);
1341
+ const statements = stmts.map(normalizeStatement2);
675
1342
  const response = await sendDaemonRequest({
676
1343
  type: "db-batch",
677
1344
  statements,
@@ -766,6 +1433,18 @@ __export(database_exports, {
766
1433
  });
767
1434
  import { createClient } from "@libsql/client";
768
1435
  async function initDatabase(config) {
1436
+ if (_walCheckpointTimer) {
1437
+ clearInterval(_walCheckpointTimer);
1438
+ _walCheckpointTimer = null;
1439
+ }
1440
+ if (_daemonClient) {
1441
+ _daemonClient.close();
1442
+ _daemonClient = null;
1443
+ }
1444
+ if (_adapterClient && _adapterClient !== _resilientClient) {
1445
+ _adapterClient.close();
1446
+ }
1447
+ _adapterClient = null;
769
1448
  if (_client) {
770
1449
  _client.close();
771
1450
  _client = null;
@@ -779,6 +1458,7 @@ async function initDatabase(config) {
779
1458
  }
780
1459
  _client = createClient(opts);
781
1460
  _resilientClient = wrapWithRetry(_client);
1461
+ _adapterClient = _resilientClient;
782
1462
  _client.execute("PRAGMA busy_timeout = 30000").catch(() => {
783
1463
  });
784
1464
  _client.execute("PRAGMA journal_mode = WAL").catch(() => {
@@ -789,14 +1469,20 @@ async function initDatabase(config) {
789
1469
  });
790
1470
  }, 3e4);
791
1471
  _walCheckpointTimer.unref();
1472
+ if (process.env.DATABASE_URL) {
1473
+ _adapterClient = await createPrismaDbAdapter(_resilientClient);
1474
+ }
792
1475
  }
793
1476
  function isInitialized() {
794
- return _client !== null;
1477
+ return _adapterClient !== null || _client !== null;
795
1478
  }
796
1479
  function getClient() {
797
- if (!_resilientClient) {
1480
+ if (!_adapterClient) {
798
1481
  throw new Error("Database client not initialized. Call initDatabase() first.");
799
1482
  }
1483
+ if (process.env.DATABASE_URL) {
1484
+ return _adapterClient;
1485
+ }
800
1486
  if (process.env.EXE_IS_DAEMON === "1") {
801
1487
  return _resilientClient;
802
1488
  }
@@ -806,6 +1492,7 @@ function getClient() {
806
1492
  return _resilientClient;
807
1493
  }
808
1494
  async function initDaemonClient() {
1495
+ if (process.env.DATABASE_URL) return;
809
1496
  if (process.env.EXE_IS_DAEMON === "1") return;
810
1497
  if (!_resilientClient) return;
811
1498
  try {
@@ -1102,6 +1789,7 @@ async function ensureSchema() {
1102
1789
  project TEXT NOT NULL,
1103
1790
  summary TEXT NOT NULL,
1104
1791
  task_file TEXT,
1792
+ session_scope TEXT,
1105
1793
  read INTEGER NOT NULL DEFAULT 0,
1106
1794
  created_at TEXT NOT NULL
1107
1795
  );
@@ -1110,7 +1798,7 @@ async function ensureSchema() {
1110
1798
  ON notifications(read);
1111
1799
 
1112
1800
  CREATE INDEX IF NOT EXISTS idx_notifications_agent
1113
- ON notifications(agent_id);
1801
+ ON notifications(agent_id, session_scope);
1114
1802
 
1115
1803
  CREATE INDEX IF NOT EXISTS idx_notifications_task_file
1116
1804
  ON notifications(task_file);
@@ -1148,6 +1836,7 @@ async function ensureSchema() {
1148
1836
  target_agent TEXT NOT NULL,
1149
1837
  target_project TEXT,
1150
1838
  target_device TEXT NOT NULL DEFAULT 'local',
1839
+ session_scope TEXT,
1151
1840
  content TEXT NOT NULL,
1152
1841
  priority TEXT DEFAULT 'normal',
1153
1842
  status TEXT DEFAULT 'pending',
@@ -1161,10 +1850,31 @@ async function ensureSchema() {
1161
1850
  );
1162
1851
 
1163
1852
  CREATE INDEX IF NOT EXISTS idx_messages_target
1164
- ON messages(target_agent, status);
1853
+ ON messages(target_agent, session_scope, status);
1165
1854
 
1166
1855
  CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
1167
- ON messages(target_agent, from_agent, server_seq);
1856
+ ON messages(target_agent, session_scope, from_agent, server_seq);
1857
+ `);
1858
+ try {
1859
+ await client.execute({
1860
+ sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
1861
+ args: []
1862
+ });
1863
+ } catch {
1864
+ }
1865
+ try {
1866
+ await client.execute({
1867
+ sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
1868
+ args: []
1869
+ });
1870
+ } catch {
1871
+ }
1872
+ await client.executeMultiple(`
1873
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
1874
+ ON notifications(agent_id, session_scope, read, created_at);
1875
+
1876
+ CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
1877
+ ON messages(target_agent, session_scope, status, created_at);
1168
1878
  `);
1169
1879
  try {
1170
1880
  await client.execute({
@@ -1748,28 +2458,45 @@ async function ensureSchema() {
1748
2458
  } catch {
1749
2459
  }
1750
2460
  }
2461
+ try {
2462
+ await client.execute({
2463
+ sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
2464
+ args: []
2465
+ });
2466
+ } catch {
2467
+ }
1751
2468
  }
1752
2469
  async function disposeDatabase() {
2470
+ if (_walCheckpointTimer) {
2471
+ clearInterval(_walCheckpointTimer);
2472
+ _walCheckpointTimer = null;
2473
+ }
1753
2474
  if (_daemonClient) {
1754
2475
  _daemonClient.close();
1755
2476
  _daemonClient = null;
1756
2477
  }
2478
+ if (_adapterClient && _adapterClient !== _resilientClient) {
2479
+ _adapterClient.close();
2480
+ }
2481
+ _adapterClient = null;
1757
2482
  if (_client) {
1758
2483
  _client.close();
1759
2484
  _client = null;
1760
2485
  _resilientClient = null;
1761
2486
  }
1762
2487
  }
1763
- var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso, disposeTurso;
2488
+ var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
1764
2489
  var init_database = __esm({
1765
2490
  "src/lib/database.ts"() {
1766
2491
  "use strict";
1767
2492
  init_db_retry();
1768
2493
  init_employees();
2494
+ init_database_adapter();
1769
2495
  _client = null;
1770
2496
  _resilientClient = null;
1771
2497
  _walCheckpointTimer = null;
1772
2498
  _daemonClient = null;
2499
+ _adapterClient = null;
1773
2500
  initTurso = initDatabase;
1774
2501
  disposeTurso = disposeDatabase;
1775
2502
  }
@@ -1777,14 +2504,14 @@ var init_database = __esm({
1777
2504
 
1778
2505
  // src/lib/keychain.ts
1779
2506
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
1780
- import { existsSync as existsSync4 } from "fs";
1781
- import path5 from "path";
1782
- import os4 from "os";
2507
+ import { existsSync as existsSync6 } from "fs";
2508
+ import path7 from "path";
2509
+ import os5 from "os";
1783
2510
  function getKeyDir() {
1784
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path5.join(os4.homedir(), ".exe-os");
2511
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path7.join(os5.homedir(), ".exe-os");
1785
2512
  }
1786
2513
  function getKeyPath() {
1787
- return path5.join(getKeyDir(), "master.key");
2514
+ return path7.join(getKeyDir(), "master.key");
1788
2515
  }
1789
2516
  async function tryKeytar() {
1790
2517
  try {
@@ -1805,9 +2532,9 @@ async function getMasterKey() {
1805
2532
  }
1806
2533
  }
1807
2534
  const keyPath = getKeyPath();
1808
- if (!existsSync4(keyPath)) {
2535
+ if (!existsSync6(keyPath)) {
1809
2536
  process.stderr.write(
1810
- `[keychain] Key not found at ${keyPath} (HOME=${os4.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
2537
+ `[keychain] Key not found at ${keyPath} (HOME=${os5.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
1811
2538
  `
1812
2539
  );
1813
2540
  return null;
@@ -1892,6 +2619,7 @@ var shard_manager_exports = {};
1892
2619
  __export(shard_manager_exports, {
1893
2620
  disposeShards: () => disposeShards,
1894
2621
  ensureShardSchema: () => ensureShardSchema,
2622
+ getOpenShardCount: () => getOpenShardCount,
1895
2623
  getReadyShardClient: () => getReadyShardClient,
1896
2624
  getShardClient: () => getShardClient,
1897
2625
  getShardsDir: () => getShardsDir,
@@ -1900,15 +2628,18 @@ __export(shard_manager_exports, {
1900
2628
  listShards: () => listShards,
1901
2629
  shardExists: () => shardExists
1902
2630
  });
1903
- import path6 from "path";
1904
- import { existsSync as existsSync5, mkdirSync as mkdirSync2, readdirSync as readdirSync2 } from "fs";
2631
+ import path8 from "path";
2632
+ import { existsSync as existsSync7, mkdirSync as mkdirSync3, readdirSync as readdirSync2 } from "fs";
1905
2633
  import { createClient as createClient2 } from "@libsql/client";
1906
2634
  function initShardManager(encryptionKey) {
1907
2635
  _encryptionKey = encryptionKey;
1908
- if (!existsSync5(SHARDS_DIR)) {
1909
- mkdirSync2(SHARDS_DIR, { recursive: true });
2636
+ if (!existsSync7(SHARDS_DIR)) {
2637
+ mkdirSync3(SHARDS_DIR, { recursive: true });
1910
2638
  }
1911
2639
  _shardingEnabled = true;
2640
+ if (_evictionTimer) clearInterval(_evictionTimer);
2641
+ _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
2642
+ _evictionTimer.unref();
1912
2643
  }
1913
2644
  function isShardingEnabled() {
1914
2645
  return _shardingEnabled;
@@ -1925,21 +2656,28 @@ function getShardClient(projectName) {
1925
2656
  throw new Error(`Invalid project name for shard: "${projectName}"`);
1926
2657
  }
1927
2658
  const cached = _shards.get(safeName);
1928
- if (cached) return cached;
1929
- const dbPath = path6.join(SHARDS_DIR, `${safeName}.db`);
2659
+ if (cached) {
2660
+ _shardLastAccess.set(safeName, Date.now());
2661
+ return cached;
2662
+ }
2663
+ while (_shards.size >= MAX_OPEN_SHARDS) {
2664
+ evictLRU();
2665
+ }
2666
+ const dbPath = path8.join(SHARDS_DIR, `${safeName}.db`);
1930
2667
  const client = createClient2({
1931
2668
  url: `file:${dbPath}`,
1932
2669
  encryptionKey: _encryptionKey
1933
2670
  });
1934
2671
  _shards.set(safeName, client);
2672
+ _shardLastAccess.set(safeName, Date.now());
1935
2673
  return client;
1936
2674
  }
1937
2675
  function shardExists(projectName) {
1938
2676
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
1939
- return existsSync5(path6.join(SHARDS_DIR, `${safeName}.db`));
2677
+ return existsSync7(path8.join(SHARDS_DIR, `${safeName}.db`));
1940
2678
  }
1941
2679
  function listShards() {
1942
- if (!existsSync5(SHARDS_DIR)) return [];
2680
+ if (!existsSync7(SHARDS_DIR)) return [];
1943
2681
  return readdirSync2(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
1944
2682
  }
1945
2683
  async function ensureShardSchema(client) {
@@ -1991,6 +2729,8 @@ async function ensureShardSchema(client) {
1991
2729
  for (const col of [
1992
2730
  "ALTER TABLE memories ADD COLUMN task_id TEXT",
1993
2731
  "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
2732
+ "ALTER TABLE memories ADD COLUMN author_device_id TEXT",
2733
+ "ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
1994
2734
  "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
1995
2735
  "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
1996
2736
  "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
@@ -2013,7 +2753,23 @@ async function ensureShardSchema(client) {
2013
2753
  // MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
2014
2754
  "ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
2015
2755
  "ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
2016
- "ALTER TABLE memories ADD COLUMN trajectory TEXT"
2756
+ "ALTER TABLE memories ADD COLUMN trajectory TEXT",
2757
+ // Metadata enrichment columns (must match database.ts)
2758
+ "ALTER TABLE memories ADD COLUMN intent TEXT",
2759
+ "ALTER TABLE memories ADD COLUMN outcome TEXT",
2760
+ "ALTER TABLE memories ADD COLUMN domain TEXT",
2761
+ "ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
2762
+ "ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
2763
+ "ALTER TABLE memories ADD COLUMN chain_position TEXT",
2764
+ "ALTER TABLE memories ADD COLUMN review_status TEXT",
2765
+ "ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
2766
+ "ALTER TABLE memories ADD COLUMN file_paths TEXT",
2767
+ "ALTER TABLE memories ADD COLUMN commit_hash TEXT",
2768
+ "ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
2769
+ "ALTER TABLE memories ADD COLUMN token_cost REAL",
2770
+ "ALTER TABLE memories ADD COLUMN audience TEXT",
2771
+ "ALTER TABLE memories ADD COLUMN language_type TEXT",
2772
+ "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
2017
2773
  ]) {
2018
2774
  try {
2019
2775
  await client.execute(col);
@@ -2112,21 +2868,69 @@ async function getReadyShardClient(projectName) {
2112
2868
  await ensureShardSchema(client);
2113
2869
  return client;
2114
2870
  }
2871
+ function evictLRU() {
2872
+ let oldest = null;
2873
+ let oldestTime = Infinity;
2874
+ for (const [name, time] of _shardLastAccess) {
2875
+ if (time < oldestTime) {
2876
+ oldestTime = time;
2877
+ oldest = name;
2878
+ }
2879
+ }
2880
+ if (oldest) {
2881
+ const client = _shards.get(oldest);
2882
+ if (client) {
2883
+ client.close();
2884
+ }
2885
+ _shards.delete(oldest);
2886
+ _shardLastAccess.delete(oldest);
2887
+ }
2888
+ }
2889
+ function evictIdleShards() {
2890
+ const now = Date.now();
2891
+ const toEvict = [];
2892
+ for (const [name, lastAccess] of _shardLastAccess) {
2893
+ if (now - lastAccess > SHARD_IDLE_MS) {
2894
+ toEvict.push(name);
2895
+ }
2896
+ }
2897
+ for (const name of toEvict) {
2898
+ const client = _shards.get(name);
2899
+ if (client) {
2900
+ client.close();
2901
+ }
2902
+ _shards.delete(name);
2903
+ _shardLastAccess.delete(name);
2904
+ }
2905
+ }
2906
+ function getOpenShardCount() {
2907
+ return _shards.size;
2908
+ }
2115
2909
  function disposeShards() {
2910
+ if (_evictionTimer) {
2911
+ clearInterval(_evictionTimer);
2912
+ _evictionTimer = null;
2913
+ }
2116
2914
  for (const [, client] of _shards) {
2117
2915
  client.close();
2118
2916
  }
2119
2917
  _shards.clear();
2918
+ _shardLastAccess.clear();
2120
2919
  _shardingEnabled = false;
2121
2920
  _encryptionKey = null;
2122
2921
  }
2123
- var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
2922
+ var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
2124
2923
  var init_shard_manager = __esm({
2125
2924
  "src/lib/shard-manager.ts"() {
2126
2925
  "use strict";
2127
2926
  init_config();
2128
- SHARDS_DIR = path6.join(EXE_AI_DIR, "shards");
2927
+ SHARDS_DIR = path8.join(EXE_AI_DIR, "shards");
2928
+ SHARD_IDLE_MS = 5 * 60 * 1e3;
2929
+ MAX_OPEN_SHARDS = 10;
2930
+ EVICTION_INTERVAL_MS = 60 * 1e3;
2129
2931
  _shards = /* @__PURE__ */ new Map();
2932
+ _shardLastAccess = /* @__PURE__ */ new Map();
2933
+ _evictionTimer = null;
2130
2934
  _encryptionKey = null;
2131
2935
  _shardingEnabled = false;
2132
2936
  }
@@ -2891,7 +3695,7 @@ var init_store = __esm({
2891
3695
 
2892
3696
  // src/lib/active-agent.ts
2893
3697
  init_config();
2894
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync, unlinkSync as unlinkSync2, readdirSync } from "fs";
3698
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2, readdirSync } from "fs";
2895
3699
  import { execSync as execSync3 } from "child_process";
2896
3700
  import path3 from "path";
2897
3701