@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
@@ -89,6 +89,44 @@ var init_db_retry = __esm({
89
89
  }
90
90
  });
91
91
 
92
+ // src/lib/secure-files.ts
93
+ import { chmodSync, existsSync, mkdirSync } from "fs";
94
+ import { chmod, mkdir } from "fs/promises";
95
+ async function ensurePrivateDir(dirPath) {
96
+ await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
97
+ try {
98
+ await chmod(dirPath, PRIVATE_DIR_MODE);
99
+ } catch {
100
+ }
101
+ }
102
+ function ensurePrivateDirSync(dirPath) {
103
+ mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
104
+ try {
105
+ chmodSync(dirPath, PRIVATE_DIR_MODE);
106
+ } catch {
107
+ }
108
+ }
109
+ async function enforcePrivateFile(filePath) {
110
+ try {
111
+ await chmod(filePath, PRIVATE_FILE_MODE);
112
+ } catch {
113
+ }
114
+ }
115
+ function enforcePrivateFileSync(filePath) {
116
+ try {
117
+ if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
118
+ } catch {
119
+ }
120
+ }
121
+ var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
122
+ var init_secure_files = __esm({
123
+ "src/lib/secure-files.ts"() {
124
+ "use strict";
125
+ PRIVATE_DIR_MODE = 448;
126
+ PRIVATE_FILE_MODE = 384;
127
+ }
128
+ });
129
+
92
130
  // src/lib/config.ts
93
131
  var config_exports = {};
94
132
  __export(config_exports, {
@@ -105,8 +143,8 @@ __export(config_exports, {
105
143
  migrateConfig: () => migrateConfig,
106
144
  saveConfig: () => saveConfig
107
145
  });
108
- import { readFile, writeFile, mkdir, chmod } from "fs/promises";
109
- import { readFileSync, existsSync, renameSync } from "fs";
146
+ import { readFile, writeFile } from "fs/promises";
147
+ import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
110
148
  import path from "path";
111
149
  import os from "os";
112
150
  function resolveDataDir() {
@@ -114,7 +152,7 @@ function resolveDataDir() {
114
152
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
115
153
  const newDir = path.join(os.homedir(), ".exe-os");
116
154
  const legacyDir = path.join(os.homedir(), ".exe-mem");
117
- if (!existsSync(newDir) && existsSync(legacyDir)) {
155
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
118
156
  try {
119
157
  renameSync(legacyDir, newDir);
120
158
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -177,9 +215,9 @@ function normalizeAutoUpdate(raw) {
177
215
  }
178
216
  async function loadConfig() {
179
217
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
180
- await mkdir(dir, { recursive: true });
218
+ await ensurePrivateDir(dir);
181
219
  const configPath = path.join(dir, "config.json");
182
- if (!existsSync(configPath)) {
220
+ if (!existsSync2(configPath)) {
183
221
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
184
222
  }
185
223
  const raw = await readFile(configPath, "utf-8");
@@ -192,6 +230,7 @@ async function loadConfig() {
192
230
  `);
193
231
  try {
194
232
  await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
233
+ await enforcePrivateFile(configPath);
195
234
  } catch {
196
235
  }
197
236
  }
@@ -210,7 +249,7 @@ async function loadConfig() {
210
249
  function loadConfigSync() {
211
250
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
212
251
  const configPath = path.join(dir, "config.json");
213
- if (!existsSync(configPath)) {
252
+ if (!existsSync2(configPath)) {
214
253
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
215
254
  }
216
255
  try {
@@ -228,12 +267,10 @@ function loadConfigSync() {
228
267
  }
229
268
  async function saveConfig(config) {
230
269
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
231
- await mkdir(dir, { recursive: true });
270
+ await ensurePrivateDir(dir);
232
271
  const configPath = path.join(dir, "config.json");
233
272
  await writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
234
- if (config.cloud?.apiKey) {
235
- await chmod(configPath, 384);
236
- }
273
+ await enforcePrivateFile(configPath);
237
274
  }
238
275
  async function loadConfigFrom(configPath) {
239
276
  const raw = await readFile(configPath, "utf-8");
@@ -253,6 +290,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
253
290
  var init_config = __esm({
254
291
  "src/lib/config.ts"() {
255
292
  "use strict";
293
+ init_secure_files();
256
294
  EXE_AI_DIR = resolveDataDir();
257
295
  DB_PATH = path.join(EXE_AI_DIR, "memories.db");
258
296
  MODELS_DIR = path.join(EXE_AI_DIR, "models");
@@ -331,7 +369,7 @@ var init_config = __esm({
331
369
 
332
370
  // src/lib/employees.ts
333
371
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
334
- import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
372
+ import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
335
373
  import { execSync } from "child_process";
336
374
  import path2 from "path";
337
375
  import os2 from "os";
@@ -355,7 +393,7 @@ function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
355
393
  return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
356
394
  }
357
395
  async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
358
- if (!existsSync2(employeesPath)) {
396
+ if (!existsSync3(employeesPath)) {
359
397
  return [];
360
398
  }
361
399
  const raw = await readFile2(employeesPath, "utf-8");
@@ -370,7 +408,7 @@ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
370
408
  await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
371
409
  }
372
410
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
373
- if (!existsSync2(employeesPath)) return [];
411
+ if (!existsSync3(employeesPath)) return [];
374
412
  try {
375
413
  return JSON.parse(readFileSync2(employeesPath, "utf-8"));
376
414
  } catch {
@@ -404,7 +442,7 @@ function registerBinSymlinks(name) {
404
442
  for (const suffix of ["", "-opencode"]) {
405
443
  const linkName = `${name}${suffix}`;
406
444
  const linkPath = path2.join(binDir, linkName);
407
- if (existsSync2(linkPath)) {
445
+ if (existsSync3(linkPath)) {
408
446
  skipped.push(linkName);
409
447
  continue;
410
448
  }
@@ -417,7 +455,7 @@ function registerBinSymlinks(name) {
417
455
  }
418
456
  return { created, skipped, errors };
419
457
  }
420
- var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE;
458
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, IDENTITY_DIR;
421
459
  var init_employees = __esm({
422
460
  "src/lib/employees.ts"() {
423
461
  "use strict";
@@ -425,16 +463,638 @@ var init_employees = __esm({
425
463
  EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
426
464
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
427
465
  COORDINATOR_ROLE = "COO";
466
+ IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
467
+ }
468
+ });
469
+
470
+ // src/lib/database-adapter.ts
471
+ import os3 from "os";
472
+ import path3 from "path";
473
+ import { createRequire } from "module";
474
+ import { pathToFileURL } from "url";
475
+ function quotedIdentifier(identifier) {
476
+ return `"${identifier.replace(/"/g, '""')}"`;
477
+ }
478
+ function unqualifiedTableName(name) {
479
+ const raw = name.trim().replace(/^"|"$/g, "");
480
+ const parts = raw.split(".");
481
+ return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
482
+ }
483
+ function stripTrailingSemicolon(sql) {
484
+ return sql.trim().replace(/;+\s*$/u, "");
485
+ }
486
+ function appendClause(sql, clause) {
487
+ const trimmed = stripTrailingSemicolon(sql);
488
+ const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
489
+ if (!returningMatch) {
490
+ return `${trimmed}${clause}`;
491
+ }
492
+ const idx = returningMatch.index;
493
+ return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
494
+ }
495
+ function normalizeStatement(stmt) {
496
+ if (typeof stmt === "string") {
497
+ return { kind: "positional", sql: stmt, args: [] };
498
+ }
499
+ const sql = stmt.sql;
500
+ if (Array.isArray(stmt.args) || stmt.args === void 0) {
501
+ return { kind: "positional", sql, args: stmt.args ?? [] };
502
+ }
503
+ return { kind: "named", sql, args: stmt.args };
504
+ }
505
+ function rewriteBooleanLiterals(sql) {
506
+ let out = sql;
507
+ for (const column of BOOLEAN_COLUMN_NAMES) {
508
+ const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
509
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
510
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
511
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
512
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
513
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
514
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
515
+ }
516
+ return out;
517
+ }
518
+ function rewriteInsertOrIgnore(sql) {
519
+ if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
520
+ return sql;
521
+ }
522
+ const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
523
+ return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
524
+ }
525
+ function rewriteInsertOrReplace(sql) {
526
+ const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
527
+ if (!match) {
528
+ return sql;
529
+ }
530
+ const rawTable = match[1];
531
+ const rawColumns = match[2];
532
+ const remainder = match[3];
533
+ const tableName = unqualifiedTableName(rawTable);
534
+ const conflictKeys = UPSERT_KEYS[tableName];
535
+ if (!conflictKeys?.length) {
536
+ return sql;
537
+ }
538
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
539
+ const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
540
+ const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
541
+ const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
542
+ return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
543
+ }
544
+ function rewriteSql(sql) {
545
+ let out = sql;
546
+ out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
547
+ out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
548
+ out = rewriteBooleanLiterals(out);
549
+ out = rewriteInsertOrReplace(out);
550
+ out = rewriteInsertOrIgnore(out);
551
+ return stripTrailingSemicolon(out);
552
+ }
553
+ function toBoolean(value) {
554
+ if (value === null || value === void 0) return value;
555
+ if (typeof value === "boolean") return value;
556
+ if (typeof value === "number") return value !== 0;
557
+ if (typeof value === "bigint") return value !== 0n;
558
+ if (typeof value === "string") {
559
+ const normalized = value.trim().toLowerCase();
560
+ if (normalized === "0" || normalized === "false") return false;
561
+ if (normalized === "1" || normalized === "true") return true;
562
+ }
563
+ return Boolean(value);
564
+ }
565
+ function countQuestionMarks(sql, end) {
566
+ let count = 0;
567
+ let inSingle = false;
568
+ let inDouble = false;
569
+ let inLineComment = false;
570
+ let inBlockComment = false;
571
+ for (let i = 0; i < end; i++) {
572
+ const ch = sql[i];
573
+ const next = sql[i + 1];
574
+ if (inLineComment) {
575
+ if (ch === "\n") inLineComment = false;
576
+ continue;
577
+ }
578
+ if (inBlockComment) {
579
+ if (ch === "*" && next === "/") {
580
+ inBlockComment = false;
581
+ i += 1;
582
+ }
583
+ continue;
584
+ }
585
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
586
+ inLineComment = true;
587
+ i += 1;
588
+ continue;
589
+ }
590
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
591
+ inBlockComment = true;
592
+ i += 1;
593
+ continue;
594
+ }
595
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
596
+ inSingle = !inSingle;
597
+ continue;
598
+ }
599
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
600
+ inDouble = !inDouble;
601
+ continue;
602
+ }
603
+ if (!inSingle && !inDouble && ch === "?") {
604
+ count += 1;
605
+ }
606
+ }
607
+ return count;
608
+ }
609
+ function findBooleanPlaceholderIndexes(sql) {
610
+ const indexes = /* @__PURE__ */ new Set();
611
+ for (const column of BOOLEAN_COLUMN_NAMES) {
612
+ const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
613
+ for (const match of sql.matchAll(pattern)) {
614
+ const matchText = match[0];
615
+ const qIndex = match.index + matchText.lastIndexOf("?");
616
+ indexes.add(countQuestionMarks(sql, qIndex + 1));
617
+ }
618
+ }
619
+ return indexes;
620
+ }
621
+ function coerceInsertBooleanArgs(sql, args) {
622
+ const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
623
+ if (!match) return;
624
+ const rawTable = match[1];
625
+ const rawColumns = match[2];
626
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
627
+ if (!boolColumns?.size) return;
628
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
629
+ for (const [index, column] of columns.entries()) {
630
+ if (boolColumns.has(column) && index < args.length) {
631
+ args[index] = toBoolean(args[index]);
632
+ }
633
+ }
634
+ }
635
+ function coerceUpdateBooleanArgs(sql, args) {
636
+ const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
637
+ if (!match) return;
638
+ const rawTable = match[1];
639
+ const setClause = match[2];
640
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
641
+ if (!boolColumns?.size) return;
642
+ const assignments = setClause.split(",");
643
+ let placeholderIndex = 0;
644
+ for (const assignment of assignments) {
645
+ if (!assignment.includes("?")) continue;
646
+ placeholderIndex += 1;
647
+ const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
648
+ if (colMatch && boolColumns.has(colMatch[1])) {
649
+ args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
650
+ }
651
+ }
652
+ }
653
+ function coerceBooleanArgs(sql, args) {
654
+ const nextArgs = [...args];
655
+ coerceInsertBooleanArgs(sql, nextArgs);
656
+ coerceUpdateBooleanArgs(sql, nextArgs);
657
+ const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
658
+ for (const index of placeholderIndexes) {
659
+ if (index > 0 && index <= nextArgs.length) {
660
+ nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
661
+ }
662
+ }
663
+ return nextArgs;
664
+ }
665
+ function convertQuestionMarksToDollarParams(sql) {
666
+ let out = "";
667
+ let placeholder = 0;
668
+ let inSingle = false;
669
+ let inDouble = false;
670
+ let inLineComment = false;
671
+ let inBlockComment = false;
672
+ for (let i = 0; i < sql.length; i++) {
673
+ const ch = sql[i];
674
+ const next = sql[i + 1];
675
+ if (inLineComment) {
676
+ out += ch;
677
+ if (ch === "\n") inLineComment = false;
678
+ continue;
679
+ }
680
+ if (inBlockComment) {
681
+ out += ch;
682
+ if (ch === "*" && next === "/") {
683
+ out += next;
684
+ inBlockComment = false;
685
+ i += 1;
686
+ }
687
+ continue;
688
+ }
689
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
690
+ out += ch + next;
691
+ inLineComment = true;
692
+ i += 1;
693
+ continue;
694
+ }
695
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
696
+ out += ch + next;
697
+ inBlockComment = true;
698
+ i += 1;
699
+ continue;
700
+ }
701
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
702
+ inSingle = !inSingle;
703
+ out += ch;
704
+ continue;
705
+ }
706
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
707
+ inDouble = !inDouble;
708
+ out += ch;
709
+ continue;
710
+ }
711
+ if (!inSingle && !inDouble && ch === "?") {
712
+ placeholder += 1;
713
+ out += `$${placeholder}`;
714
+ continue;
715
+ }
716
+ out += ch;
717
+ }
718
+ return out;
719
+ }
720
+ function translateStatementForPostgres(stmt) {
721
+ const normalized = normalizeStatement(stmt);
722
+ if (normalized.kind === "named") {
723
+ throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
724
+ }
725
+ const rewrittenSql = rewriteSql(normalized.sql);
726
+ const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
727
+ return {
728
+ sql: convertQuestionMarksToDollarParams(rewrittenSql),
729
+ args: coercedArgs
730
+ };
731
+ }
732
+ function shouldBypassPostgres(stmt) {
733
+ const normalized = normalizeStatement(stmt);
734
+ if (normalized.kind === "named") {
735
+ return true;
736
+ }
737
+ return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
738
+ }
739
+ function shouldFallbackOnError(error) {
740
+ const message = error instanceof Error ? error.message : String(error);
741
+ return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
742
+ }
743
+ function isReadQuery(sql) {
744
+ const trimmed = sql.trimStart();
745
+ return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
746
+ }
747
+ function buildRow(row, columns) {
748
+ const values = columns.map((column) => row[column]);
749
+ return Object.assign(values, row);
750
+ }
751
+ function buildResultSet(rows, rowsAffected = 0) {
752
+ const columns = rows[0] ? Object.keys(rows[0]) : [];
753
+ const resultRows = rows.map((row) => buildRow(row, columns));
754
+ return {
755
+ columns,
756
+ columnTypes: columns.map(() => ""),
757
+ rows: resultRows,
758
+ rowsAffected,
759
+ lastInsertRowid: void 0,
760
+ toJSON() {
761
+ return {
762
+ columns,
763
+ columnTypes: columns.map(() => ""),
764
+ rows,
765
+ rowsAffected,
766
+ lastInsertRowid: void 0
767
+ };
768
+ }
769
+ };
770
+ }
771
+ async function loadPrismaClient() {
772
+ if (!prismaClientPromise) {
773
+ prismaClientPromise = (async () => {
774
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
775
+ if (explicitPath) {
776
+ const module2 = await import(pathToFileURL(explicitPath).href);
777
+ const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
778
+ if (!PrismaClient2) {
779
+ throw new Error(`No PrismaClient export found at ${explicitPath}`);
780
+ }
781
+ return new PrismaClient2();
782
+ }
783
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path3.join(os3.homedir(), "exe-db");
784
+ const requireFromExeDb = createRequire(path3.join(exeDbRoot, "package.json"));
785
+ const prismaEntry = requireFromExeDb.resolve("@prisma/client");
786
+ const module = await import(pathToFileURL(prismaEntry).href);
787
+ const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
788
+ if (!PrismaClient) {
789
+ throw new Error(`No PrismaClient export found in ${prismaEntry}`);
790
+ }
791
+ return new PrismaClient();
792
+ })();
793
+ }
794
+ return prismaClientPromise;
795
+ }
796
+ async function ensureCompatibilityViews(prisma) {
797
+ if (!compatibilityBootstrapPromise) {
798
+ compatibilityBootstrapPromise = (async () => {
799
+ for (const mapping of VIEW_MAPPINGS) {
800
+ const relation = mapping.source.replace(/"/g, "");
801
+ const rows = await prisma.$queryRawUnsafe(
802
+ "SELECT to_regclass($1) AS regclass",
803
+ relation
804
+ );
805
+ if (!rows[0]?.regclass) {
806
+ continue;
807
+ }
808
+ await prisma.$executeRawUnsafe(
809
+ `CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
810
+ );
811
+ }
812
+ })();
813
+ }
814
+ return compatibilityBootstrapPromise;
815
+ }
816
+ async function executeOnPrisma(executor, stmt) {
817
+ const translated = translateStatementForPostgres(stmt);
818
+ if (isReadQuery(translated.sql)) {
819
+ const rows = await executor.$queryRawUnsafe(
820
+ translated.sql,
821
+ ...translated.args
822
+ );
823
+ return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
824
+ }
825
+ const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
826
+ return buildResultSet([], rowsAffected);
827
+ }
828
+ function splitSqlStatements(sql) {
829
+ const parts = [];
830
+ let current = "";
831
+ let inSingle = false;
832
+ let inDouble = false;
833
+ let inLineComment = false;
834
+ let inBlockComment = false;
835
+ for (let i = 0; i < sql.length; i++) {
836
+ const ch = sql[i];
837
+ const next = sql[i + 1];
838
+ if (inLineComment) {
839
+ current += ch;
840
+ if (ch === "\n") inLineComment = false;
841
+ continue;
842
+ }
843
+ if (inBlockComment) {
844
+ current += ch;
845
+ if (ch === "*" && next === "/") {
846
+ current += next;
847
+ inBlockComment = false;
848
+ i += 1;
849
+ }
850
+ continue;
851
+ }
852
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
853
+ current += ch + next;
854
+ inLineComment = true;
855
+ i += 1;
856
+ continue;
857
+ }
858
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
859
+ current += ch + next;
860
+ inBlockComment = true;
861
+ i += 1;
862
+ continue;
863
+ }
864
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
865
+ inSingle = !inSingle;
866
+ current += ch;
867
+ continue;
868
+ }
869
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
870
+ inDouble = !inDouble;
871
+ current += ch;
872
+ continue;
873
+ }
874
+ if (!inSingle && !inDouble && ch === ";") {
875
+ if (current.trim()) {
876
+ parts.push(current.trim());
877
+ }
878
+ current = "";
879
+ continue;
880
+ }
881
+ current += ch;
882
+ }
883
+ if (current.trim()) {
884
+ parts.push(current.trim());
885
+ }
886
+ return parts;
887
+ }
888
+ async function createPrismaDbAdapter(fallbackClient) {
889
+ const prisma = await loadPrismaClient();
890
+ await ensureCompatibilityViews(prisma);
891
+ let closed = false;
892
+ let adapter;
893
+ const fallbackExecute = async (stmt, error) => {
894
+ if (!fallbackClient) {
895
+ if (error) throw error;
896
+ throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
897
+ }
898
+ if (error) {
899
+ process.stderr.write(
900
+ `[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
901
+ `
902
+ );
903
+ }
904
+ return fallbackClient.execute(stmt);
905
+ };
906
+ adapter = {
907
+ async execute(stmt) {
908
+ if (shouldBypassPostgres(stmt)) {
909
+ return fallbackExecute(stmt);
910
+ }
911
+ try {
912
+ return await executeOnPrisma(prisma, stmt);
913
+ } catch (error) {
914
+ if (shouldFallbackOnError(error)) {
915
+ return fallbackExecute(stmt, error);
916
+ }
917
+ throw error;
918
+ }
919
+ },
920
+ async batch(stmts, mode) {
921
+ if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
922
+ if (!fallbackClient) {
923
+ throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
924
+ }
925
+ return fallbackClient.batch(stmts, mode);
926
+ }
927
+ try {
928
+ if (prisma.$transaction) {
929
+ return await prisma.$transaction(async (tx) => {
930
+ const results2 = [];
931
+ for (const stmt of stmts) {
932
+ results2.push(await executeOnPrisma(tx, stmt));
933
+ }
934
+ return results2;
935
+ });
936
+ }
937
+ const results = [];
938
+ for (const stmt of stmts) {
939
+ results.push(await executeOnPrisma(prisma, stmt));
940
+ }
941
+ return results;
942
+ } catch (error) {
943
+ if (fallbackClient && shouldFallbackOnError(error)) {
944
+ process.stderr.write(
945
+ `[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
946
+ `
947
+ );
948
+ return fallbackClient.batch(stmts, mode);
949
+ }
950
+ throw error;
951
+ }
952
+ },
953
+ async migrate(stmts) {
954
+ if (fallbackClient) {
955
+ return fallbackClient.migrate(stmts);
956
+ }
957
+ return adapter.batch(stmts, "deferred");
958
+ },
959
+ async transaction(mode) {
960
+ if (!fallbackClient) {
961
+ throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
962
+ }
963
+ return fallbackClient.transaction(mode);
964
+ },
965
+ async executeMultiple(sql) {
966
+ if (fallbackClient && shouldBypassPostgres(sql)) {
967
+ return fallbackClient.executeMultiple(sql);
968
+ }
969
+ for (const statement of splitSqlStatements(sql)) {
970
+ await adapter.execute(statement);
971
+ }
972
+ },
973
+ async sync() {
974
+ if (fallbackClient) {
975
+ return fallbackClient.sync();
976
+ }
977
+ return { frame_no: 0, frames_synced: 0 };
978
+ },
979
+ close() {
980
+ closed = true;
981
+ prismaClientPromise = null;
982
+ compatibilityBootstrapPromise = null;
983
+ void prisma.$disconnect?.();
984
+ },
985
+ get closed() {
986
+ return closed;
987
+ },
988
+ get protocol() {
989
+ return "prisma-postgres";
990
+ }
991
+ };
992
+ return adapter;
993
+ }
994
+ var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
995
+ var init_database_adapter = __esm({
996
+ "src/lib/database-adapter.ts"() {
997
+ "use strict";
998
+ VIEW_MAPPINGS = [
999
+ { view: "memories", source: "memory.memory_records" },
1000
+ { view: "tasks", source: "memory.tasks" },
1001
+ { view: "behaviors", source: "memory.behaviors" },
1002
+ { view: "entities", source: "memory.entities" },
1003
+ { view: "relationships", source: "memory.relationships" },
1004
+ { view: "entity_memories", source: "memory.entity_memories" },
1005
+ { view: "entity_aliases", source: "memory.entity_aliases" },
1006
+ { view: "notifications", source: "memory.notifications" },
1007
+ { view: "messages", source: "memory.messages" },
1008
+ { view: "users", source: "wiki.users" },
1009
+ { view: "workspaces", source: "wiki.workspaces" },
1010
+ { view: "workspace_users", source: "wiki.workspace_users" },
1011
+ { view: "documents", source: "wiki.workspace_documents" },
1012
+ { view: "chats", source: "wiki.workspace_chats" }
1013
+ ];
1014
+ UPSERT_KEYS = {
1015
+ memories: ["id"],
1016
+ tasks: ["id"],
1017
+ behaviors: ["id"],
1018
+ entities: ["id"],
1019
+ relationships: ["id"],
1020
+ entity_aliases: ["alias"],
1021
+ notifications: ["id"],
1022
+ messages: ["id"],
1023
+ users: ["id"],
1024
+ workspaces: ["id"],
1025
+ workspace_users: ["id"],
1026
+ documents: ["id"],
1027
+ chats: ["id"]
1028
+ };
1029
+ BOOLEAN_COLUMNS_BY_TABLE = {
1030
+ memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
1031
+ behaviors: /* @__PURE__ */ new Set(["active"]),
1032
+ notifications: /* @__PURE__ */ new Set(["read"]),
1033
+ users: /* @__PURE__ */ new Set(["has_personal_memory"])
1034
+ };
1035
+ BOOLEAN_COLUMN_NAMES = new Set(
1036
+ Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
1037
+ );
1038
+ IMMEDIATE_FALLBACK_PATTERNS = [
1039
+ /\bPRAGMA\b/i,
1040
+ /\bsqlite_master\b/i,
1041
+ /(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
1042
+ /\bMATCH\b/i,
1043
+ /\bvector_distance_cos\s*\(/i,
1044
+ /\bjson_extract\s*\(/i,
1045
+ /\bjulianday\s*\(/i,
1046
+ /\bstrftime\s*\(/i,
1047
+ /\blast_insert_rowid\s*\(/i
1048
+ ];
1049
+ prismaClientPromise = null;
1050
+ compatibilityBootstrapPromise = null;
1051
+ }
1052
+ });
1053
+
1054
+ // src/lib/daemon-auth.ts
1055
+ import crypto from "crypto";
1056
+ import path4 from "path";
1057
+ import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
1058
+ function normalizeToken(token) {
1059
+ if (!token) return null;
1060
+ const trimmed = token.trim();
1061
+ return trimmed.length > 0 ? trimmed : null;
1062
+ }
1063
+ function readDaemonToken() {
1064
+ try {
1065
+ if (!existsSync4(DAEMON_TOKEN_PATH)) return null;
1066
+ return normalizeToken(readFileSync3(DAEMON_TOKEN_PATH, "utf8"));
1067
+ } catch {
1068
+ return null;
1069
+ }
1070
+ }
1071
+ function ensureDaemonToken(seed) {
1072
+ const existing = readDaemonToken();
1073
+ if (existing) return existing;
1074
+ const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
1075
+ ensurePrivateDirSync(EXE_AI_DIR);
1076
+ writeFileSync2(DAEMON_TOKEN_PATH, `${token}
1077
+ `, "utf8");
1078
+ enforcePrivateFileSync(DAEMON_TOKEN_PATH);
1079
+ return token;
1080
+ }
1081
+ var DAEMON_TOKEN_PATH;
1082
+ var init_daemon_auth = __esm({
1083
+ "src/lib/daemon-auth.ts"() {
1084
+ "use strict";
1085
+ init_config();
1086
+ init_secure_files();
1087
+ DAEMON_TOKEN_PATH = path4.join(EXE_AI_DIR, "exed.token");
428
1088
  }
429
1089
  });
430
1090
 
431
1091
  // src/lib/exe-daemon-client.ts
432
1092
  import net from "net";
433
- import os3 from "os";
1093
+ import os4 from "os";
434
1094
  import { spawn } from "child_process";
435
1095
  import { randomUUID } from "crypto";
436
- import { existsSync as existsSync3, unlinkSync as unlinkSync2, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
437
- import path3 from "path";
1096
+ import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
1097
+ import path5 from "path";
438
1098
  import { fileURLToPath } from "url";
439
1099
  function handleData(chunk) {
440
1100
  _buffer += chunk.toString();
@@ -462,9 +1122,9 @@ function handleData(chunk) {
462
1122
  }
463
1123
  }
464
1124
  function cleanupStaleFiles() {
465
- if (existsSync3(PID_PATH)) {
1125
+ if (existsSync5(PID_PATH)) {
466
1126
  try {
467
- const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
1127
+ const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
468
1128
  if (pid > 0) {
469
1129
  try {
470
1130
  process.kill(pid, 0);
@@ -485,17 +1145,17 @@ function cleanupStaleFiles() {
485
1145
  }
486
1146
  }
487
1147
  function findPackageRoot() {
488
- let dir = path3.dirname(fileURLToPath(import.meta.url));
489
- const { root } = path3.parse(dir);
1148
+ let dir = path5.dirname(fileURLToPath(import.meta.url));
1149
+ const { root } = path5.parse(dir);
490
1150
  while (dir !== root) {
491
- if (existsSync3(path3.join(dir, "package.json"))) return dir;
492
- dir = path3.dirname(dir);
1151
+ if (existsSync5(path5.join(dir, "package.json"))) return dir;
1152
+ dir = path5.dirname(dir);
493
1153
  }
494
1154
  return null;
495
1155
  }
496
1156
  function spawnDaemon() {
497
- const freeGB = os3.freemem() / (1024 * 1024 * 1024);
498
- const totalGB = os3.totalmem() / (1024 * 1024 * 1024);
1157
+ const freeGB = os4.freemem() / (1024 * 1024 * 1024);
1158
+ const totalGB = os4.totalmem() / (1024 * 1024 * 1024);
499
1159
  if (totalGB <= 8) {
500
1160
  process.stderr.write(
501
1161
  `[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
@@ -515,16 +1175,17 @@ function spawnDaemon() {
515
1175
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
516
1176
  return;
517
1177
  }
518
- const daemonPath = path3.join(pkgRoot, "dist", "lib", "exe-daemon.js");
519
- if (!existsSync3(daemonPath)) {
1178
+ const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1179
+ if (!existsSync5(daemonPath)) {
520
1180
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
521
1181
  `);
522
1182
  return;
523
1183
  }
524
1184
  const resolvedPath = daemonPath;
1185
+ const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
525
1186
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
526
1187
  `);
527
- const logPath = path3.join(path3.dirname(SOCKET_PATH), "exed.log");
1188
+ const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
528
1189
  let stderrFd = "ignore";
529
1190
  try {
530
1191
  stderrFd = openSync(logPath, "a");
@@ -542,7 +1203,8 @@ function spawnDaemon() {
542
1203
  TMUX_PANE: void 0,
543
1204
  // Prevents resolveExeSession() from scoping to one session
544
1205
  EXE_DAEMON_SOCK: SOCKET_PATH,
545
- EXE_DAEMON_PID: PID_PATH
1206
+ EXE_DAEMON_PID: PID_PATH,
1207
+ [DAEMON_TOKEN_ENV]: daemonToken
546
1208
  }
547
1209
  });
548
1210
  child.unref();
@@ -652,13 +1314,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
652
1314
  return;
653
1315
  }
654
1316
  const id = randomUUID();
1317
+ const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
655
1318
  const timer = setTimeout(() => {
656
1319
  _pending.delete(id);
657
1320
  resolve({ error: "Request timeout" });
658
1321
  }, timeoutMs);
659
1322
  _pending.set(id, { resolve, timer });
660
1323
  try {
661
- _socket.write(JSON.stringify({ id, ...payload }) + "\n");
1324
+ _socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
662
1325
  } catch {
663
1326
  clearTimeout(timer);
664
1327
  _pending.delete(id);
@@ -675,74 +1338,123 @@ async function pingDaemon() {
675
1338
  return null;
676
1339
  }
677
1340
  function killAndRespawnDaemon() {
678
- process.stderr.write("[exed-client] Killing daemon for restart...\n");
679
- if (existsSync3(PID_PATH)) {
680
- try {
681
- const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
682
- if (pid > 0) {
683
- try {
684
- process.kill(pid, "SIGKILL");
685
- } catch {
1341
+ if (!acquireSpawnLock()) {
1342
+ process.stderr.write("[exed-client] Another process is already restarting daemon \u2014 skipping\n");
1343
+ if (_socket) {
1344
+ _socket.destroy();
1345
+ _socket = null;
1346
+ }
1347
+ _connected = false;
1348
+ _buffer = "";
1349
+ return;
1350
+ }
1351
+ try {
1352
+ process.stderr.write("[exed-client] Killing daemon for restart...\n");
1353
+ if (existsSync5(PID_PATH)) {
1354
+ try {
1355
+ const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
1356
+ if (pid > 0) {
1357
+ try {
1358
+ process.kill(pid, "SIGKILL");
1359
+ } catch {
1360
+ }
686
1361
  }
1362
+ } catch {
687
1363
  }
1364
+ }
1365
+ if (_socket) {
1366
+ _socket.destroy();
1367
+ _socket = null;
1368
+ }
1369
+ _connected = false;
1370
+ _buffer = "";
1371
+ try {
1372
+ unlinkSync2(PID_PATH);
688
1373
  } catch {
689
1374
  }
1375
+ try {
1376
+ unlinkSync2(SOCKET_PATH);
1377
+ } catch {
1378
+ }
1379
+ spawnDaemon();
1380
+ } finally {
1381
+ releaseSpawnLock();
690
1382
  }
691
- if (_socket) {
692
- _socket.destroy();
693
- _socket = null;
694
- }
695
- _connected = false;
696
- _buffer = "";
1383
+ }
1384
+ function isDaemonTooYoung() {
697
1385
  try {
698
- unlinkSync2(PID_PATH);
1386
+ const stat = statSync(PID_PATH);
1387
+ return Date.now() - stat.mtimeMs < MIN_DAEMON_AGE_MS;
699
1388
  } catch {
1389
+ return false;
700
1390
  }
701
- try {
702
- unlinkSync2(SOCKET_PATH);
703
- } catch {
1391
+ }
1392
+ async function retryThenRestart(doRequest, label) {
1393
+ const result = await doRequest();
1394
+ if (!result.error) {
1395
+ _consecutiveFailures = 0;
1396
+ return result;
704
1397
  }
705
- spawnDaemon();
1398
+ _consecutiveFailures++;
1399
+ for (let i = 0; i < MAX_RETRIES_BEFORE_RESTART; i++) {
1400
+ const delayMs = RETRY_DELAYS_MS[i] ?? 5e3;
1401
+ process.stderr.write(`[exed-client] ${label} failed (${result.error}), retry ${i + 1}/${MAX_RETRIES_BEFORE_RESTART} in ${delayMs}ms
1402
+ `);
1403
+ await new Promise((r) => setTimeout(r, delayMs));
1404
+ if (!_connected) {
1405
+ if (!await connectToSocket()) continue;
1406
+ }
1407
+ const retry = await doRequest();
1408
+ if (!retry.error) {
1409
+ _consecutiveFailures = 0;
1410
+ return retry;
1411
+ }
1412
+ _consecutiveFailures++;
1413
+ }
1414
+ if (isDaemonTooYoung()) {
1415
+ process.stderr.write(`[exed-client] ${label}: daemon too young (< ${MIN_DAEMON_AGE_MS / 1e3}s) \u2014 skipping restart
1416
+ `);
1417
+ return { error: result.error };
1418
+ }
1419
+ process.stderr.write(`[exed-client] ${label}: ${_consecutiveFailures} consecutive failures \u2014 restarting daemon
1420
+ `);
1421
+ killAndRespawnDaemon();
1422
+ const start = Date.now();
1423
+ let delay2 = 200;
1424
+ while (Date.now() - start < CONNECT_TIMEOUT_MS) {
1425
+ await new Promise((r) => setTimeout(r, delay2));
1426
+ if (await connectToSocket()) break;
1427
+ delay2 = Math.min(delay2 * 2, 3e3);
1428
+ }
1429
+ if (!_connected) return { error: "Daemon restart failed" };
1430
+ const final = await doRequest();
1431
+ if (!final.error) _consecutiveFailures = 0;
1432
+ return final;
706
1433
  }
707
1434
  async function embedViaClient(text, priority = "high") {
708
1435
  if (!_connected && !await connectEmbedDaemon()) return null;
709
1436
  _requestCount++;
710
1437
  if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
711
1438
  const health = await pingDaemon();
712
- if (!health) {
1439
+ if (!health && !isDaemonTooYoung()) {
713
1440
  process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
714
1441
  `);
715
1442
  killAndRespawnDaemon();
716
1443
  const start = Date.now();
717
- let delay2 = 200;
1444
+ let d = 200;
718
1445
  while (Date.now() - start < CONNECT_TIMEOUT_MS) {
719
- await new Promise((r) => setTimeout(r, delay2));
1446
+ await new Promise((r) => setTimeout(r, d));
720
1447
  if (await connectToSocket()) break;
721
- delay2 = Math.min(delay2 * 2, 3e3);
1448
+ d = Math.min(d * 2, 3e3);
722
1449
  }
723
1450
  if (!_connected) return null;
724
1451
  }
725
1452
  }
726
- const result = await sendRequest([text], priority);
727
- if (!result.error && result.vectors?.[0]) return result.vectors[0];
728
- if (result.error) {
729
- process.stderr.write(`[exed-client] Embed failed (${result.error}) \u2014 attempting restart
730
- `);
731
- killAndRespawnDaemon();
732
- const start = Date.now();
733
- let delay2 = 200;
734
- while (Date.now() - start < CONNECT_TIMEOUT_MS) {
735
- await new Promise((r) => setTimeout(r, delay2));
736
- if (await connectToSocket()) break;
737
- delay2 = Math.min(delay2 * 2, 3e3);
738
- }
739
- if (!_connected) return null;
740
- const retry = await sendRequest([text], priority);
741
- if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
742
- process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
743
- `);
744
- }
745
- return null;
1453
+ const result = await retryThenRestart(
1454
+ () => sendRequest([text], priority),
1455
+ "Embed"
1456
+ );
1457
+ return !result.error && result.vectors?.[0] ? result.vectors[0] : null;
746
1458
  }
747
1459
  function disconnectClient() {
748
1460
  if (_socket) {
@@ -760,22 +1472,28 @@ function disconnectClient() {
760
1472
  function isClientConnected() {
761
1473
  return _connected;
762
1474
  }
763
- var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _requestCount, HEALTH_CHECK_INTERVAL, _pending, MAX_BUFFER;
1475
+ var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, DAEMON_TOKEN_ENV, _socket, _connected, _buffer, _requestCount, _consecutiveFailures, HEALTH_CHECK_INTERVAL, MAX_RETRIES_BEFORE_RESTART, RETRY_DELAYS_MS, MIN_DAEMON_AGE_MS, _pending, MAX_BUFFER;
764
1476
  var init_exe_daemon_client = __esm({
765
1477
  "src/lib/exe-daemon-client.ts"() {
766
1478
  "use strict";
767
1479
  init_config();
768
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path3.join(EXE_AI_DIR, "exed.sock");
769
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path3.join(EXE_AI_DIR, "exed.pid");
770
- SPAWN_LOCK_PATH = path3.join(EXE_AI_DIR, "exed-spawn.lock");
1480
+ init_daemon_auth();
1481
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path5.join(EXE_AI_DIR, "exed.sock");
1482
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path5.join(EXE_AI_DIR, "exed.pid");
1483
+ SPAWN_LOCK_PATH = path5.join(EXE_AI_DIR, "exed-spawn.lock");
771
1484
  SPAWN_LOCK_STALE_MS = 3e4;
772
1485
  CONNECT_TIMEOUT_MS = 15e3;
773
1486
  REQUEST_TIMEOUT_MS = 3e4;
1487
+ DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
774
1488
  _socket = null;
775
1489
  _connected = false;
776
1490
  _buffer = "";
777
1491
  _requestCount = 0;
1492
+ _consecutiveFailures = 0;
778
1493
  HEALTH_CHECK_INTERVAL = 100;
1494
+ MAX_RETRIES_BEFORE_RESTART = 3;
1495
+ RETRY_DELAYS_MS = [1e3, 3e3, 5e3];
1496
+ MIN_DAEMON_AGE_MS = 3e4;
779
1497
  _pending = /* @__PURE__ */ new Map();
780
1498
  MAX_BUFFER = 1e7;
781
1499
  }
@@ -851,7 +1569,7 @@ __export(db_daemon_client_exports, {
851
1569
  createDaemonDbClient: () => createDaemonDbClient,
852
1570
  initDaemonDbClient: () => initDaemonDbClient
853
1571
  });
854
- function normalizeStatement(stmt) {
1572
+ function normalizeStatement2(stmt) {
855
1573
  if (typeof stmt === "string") {
856
1574
  return { sql: stmt, args: [] };
857
1575
  }
@@ -875,7 +1593,7 @@ function createDaemonDbClient(fallbackClient) {
875
1593
  if (!_useDaemon || !isClientConnected()) {
876
1594
  return fallbackClient.execute(stmt);
877
1595
  }
878
- const { sql, args } = normalizeStatement(stmt);
1596
+ const { sql, args } = normalizeStatement2(stmt);
879
1597
  const response = await sendDaemonRequest({
880
1598
  type: "db-execute",
881
1599
  sql,
@@ -900,7 +1618,7 @@ function createDaemonDbClient(fallbackClient) {
900
1618
  if (!_useDaemon || !isClientConnected()) {
901
1619
  return fallbackClient.batch(stmts, mode);
902
1620
  }
903
- const statements = stmts.map(normalizeStatement);
1621
+ const statements = stmts.map(normalizeStatement2);
904
1622
  const response = await sendDaemonRequest({
905
1623
  type: "db-batch",
906
1624
  statements,
@@ -995,6 +1713,18 @@ __export(database_exports, {
995
1713
  });
996
1714
  import { createClient } from "@libsql/client";
997
1715
  async function initDatabase(config) {
1716
+ if (_walCheckpointTimer) {
1717
+ clearInterval(_walCheckpointTimer);
1718
+ _walCheckpointTimer = null;
1719
+ }
1720
+ if (_daemonClient) {
1721
+ _daemonClient.close();
1722
+ _daemonClient = null;
1723
+ }
1724
+ if (_adapterClient && _adapterClient !== _resilientClient) {
1725
+ _adapterClient.close();
1726
+ }
1727
+ _adapterClient = null;
998
1728
  if (_client) {
999
1729
  _client.close();
1000
1730
  _client = null;
@@ -1008,6 +1738,7 @@ async function initDatabase(config) {
1008
1738
  }
1009
1739
  _client = createClient(opts);
1010
1740
  _resilientClient = wrapWithRetry(_client);
1741
+ _adapterClient = _resilientClient;
1011
1742
  _client.execute("PRAGMA busy_timeout = 30000").catch(() => {
1012
1743
  });
1013
1744
  _client.execute("PRAGMA journal_mode = WAL").catch(() => {
@@ -1018,14 +1749,20 @@ async function initDatabase(config) {
1018
1749
  });
1019
1750
  }, 3e4);
1020
1751
  _walCheckpointTimer.unref();
1752
+ if (process.env.DATABASE_URL) {
1753
+ _adapterClient = await createPrismaDbAdapter(_resilientClient);
1754
+ }
1021
1755
  }
1022
1756
  function isInitialized() {
1023
- return _client !== null;
1757
+ return _adapterClient !== null || _client !== null;
1024
1758
  }
1025
1759
  function getClient() {
1026
- if (!_resilientClient) {
1760
+ if (!_adapterClient) {
1027
1761
  throw new Error("Database client not initialized. Call initDatabase() first.");
1028
1762
  }
1763
+ if (process.env.DATABASE_URL) {
1764
+ return _adapterClient;
1765
+ }
1029
1766
  if (process.env.EXE_IS_DAEMON === "1") {
1030
1767
  return _resilientClient;
1031
1768
  }
@@ -1035,6 +1772,7 @@ function getClient() {
1035
1772
  return _resilientClient;
1036
1773
  }
1037
1774
  async function initDaemonClient() {
1775
+ if (process.env.DATABASE_URL) return;
1038
1776
  if (process.env.EXE_IS_DAEMON === "1") return;
1039
1777
  if (!_resilientClient) return;
1040
1778
  try {
@@ -1331,6 +2069,7 @@ async function ensureSchema() {
1331
2069
  project TEXT NOT NULL,
1332
2070
  summary TEXT NOT NULL,
1333
2071
  task_file TEXT,
2072
+ session_scope TEXT,
1334
2073
  read INTEGER NOT NULL DEFAULT 0,
1335
2074
  created_at TEXT NOT NULL
1336
2075
  );
@@ -1339,7 +2078,7 @@ async function ensureSchema() {
1339
2078
  ON notifications(read);
1340
2079
 
1341
2080
  CREATE INDEX IF NOT EXISTS idx_notifications_agent
1342
- ON notifications(agent_id);
2081
+ ON notifications(agent_id, session_scope);
1343
2082
 
1344
2083
  CREATE INDEX IF NOT EXISTS idx_notifications_task_file
1345
2084
  ON notifications(task_file);
@@ -1377,6 +2116,7 @@ async function ensureSchema() {
1377
2116
  target_agent TEXT NOT NULL,
1378
2117
  target_project TEXT,
1379
2118
  target_device TEXT NOT NULL DEFAULT 'local',
2119
+ session_scope TEXT,
1380
2120
  content TEXT NOT NULL,
1381
2121
  priority TEXT DEFAULT 'normal',
1382
2122
  status TEXT DEFAULT 'pending',
@@ -1390,10 +2130,31 @@ async function ensureSchema() {
1390
2130
  );
1391
2131
 
1392
2132
  CREATE INDEX IF NOT EXISTS idx_messages_target
1393
- ON messages(target_agent, status);
2133
+ ON messages(target_agent, session_scope, status);
1394
2134
 
1395
2135
  CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
1396
- ON messages(target_agent, from_agent, server_seq);
2136
+ ON messages(target_agent, session_scope, from_agent, server_seq);
2137
+ `);
2138
+ try {
2139
+ await client.execute({
2140
+ sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
2141
+ args: []
2142
+ });
2143
+ } catch {
2144
+ }
2145
+ try {
2146
+ await client.execute({
2147
+ sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
2148
+ args: []
2149
+ });
2150
+ } catch {
2151
+ }
2152
+ await client.executeMultiple(`
2153
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
2154
+ ON notifications(agent_id, session_scope, read, created_at);
2155
+
2156
+ CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
2157
+ ON messages(target_agent, session_scope, status, created_at);
1397
2158
  `);
1398
2159
  try {
1399
2160
  await client.execute({
@@ -1977,28 +2738,45 @@ async function ensureSchema() {
1977
2738
  } catch {
1978
2739
  }
1979
2740
  }
2741
+ try {
2742
+ await client.execute({
2743
+ sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
2744
+ args: []
2745
+ });
2746
+ } catch {
2747
+ }
1980
2748
  }
1981
2749
  async function disposeDatabase() {
2750
+ if (_walCheckpointTimer) {
2751
+ clearInterval(_walCheckpointTimer);
2752
+ _walCheckpointTimer = null;
2753
+ }
1982
2754
  if (_daemonClient) {
1983
2755
  _daemonClient.close();
1984
2756
  _daemonClient = null;
1985
2757
  }
2758
+ if (_adapterClient && _adapterClient !== _resilientClient) {
2759
+ _adapterClient.close();
2760
+ }
2761
+ _adapterClient = null;
1986
2762
  if (_client) {
1987
2763
  _client.close();
1988
2764
  _client = null;
1989
2765
  _resilientClient = null;
1990
2766
  }
1991
2767
  }
1992
- var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso, disposeTurso;
2768
+ var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
1993
2769
  var init_database = __esm({
1994
2770
  "src/lib/database.ts"() {
1995
2771
  "use strict";
1996
2772
  init_db_retry();
1997
2773
  init_employees();
2774
+ init_database_adapter();
1998
2775
  _client = null;
1999
2776
  _resilientClient = null;
2000
2777
  _walCheckpointTimer = null;
2001
2778
  _daemonClient = null;
2779
+ _adapterClient = null;
2002
2780
  initTurso = initDatabase;
2003
2781
  disposeTurso = disposeDatabase;
2004
2782
  }
@@ -2014,14 +2792,14 @@ __export(keychain_exports, {
2014
2792
  setMasterKey: () => setMasterKey
2015
2793
  });
2016
2794
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
2017
- import { existsSync as existsSync4 } from "fs";
2018
- import path4 from "path";
2019
- import os4 from "os";
2795
+ import { existsSync as existsSync6 } from "fs";
2796
+ import path6 from "path";
2797
+ import os5 from "os";
2020
2798
  function getKeyDir() {
2021
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path4.join(os4.homedir(), ".exe-os");
2799
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path6.join(os5.homedir(), ".exe-os");
2022
2800
  }
2023
2801
  function getKeyPath() {
2024
- return path4.join(getKeyDir(), "master.key");
2802
+ return path6.join(getKeyDir(), "master.key");
2025
2803
  }
2026
2804
  async function tryKeytar() {
2027
2805
  try {
@@ -2042,9 +2820,9 @@ async function getMasterKey() {
2042
2820
  }
2043
2821
  }
2044
2822
  const keyPath = getKeyPath();
2045
- if (!existsSync4(keyPath)) {
2823
+ if (!existsSync6(keyPath)) {
2046
2824
  process.stderr.write(
2047
- `[keychain] Key not found at ${keyPath} (HOME=${os4.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
2825
+ `[keychain] Key not found at ${keyPath} (HOME=${os5.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
2048
2826
  `
2049
2827
  );
2050
2828
  return null;
@@ -2085,7 +2863,7 @@ async function deleteMasterKey() {
2085
2863
  }
2086
2864
  }
2087
2865
  const keyPath = getKeyPath();
2088
- if (existsSync4(keyPath)) {
2866
+ if (existsSync6(keyPath)) {
2089
2867
  await unlink(keyPath);
2090
2868
  }
2091
2869
  }
@@ -2187,6 +2965,7 @@ var shard_manager_exports = {};
2187
2965
  __export(shard_manager_exports, {
2188
2966
  disposeShards: () => disposeShards,
2189
2967
  ensureShardSchema: () => ensureShardSchema,
2968
+ getOpenShardCount: () => getOpenShardCount,
2190
2969
  getReadyShardClient: () => getReadyShardClient,
2191
2970
  getShardClient: () => getShardClient,
2192
2971
  getShardsDir: () => getShardsDir,
@@ -2195,15 +2974,18 @@ __export(shard_manager_exports, {
2195
2974
  listShards: () => listShards,
2196
2975
  shardExists: () => shardExists
2197
2976
  });
2198
- import path5 from "path";
2199
- import { existsSync as existsSync5, mkdirSync, readdirSync } from "fs";
2977
+ import path7 from "path";
2978
+ import { existsSync as existsSync7, mkdirSync as mkdirSync2, readdirSync } from "fs";
2200
2979
  import { createClient as createClient2 } from "@libsql/client";
2201
2980
  function initShardManager(encryptionKey) {
2202
2981
  _encryptionKey = encryptionKey;
2203
- if (!existsSync5(SHARDS_DIR)) {
2204
- mkdirSync(SHARDS_DIR, { recursive: true });
2982
+ if (!existsSync7(SHARDS_DIR)) {
2983
+ mkdirSync2(SHARDS_DIR, { recursive: true });
2205
2984
  }
2206
2985
  _shardingEnabled = true;
2986
+ if (_evictionTimer) clearInterval(_evictionTimer);
2987
+ _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
2988
+ _evictionTimer.unref();
2207
2989
  }
2208
2990
  function isShardingEnabled() {
2209
2991
  return _shardingEnabled;
@@ -2220,21 +3002,28 @@ function getShardClient(projectName) {
2220
3002
  throw new Error(`Invalid project name for shard: "${projectName}"`);
2221
3003
  }
2222
3004
  const cached = _shards.get(safeName);
2223
- if (cached) return cached;
2224
- const dbPath = path5.join(SHARDS_DIR, `${safeName}.db`);
3005
+ if (cached) {
3006
+ _shardLastAccess.set(safeName, Date.now());
3007
+ return cached;
3008
+ }
3009
+ while (_shards.size >= MAX_OPEN_SHARDS) {
3010
+ evictLRU();
3011
+ }
3012
+ const dbPath = path7.join(SHARDS_DIR, `${safeName}.db`);
2225
3013
  const client = createClient2({
2226
3014
  url: `file:${dbPath}`,
2227
3015
  encryptionKey: _encryptionKey
2228
3016
  });
2229
3017
  _shards.set(safeName, client);
3018
+ _shardLastAccess.set(safeName, Date.now());
2230
3019
  return client;
2231
3020
  }
2232
3021
  function shardExists(projectName) {
2233
3022
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
2234
- return existsSync5(path5.join(SHARDS_DIR, `${safeName}.db`));
3023
+ return existsSync7(path7.join(SHARDS_DIR, `${safeName}.db`));
2235
3024
  }
2236
3025
  function listShards() {
2237
- if (!existsSync5(SHARDS_DIR)) return [];
3026
+ if (!existsSync7(SHARDS_DIR)) return [];
2238
3027
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
2239
3028
  }
2240
3029
  async function ensureShardSchema(client) {
@@ -2286,6 +3075,8 @@ async function ensureShardSchema(client) {
2286
3075
  for (const col of [
2287
3076
  "ALTER TABLE memories ADD COLUMN task_id TEXT",
2288
3077
  "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
3078
+ "ALTER TABLE memories ADD COLUMN author_device_id TEXT",
3079
+ "ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
2289
3080
  "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
2290
3081
  "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
2291
3082
  "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
@@ -2308,7 +3099,23 @@ async function ensureShardSchema(client) {
2308
3099
  // MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
2309
3100
  "ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
2310
3101
  "ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
2311
- "ALTER TABLE memories ADD COLUMN trajectory TEXT"
3102
+ "ALTER TABLE memories ADD COLUMN trajectory TEXT",
3103
+ // Metadata enrichment columns (must match database.ts)
3104
+ "ALTER TABLE memories ADD COLUMN intent TEXT",
3105
+ "ALTER TABLE memories ADD COLUMN outcome TEXT",
3106
+ "ALTER TABLE memories ADD COLUMN domain TEXT",
3107
+ "ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
3108
+ "ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
3109
+ "ALTER TABLE memories ADD COLUMN chain_position TEXT",
3110
+ "ALTER TABLE memories ADD COLUMN review_status TEXT",
3111
+ "ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
3112
+ "ALTER TABLE memories ADD COLUMN file_paths TEXT",
3113
+ "ALTER TABLE memories ADD COLUMN commit_hash TEXT",
3114
+ "ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
3115
+ "ALTER TABLE memories ADD COLUMN token_cost REAL",
3116
+ "ALTER TABLE memories ADD COLUMN audience TEXT",
3117
+ "ALTER TABLE memories ADD COLUMN language_type TEXT",
3118
+ "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
2312
3119
  ]) {
2313
3120
  try {
2314
3121
  await client.execute(col);
@@ -2407,21 +3214,69 @@ async function getReadyShardClient(projectName) {
2407
3214
  await ensureShardSchema(client);
2408
3215
  return client;
2409
3216
  }
3217
+ function evictLRU() {
3218
+ let oldest = null;
3219
+ let oldestTime = Infinity;
3220
+ for (const [name, time] of _shardLastAccess) {
3221
+ if (time < oldestTime) {
3222
+ oldestTime = time;
3223
+ oldest = name;
3224
+ }
3225
+ }
3226
+ if (oldest) {
3227
+ const client = _shards.get(oldest);
3228
+ if (client) {
3229
+ client.close();
3230
+ }
3231
+ _shards.delete(oldest);
3232
+ _shardLastAccess.delete(oldest);
3233
+ }
3234
+ }
3235
+ function evictIdleShards() {
3236
+ const now = Date.now();
3237
+ const toEvict = [];
3238
+ for (const [name, lastAccess] of _shardLastAccess) {
3239
+ if (now - lastAccess > SHARD_IDLE_MS) {
3240
+ toEvict.push(name);
3241
+ }
3242
+ }
3243
+ for (const name of toEvict) {
3244
+ const client = _shards.get(name);
3245
+ if (client) {
3246
+ client.close();
3247
+ }
3248
+ _shards.delete(name);
3249
+ _shardLastAccess.delete(name);
3250
+ }
3251
+ }
3252
+ function getOpenShardCount() {
3253
+ return _shards.size;
3254
+ }
2410
3255
  function disposeShards() {
3256
+ if (_evictionTimer) {
3257
+ clearInterval(_evictionTimer);
3258
+ _evictionTimer = null;
3259
+ }
2411
3260
  for (const [, client] of _shards) {
2412
3261
  client.close();
2413
3262
  }
2414
3263
  _shards.clear();
3264
+ _shardLastAccess.clear();
2415
3265
  _shardingEnabled = false;
2416
3266
  _encryptionKey = null;
2417
3267
  }
2418
- var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
3268
+ var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
2419
3269
  var init_shard_manager = __esm({
2420
3270
  "src/lib/shard-manager.ts"() {
2421
3271
  "use strict";
2422
3272
  init_config();
2423
- SHARDS_DIR = path5.join(EXE_AI_DIR, "shards");
3273
+ SHARDS_DIR = path7.join(EXE_AI_DIR, "shards");
3274
+ SHARD_IDLE_MS = 5 * 60 * 1e3;
3275
+ MAX_OPEN_SHARDS = 10;
3276
+ EVICTION_INTERVAL_MS = 60 * 1e3;
2424
3277
  _shards = /* @__PURE__ */ new Map();
3278
+ _shardLastAccess = /* @__PURE__ */ new Map();
3279
+ _evictionTimer = null;
2425
3280
  _encryptionKey = null;
2426
3281
  _shardingEnabled = false;
2427
3282
  }
@@ -2614,56 +3469,14 @@ ${p.content}`).join("\n\n");
2614
3469
  }
2615
3470
  });
2616
3471
 
2617
- // src/lib/notifications.ts
2618
- import crypto from "crypto";
2619
- import path6 from "path";
2620
- import os5 from "os";
2621
- import {
2622
- readFileSync as readFileSync4,
2623
- readdirSync as readdirSync2,
2624
- unlinkSync as unlinkSync3,
2625
- existsSync as existsSync6,
2626
- rmdirSync
2627
- } from "fs";
2628
- async function writeNotification(notification) {
2629
- try {
2630
- const client = getClient();
2631
- const id = crypto.randomUUID();
2632
- const now = (/* @__PURE__ */ new Date()).toISOString();
2633
- await client.execute({
2634
- sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
2635
- VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
2636
- args: [
2637
- id,
2638
- notification.agentId,
2639
- notification.agentRole,
2640
- notification.event,
2641
- notification.project,
2642
- notification.summary,
2643
- notification.taskFile ?? null,
2644
- now
2645
- ]
2646
- });
2647
- } catch (err) {
2648
- process.stderr.write(`[notifications] WRITE FAILED: ${err instanceof Error ? err.message : String(err)}
2649
- `);
2650
- }
2651
- }
2652
- var init_notifications = __esm({
2653
- "src/lib/notifications.ts"() {
2654
- "use strict";
2655
- init_database();
2656
- }
2657
- });
2658
-
2659
3472
  // src/lib/session-registry.ts
2660
- import path7 from "path";
3473
+ import path8 from "path";
2661
3474
  import os6 from "os";
2662
3475
  var REGISTRY_PATH;
2663
3476
  var init_session_registry = __esm({
2664
3477
  "src/lib/session-registry.ts"() {
2665
3478
  "use strict";
2666
- REGISTRY_PATH = path7.join(os6.homedir(), ".exe-os", "session-registry.json");
3479
+ REGISTRY_PATH = path8.join(os6.homedir(), ".exe-os", "session-registry.json");
2667
3480
  }
2668
3481
  });
2669
3482
 
@@ -2898,15 +3711,16 @@ var init_runtime_table = __esm({
2898
3711
  });
2899
3712
 
2900
3713
  // src/lib/agent-config.ts
2901
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, existsSync as existsSync7, mkdirSync as mkdirSync2 } from "fs";
2902
- import path8 from "path";
3714
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync8 } from "fs";
3715
+ import path9 from "path";
2903
3716
  var AGENT_CONFIG_PATH, DEFAULT_MODELS;
2904
3717
  var init_agent_config = __esm({
2905
3718
  "src/lib/agent-config.ts"() {
2906
3719
  "use strict";
2907
3720
  init_config();
2908
3721
  init_runtime_table();
2909
- AGENT_CONFIG_PATH = path8.join(EXE_AI_DIR, "agent-config.json");
3722
+ init_secure_files();
3723
+ AGENT_CONFIG_PATH = path9.join(EXE_AI_DIR, "agent-config.json");
2910
3724
  DEFAULT_MODELS = {
2911
3725
  claude: "claude-opus-4",
2912
3726
  codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
@@ -2916,16 +3730,16 @@ var init_agent_config = __esm({
2916
3730
  });
2917
3731
 
2918
3732
  // src/lib/intercom-queue.ts
2919
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, renameSync as renameSync3, existsSync as existsSync8, mkdirSync as mkdirSync3 } from "fs";
2920
- import path9 from "path";
3733
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync9, mkdirSync as mkdirSync3 } from "fs";
3734
+ import path10 from "path";
2921
3735
  import os7 from "os";
2922
3736
  var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
2923
3737
  var init_intercom_queue = __esm({
2924
3738
  "src/lib/intercom-queue.ts"() {
2925
3739
  "use strict";
2926
- QUEUE_PATH = path9.join(os7.homedir(), ".exe-os", "intercom-queue.json");
3740
+ QUEUE_PATH = path10.join(os7.homedir(), ".exe-os", "intercom-queue.json");
2927
3741
  TTL_MS = 60 * 60 * 1e3;
2928
- INTERCOM_LOG = path9.join(os7.homedir(), ".exe-os", "intercom.log");
3742
+ INTERCOM_LOG = path10.join(os7.homedir(), ".exe-os", "intercom.log");
2929
3743
  }
2930
3744
  });
2931
3745
 
@@ -2946,9 +3760,12 @@ __export(license_exports, {
2946
3760
  stopLicenseRevalidation: () => stopLicenseRevalidation,
2947
3761
  validateLicense: () => validateLicense
2948
3762
  });
2949
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, existsSync as existsSync9, mkdirSync as mkdirSync4 } from "fs";
3763
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync10, mkdirSync as mkdirSync4 } from "fs";
2950
3764
  import { randomUUID as randomUUID3 } from "crypto";
2951
- import path10 from "path";
3765
+ import { createRequire as createRequire2 } from "module";
3766
+ import { pathToFileURL as pathToFileURL2 } from "url";
3767
+ import os8 from "os";
3768
+ import path11 from "path";
2952
3769
  import { jwtVerify, importSPKI } from "jose";
2953
3770
  async function fetchRetry(url, init) {
2954
3771
  try {
@@ -2959,16 +3776,16 @@ async function fetchRetry(url, init) {
2959
3776
  }
2960
3777
  }
2961
3778
  function loadDeviceId() {
2962
- const deviceJsonPath = path10.join(EXE_AI_DIR, "device.json");
3779
+ const deviceJsonPath = path11.join(EXE_AI_DIR, "device.json");
2963
3780
  try {
2964
- if (existsSync9(deviceJsonPath)) {
3781
+ if (existsSync10(deviceJsonPath)) {
2965
3782
  const data = JSON.parse(readFileSync7(deviceJsonPath, "utf8"));
2966
3783
  if (data.deviceId) return data.deviceId;
2967
3784
  }
2968
3785
  } catch {
2969
3786
  }
2970
3787
  try {
2971
- if (existsSync9(DEVICE_ID_PATH)) {
3788
+ if (existsSync10(DEVICE_ID_PATH)) {
2972
3789
  const id2 = readFileSync7(DEVICE_ID_PATH, "utf8").trim();
2973
3790
  if (id2) return id2;
2974
3791
  }
@@ -2976,12 +3793,12 @@ function loadDeviceId() {
2976
3793
  }
2977
3794
  const id = randomUUID3();
2978
3795
  mkdirSync4(EXE_AI_DIR, { recursive: true });
2979
- writeFileSync4(DEVICE_ID_PATH, id, "utf8");
3796
+ writeFileSync5(DEVICE_ID_PATH, id, "utf8");
2980
3797
  return id;
2981
3798
  }
2982
3799
  function loadLicense() {
2983
3800
  try {
2984
- if (!existsSync9(LICENSE_PATH)) return null;
3801
+ if (!existsSync10(LICENSE_PATH)) return null;
2985
3802
  return readFileSync7(LICENSE_PATH, "utf8").trim();
2986
3803
  } catch {
2987
3804
  return null;
@@ -2989,7 +3806,7 @@ function loadLicense() {
2989
3806
  }
2990
3807
  function saveLicense(apiKey) {
2991
3808
  mkdirSync4(EXE_AI_DIR, { recursive: true });
2992
- writeFileSync4(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
3809
+ writeFileSync5(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
2993
3810
  }
2994
3811
  async function verifyLicenseJwt(token) {
2995
3812
  try {
@@ -3015,7 +3832,7 @@ async function verifyLicenseJwt(token) {
3015
3832
  }
3016
3833
  async function getCachedLicense() {
3017
3834
  try {
3018
- if (!existsSync9(CACHE_PATH)) return null;
3835
+ if (!existsSync10(CACHE_PATH)) return null;
3019
3836
  const raw = JSON.parse(readFileSync7(CACHE_PATH, "utf8"));
3020
3837
  if (!raw.token || typeof raw.token !== "string") return null;
3021
3838
  return await verifyLicenseJwt(raw.token);
@@ -3025,7 +3842,7 @@ async function getCachedLicense() {
3025
3842
  }
3026
3843
  function readCachedToken() {
3027
3844
  try {
3028
- if (!existsSync9(CACHE_PATH)) return null;
3845
+ if (!existsSync10(CACHE_PATH)) return null;
3029
3846
  const raw = JSON.parse(readFileSync7(CACHE_PATH, "utf8"));
3030
3847
  return typeof raw.token === "string" ? raw.token : null;
3031
3848
  } catch {
@@ -3060,56 +3877,130 @@ function getRawCachedPlan() {
3060
3877
  }
3061
3878
  function cacheResponse(token) {
3062
3879
  try {
3063
- writeFileSync4(CACHE_PATH, JSON.stringify({ token }), "utf8");
3880
+ writeFileSync5(CACHE_PATH, JSON.stringify({ token }), "utf8");
3064
3881
  } catch {
3065
3882
  }
3066
3883
  }
3067
- async function validateLicense(apiKey, deviceId) {
3068
- const did = deviceId ?? loadDeviceId();
3884
+ function loadPrismaForLicense() {
3885
+ if (_prismaFailed) return null;
3886
+ const dbUrl = process.env.DATABASE_URL;
3887
+ if (!dbUrl) {
3888
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path11.join(os8.homedir(), "exe-db");
3889
+ if (!existsSync10(path11.join(exeDbRoot, "package.json"))) {
3890
+ _prismaFailed = true;
3891
+ return null;
3892
+ }
3893
+ }
3894
+ if (!_prismaPromise) {
3895
+ _prismaPromise = (async () => {
3896
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
3897
+ if (explicitPath) {
3898
+ const mod2 = await import(pathToFileURL2(explicitPath).href);
3899
+ const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
3900
+ if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
3901
+ return new Ctor2();
3902
+ }
3903
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path11.join(os8.homedir(), "exe-db");
3904
+ const req = createRequire2(path11.join(exeDbRoot, "package.json"));
3905
+ const entry = req.resolve("@prisma/client");
3906
+ const mod = await import(pathToFileURL2(entry).href);
3907
+ const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
3908
+ if (!Ctor) throw new Error(`No PrismaClient in ${entry}`);
3909
+ return new Ctor();
3910
+ })().catch((err) => {
3911
+ _prismaFailed = true;
3912
+ _prismaPromise = null;
3913
+ throw err;
3914
+ });
3915
+ }
3916
+ return _prismaPromise;
3917
+ }
3918
+ async function validateViaPostgres(apiKey) {
3919
+ const loader = loadPrismaForLicense();
3920
+ if (!loader) return null;
3921
+ try {
3922
+ const prisma = await loader;
3923
+ const rows = await prisma.$queryRawUnsafe(
3924
+ `SELECT plan, email, status, device_limit, employee_limit, memory_limit, expires_at
3925
+ FROM billing.licenses WHERE key = $1 LIMIT 1`,
3926
+ apiKey
3927
+ );
3928
+ if (!rows || rows.length === 0) return null;
3929
+ const row = rows[0];
3930
+ if (row.status !== "active") return null;
3931
+ if (row.expires_at && new Date(row.expires_at) < /* @__PURE__ */ new Date()) return null;
3932
+ const plan = row.plan;
3933
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
3934
+ return {
3935
+ valid: true,
3936
+ plan,
3937
+ email: row.email,
3938
+ expiresAt: row.expires_at ? new Date(row.expires_at).toISOString() : null,
3939
+ deviceLimit: row.device_limit ?? limits.devices,
3940
+ employeeLimit: row.employee_limit ?? limits.employees,
3941
+ memoryLimit: row.memory_limit ?? limits.memories
3942
+ };
3943
+ } catch {
3944
+ return null;
3945
+ }
3946
+ }
3947
+ async function validateViaCFWorker(apiKey, deviceId) {
3069
3948
  try {
3070
3949
  const res = await fetchRetry(`${API_BASE}/auth/activate`, {
3071
3950
  method: "POST",
3072
3951
  headers: { "Content-Type": "application/json" },
3073
- body: JSON.stringify({ apiKey, deviceId: did }),
3952
+ body: JSON.stringify({ apiKey, deviceId }),
3074
3953
  signal: AbortSignal.timeout(1e4)
3075
3954
  });
3076
- if (res.ok) {
3077
- const data = await res.json();
3078
- if (data.error === "device_limit_exceeded") {
3079
- const cached2 = await getCachedLicense();
3080
- if (cached2) return cached2;
3081
- const raw2 = getRawCachedPlan();
3082
- if (raw2) return { ...raw2, valid: false };
3083
- return { ...FREE_LICENSE, valid: false, plan: "free" };
3084
- }
3085
- if (data.token) {
3086
- cacheResponse(data.token);
3087
- const verified = await verifyLicenseJwt(data.token);
3088
- if (verified) return verified;
3955
+ if (!res.ok) return null;
3956
+ const data = await res.json();
3957
+ if (data.error === "device_limit_exceeded") return null;
3958
+ if (!data.valid) return null;
3959
+ if (data.token) {
3960
+ cacheResponse(data.token);
3961
+ const verified = await verifyLicenseJwt(data.token);
3962
+ if (verified) return verified;
3963
+ }
3964
+ const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
3965
+ return {
3966
+ valid: data.valid,
3967
+ plan: data.plan,
3968
+ email: data.email,
3969
+ expiresAt: data.expiresAt,
3970
+ deviceLimit: limits.devices,
3971
+ employeeLimit: limits.employees,
3972
+ memoryLimit: limits.memories
3973
+ };
3974
+ } catch {
3975
+ return null;
3976
+ }
3977
+ }
3978
+ async function validateLicense(apiKey, deviceId) {
3979
+ const did = deviceId ?? loadDeviceId();
3980
+ const pgResult = await validateViaPostgres(apiKey);
3981
+ if (pgResult) {
3982
+ try {
3983
+ writeFileSync5(CACHE_PATH, JSON.stringify({ pgLicense: pgResult, ts: Date.now() }), "utf8");
3984
+ } catch {
3985
+ }
3986
+ return pgResult;
3987
+ }
3988
+ const cfResult = await validateViaCFWorker(apiKey, did);
3989
+ if (cfResult) return cfResult;
3990
+ const cached = await getCachedLicense();
3991
+ if (cached) return cached;
3992
+ try {
3993
+ if (existsSync10(CACHE_PATH)) {
3994
+ const raw = JSON.parse(readFileSync7(CACHE_PATH, "utf8"));
3995
+ if (raw.pgLicense && raw.ts && Date.now() - raw.ts < 7 * 24 * 60 * 60 * 1e3) {
3996
+ return raw.pgLicense;
3089
3997
  }
3090
- const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
3091
- return {
3092
- valid: data.valid,
3093
- plan: data.plan,
3094
- email: data.email,
3095
- expiresAt: data.expiresAt,
3096
- deviceLimit: limits.devices,
3097
- employeeLimit: limits.employees,
3098
- memoryLimit: limits.memories
3099
- };
3100
3998
  }
3101
- const cached = await getCachedLicense();
3102
- if (cached) return cached;
3103
- const raw = getRawCachedPlan();
3104
- if (raw) return raw;
3105
- return { ...FREE_LICENSE, valid: false, plan: "free" };
3106
3999
  } catch {
3107
- const cached = await getCachedLicense();
3108
- if (cached) return cached;
3109
- const rawFallback = getRawCachedPlan();
3110
- if (rawFallback) return rawFallback;
3111
- return { ...FREE_LICENSE, valid: false, error: "offline" };
3112
4000
  }
4001
+ const rawFallback = getRawCachedPlan();
4002
+ if (rawFallback) return rawFallback;
4003
+ return { ...FREE_LICENSE, valid: false };
3113
4004
  }
3114
4005
  function getCacheAgeMs() {
3115
4006
  try {
@@ -3124,8 +4015,8 @@ async function checkLicense() {
3124
4015
  let key = loadLicense();
3125
4016
  if (!key) {
3126
4017
  try {
3127
- const configPath = path10.join(EXE_AI_DIR, "config.json");
3128
- if (existsSync9(configPath)) {
4018
+ const configPath = path11.join(EXE_AI_DIR, "config.json");
4019
+ if (existsSync10(configPath)) {
3129
4020
  const raw = JSON.parse(readFileSync7(configPath, "utf8"));
3130
4021
  const cloud = raw.cloud;
3131
4022
  if (cloud?.apiKey) {
@@ -3280,14 +4171,14 @@ function stopLicenseRevalidation() {
3280
4171
  _revalTimer = null;
3281
4172
  }
3282
4173
  }
3283
- var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, RETRY_DELAY_MS, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE, CACHE_MAX_AGE_MS, _revalTimer;
4174
+ var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, RETRY_DELAY_MS, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE, _prismaPromise, _prismaFailed, CACHE_MAX_AGE_MS, _revalTimer;
3284
4175
  var init_license = __esm({
3285
4176
  "src/lib/license.ts"() {
3286
4177
  "use strict";
3287
4178
  init_config();
3288
- LICENSE_PATH = path10.join(EXE_AI_DIR, "license.key");
3289
- CACHE_PATH = path10.join(EXE_AI_DIR, "license-cache.json");
3290
- DEVICE_ID_PATH = path10.join(EXE_AI_DIR, "device-id");
4179
+ LICENSE_PATH = path11.join(EXE_AI_DIR, "license.key");
4180
+ CACHE_PATH = path11.join(EXE_AI_DIR, "license-cache.json");
4181
+ DEVICE_ID_PATH = path11.join(EXE_AI_DIR, "device-id");
3291
4182
  API_BASE = "https://askexe.com/cloud";
3292
4183
  RETRY_DELAY_MS = 500;
3293
4184
  LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
@@ -3311,6 +4202,8 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
3311
4202
  employeeLimit: 1,
3312
4203
  memoryLimit: 5e3
3313
4204
  };
4205
+ _prismaPromise = null;
4206
+ _prismaFailed = false;
3314
4207
  CACHE_MAX_AGE_MS = 36e5;
3315
4208
  _revalTimer = null;
3316
4209
  }
@@ -3327,11 +4220,11 @@ __export(plan_limits_exports, {
3327
4220
  countActiveMemories: () => countActiveMemories,
3328
4221
  getLicenseSync: () => getLicenseSync
3329
4222
  });
3330
- import { readFileSync as readFileSync8, existsSync as existsSync10 } from "fs";
3331
- import path11 from "path";
4223
+ import { readFileSync as readFileSync8, existsSync as existsSync11 } from "fs";
4224
+ import path12 from "path";
3332
4225
  function getLicenseSync() {
3333
4226
  try {
3334
- if (!existsSync10(CACHE_PATH2)) return freeLicense();
4227
+ if (!existsSync11(CACHE_PATH2)) return freeLicense();
3335
4228
  const raw = JSON.parse(readFileSync8(CACHE_PATH2, "utf8"));
3336
4229
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
3337
4230
  const parts = raw.token.split(".");
@@ -3399,7 +4292,7 @@ function assertEmployeeLimitSync(rosterPath) {
3399
4292
  const filePath = rosterPath ?? EMPLOYEES_PATH;
3400
4293
  let count = 0;
3401
4294
  try {
3402
- if (existsSync10(filePath)) {
4295
+ if (existsSync11(filePath)) {
3403
4296
  const raw = readFileSync8(filePath, "utf8");
3404
4297
  const employees = JSON.parse(raw);
3405
4298
  count = Array.isArray(employees) ? employees.length : 0;
@@ -3437,14 +4330,14 @@ var init_plan_limits = __esm({
3437
4330
  this.name = "PlanLimitError";
3438
4331
  }
3439
4332
  };
3440
- CACHE_PATH2 = path11.join(EXE_AI_DIR, "license-cache.json");
4333
+ CACHE_PATH2 = path12.join(EXE_AI_DIR, "license-cache.json");
3441
4334
  }
3442
4335
  });
3443
4336
 
3444
4337
  // src/lib/tmux-routing.ts
3445
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, existsSync as existsSync11, appendFileSync, readdirSync as readdirSync3 } from "fs";
3446
- import path12 from "path";
3447
- import os8 from "os";
4338
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, existsSync as existsSync12, appendFileSync, readdirSync as readdirSync2 } from "fs";
4339
+ import path13 from "path";
4340
+ import os9 from "os";
3448
4341
  import { fileURLToPath as fileURLToPath2 } from "url";
3449
4342
  function getMySession() {
3450
4343
  return getTransport().getMySession();
@@ -3457,7 +4350,7 @@ function extractRootExe(name) {
3457
4350
  }
3458
4351
  function getParentExe(sessionKey) {
3459
4352
  try {
3460
- const data = JSON.parse(readFileSync9(path12.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
4353
+ const data = JSON.parse(readFileSync9(path13.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
3461
4354
  return data.parentExe || null;
3462
4355
  } catch {
3463
4356
  return null;
@@ -3500,10 +4393,10 @@ var init_tmux_routing = __esm({
3500
4393
  init_intercom_queue();
3501
4394
  init_plan_limits();
3502
4395
  init_employees();
3503
- SPAWN_LOCK_DIR = path12.join(os8.homedir(), ".exe-os", "spawn-locks");
3504
- SESSION_CACHE = path12.join(os8.homedir(), ".exe-os", "session-cache");
3505
- INTERCOM_LOG2 = path12.join(os8.homedir(), ".exe-os", "intercom.log");
3506
- DEBOUNCE_FILE = path12.join(SESSION_CACHE, "intercom-debounce.json");
4396
+ SPAWN_LOCK_DIR = path13.join(os9.homedir(), ".exe-os", "spawn-locks");
4397
+ SESSION_CACHE = path13.join(os9.homedir(), ".exe-os", "session-cache");
4398
+ INTERCOM_LOG2 = path13.join(os9.homedir(), ".exe-os", "intercom.log");
4399
+ DEBOUNCE_FILE = path13.join(SESSION_CACHE, "intercom-debounce.json");
3507
4400
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
3508
4401
  }
3509
4402
  });
@@ -3532,6 +4425,51 @@ var init_task_scope = __esm({
3532
4425
  }
3533
4426
  });
3534
4427
 
4428
+ // src/lib/notifications.ts
4429
+ import crypto2 from "crypto";
4430
+ import path14 from "path";
4431
+ import os10 from "os";
4432
+ import {
4433
+ readFileSync as readFileSync10,
4434
+ readdirSync as readdirSync3,
4435
+ unlinkSync as unlinkSync3,
4436
+ existsSync as existsSync13,
4437
+ rmdirSync
4438
+ } from "fs";
4439
+ async function writeNotification(notification) {
4440
+ try {
4441
+ const client = getClient();
4442
+ const id = crypto2.randomUUID();
4443
+ const now = (/* @__PURE__ */ new Date()).toISOString();
4444
+ const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
4445
+ await client.execute({
4446
+ sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
4447
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
4448
+ args: [
4449
+ id,
4450
+ notification.agentId,
4451
+ notification.agentRole,
4452
+ notification.event,
4453
+ notification.project,
4454
+ notification.summary,
4455
+ notification.taskFile ?? null,
4456
+ sessionScope,
4457
+ now
4458
+ ]
4459
+ });
4460
+ } catch (err) {
4461
+ process.stderr.write(`[notifications] WRITE FAILED: ${err instanceof Error ? err.message : String(err)}
4462
+ `);
4463
+ }
4464
+ }
4465
+ var init_notifications = __esm({
4466
+ "src/lib/notifications.ts"() {
4467
+ "use strict";
4468
+ init_database();
4469
+ init_task_scope();
4470
+ }
4471
+ });
4472
+
3535
4473
  // src/lib/embedder.ts
3536
4474
  var embedder_exports = {};
3537
4475
  __export(embedder_exports, {
@@ -3569,10 +4507,10 @@ async function disposeEmbedder() {
3569
4507
  async function embedDirect(text) {
3570
4508
  const llamaCpp = await import("node-llama-cpp");
3571
4509
  const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
3572
- const { existsSync: existsSync16 } = await import("fs");
3573
- const path17 = await import("path");
3574
- const modelPath = path17.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
3575
- if (!existsSync16(modelPath)) {
4510
+ const { existsSync: existsSync18 } = await import("fs");
4511
+ const path19 = await import("path");
4512
+ const modelPath = path19.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
4513
+ if (!existsSync18(modelPath)) {
3576
4514
  throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
3577
4515
  }
3578
4516
  const llama = await llamaCpp.getLlama();
@@ -3610,14 +4548,14 @@ __export(worker_gate_exports, {
3610
4548
  tryAcquireBackfillLock: () => tryAcquireBackfillLock,
3611
4549
  tryAcquireWorkerSlot: () => tryAcquireWorkerSlot
3612
4550
  });
3613
- import { readdirSync as readdirSync4, writeFileSync as writeFileSync6, unlinkSync as unlinkSync4, mkdirSync as mkdirSync6, existsSync as existsSync12 } from "fs";
3614
- import path13 from "path";
4551
+ import { readdirSync as readdirSync4, writeFileSync as writeFileSync7, unlinkSync as unlinkSync4, mkdirSync as mkdirSync6, existsSync as existsSync14 } from "fs";
4552
+ import path15 from "path";
3615
4553
  function tryAcquireWorkerSlot() {
3616
4554
  try {
3617
4555
  mkdirSync6(WORKER_PID_DIR, { recursive: true });
3618
4556
  const reservationId = `res-${process.pid}-${Date.now()}`;
3619
- const reservationPath = path13.join(WORKER_PID_DIR, `${reservationId}.pid`);
3620
- writeFileSync6(reservationPath, String(process.pid));
4557
+ const reservationPath = path15.join(WORKER_PID_DIR, `${reservationId}.pid`);
4558
+ writeFileSync7(reservationPath, String(process.pid));
3621
4559
  const files = readdirSync4(WORKER_PID_DIR);
3622
4560
  let alive = 0;
3623
4561
  for (const f of files) {
@@ -3634,7 +4572,7 @@ function tryAcquireWorkerSlot() {
3634
4572
  alive++;
3635
4573
  } catch {
3636
4574
  try {
3637
- unlinkSync4(path13.join(WORKER_PID_DIR, f));
4575
+ unlinkSync4(path15.join(WORKER_PID_DIR, f));
3638
4576
  } catch {
3639
4577
  }
3640
4578
  }
@@ -3658,20 +4596,20 @@ function tryAcquireWorkerSlot() {
3658
4596
  function registerWorkerPid(pid) {
3659
4597
  try {
3660
4598
  mkdirSync6(WORKER_PID_DIR, { recursive: true });
3661
- writeFileSync6(path13.join(WORKER_PID_DIR, `worker-${pid}.pid`), String(pid));
4599
+ writeFileSync7(path15.join(WORKER_PID_DIR, `worker-${pid}.pid`), String(pid));
3662
4600
  } catch {
3663
4601
  }
3664
4602
  }
3665
4603
  function cleanupWorkerPid() {
3666
4604
  try {
3667
- unlinkSync4(path13.join(WORKER_PID_DIR, `worker-${process.pid}.pid`));
4605
+ unlinkSync4(path15.join(WORKER_PID_DIR, `worker-${process.pid}.pid`));
3668
4606
  } catch {
3669
4607
  }
3670
4608
  }
3671
4609
  function tryAcquireBackfillLock() {
3672
4610
  try {
3673
4611
  mkdirSync6(WORKER_PID_DIR, { recursive: true });
3674
- if (existsSync12(BACKFILL_LOCK)) {
4612
+ if (existsSync14(BACKFILL_LOCK)) {
3675
4613
  try {
3676
4614
  const pid = parseInt(
3677
4615
  __require("fs").readFileSync(BACKFILL_LOCK, "utf8").trim(),
@@ -3687,7 +4625,7 @@ function tryAcquireBackfillLock() {
3687
4625
  } catch {
3688
4626
  }
3689
4627
  }
3690
- writeFileSync6(BACKFILL_LOCK, String(process.pid));
4628
+ writeFileSync7(BACKFILL_LOCK, String(process.pid));
3691
4629
  return true;
3692
4630
  } catch {
3693
4631
  return true;
@@ -3704,9 +4642,9 @@ var init_worker_gate = __esm({
3704
4642
  "src/lib/worker-gate.ts"() {
3705
4643
  "use strict";
3706
4644
  init_config();
3707
- WORKER_PID_DIR = path13.join(EXE_AI_DIR, "worker-pids");
4645
+ WORKER_PID_DIR = path15.join(EXE_AI_DIR, "worker-pids");
3708
4646
  MAX_CONCURRENT_WORKERS = 3;
3709
- BACKFILL_LOCK = path13.join(WORKER_PID_DIR, "backfill.lock");
4647
+ BACKFILL_LOCK = path15.join(WORKER_PID_DIR, "backfill.lock");
3710
4648
  }
3711
4649
  });
3712
4650
 
@@ -3718,13 +4656,13 @@ __export(crypto_exports, {
3718
4656
  initSyncCrypto: () => initSyncCrypto,
3719
4657
  isSyncCryptoInitialized: () => isSyncCryptoInitialized
3720
4658
  });
3721
- import crypto2 from "crypto";
4659
+ import crypto3 from "crypto";
3722
4660
  function initSyncCrypto(masterKey) {
3723
4661
  if (masterKey.length !== 32) {
3724
4662
  throw new Error(`Master key must be 32 bytes, got ${masterKey.length}`);
3725
4663
  }
3726
4664
  _syncKey = Buffer.from(
3727
- crypto2.hkdfSync("sha256", masterKey, "", SYNC_HKDF_INFO, 32)
4665
+ crypto3.hkdfSync("sha256", masterKey, "", SYNC_HKDF_INFO, 32)
3728
4666
  );
3729
4667
  }
3730
4668
  function isSyncCryptoInitialized() {
@@ -3738,8 +4676,8 @@ function requireSyncKey() {
3738
4676
  }
3739
4677
  function encryptSyncBlob(data) {
3740
4678
  const key = requireSyncKey();
3741
- const iv = crypto2.randomBytes(IV_LENGTH);
3742
- const cipher = crypto2.createCipheriv(ALGORITHM, key, iv);
4679
+ const iv = crypto3.randomBytes(IV_LENGTH);
4680
+ const cipher = crypto3.createCipheriv(ALGORITHM, key, iv);
3743
4681
  const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
3744
4682
  const tag = cipher.getAuthTag();
3745
4683
  return Buffer.concat([iv, encrypted, tag]).toString("base64");
@@ -3753,7 +4691,7 @@ function decryptSyncBlob(ciphertext) {
3753
4691
  const iv = combined.subarray(0, IV_LENGTH);
3754
4692
  const tag = combined.subarray(combined.length - TAG_LENGTH);
3755
4693
  const encrypted = combined.subarray(IV_LENGTH, combined.length - TAG_LENGTH);
3756
- const decipher = crypto2.createDecipheriv(ALGORITHM, key, iv);
4694
+ const decipher = crypto3.createDecipheriv(ALGORITHM, key, iv);
3757
4695
  decipher.setAuthTag(tag);
3758
4696
  return Buffer.concat([decipher.update(encrypted), decipher.final()]);
3759
4697
  }
@@ -3808,8 +4746,8 @@ __export(crdt_sync_exports, {
3808
4746
  rebuildFromDb: () => rebuildFromDb
3809
4747
  });
3810
4748
  import * as Y from "yjs";
3811
- import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, existsSync as existsSync13, mkdirSync as mkdirSync7, unlinkSync as unlinkSync5 } from "fs";
3812
- import path14 from "path";
4749
+ import { readFileSync as readFileSync11, writeFileSync as writeFileSync8, existsSync as existsSync15, mkdirSync as mkdirSync7, unlinkSync as unlinkSync5 } from "fs";
4750
+ import path16 from "path";
3813
4751
  import { homedir } from "os";
3814
4752
  function getStatePath() {
3815
4753
  return _statePathOverride ?? DEFAULT_STATE_PATH;
@@ -3821,9 +4759,9 @@ function initCrdtDoc() {
3821
4759
  if (doc) return doc;
3822
4760
  doc = new Y.Doc();
3823
4761
  const sp = getStatePath();
3824
- if (existsSync13(sp)) {
4762
+ if (existsSync15(sp)) {
3825
4763
  try {
3826
- const state = readFileSync10(sp);
4764
+ const state = readFileSync11(sp);
3827
4765
  Y.applyUpdate(doc, new Uint8Array(state));
3828
4766
  } catch {
3829
4767
  console.warn("[crdt-sync] WARN: corrupted state file, rebuilding from DB");
@@ -3965,10 +4903,10 @@ function persistState() {
3965
4903
  if (!doc) return;
3966
4904
  try {
3967
4905
  const sp = getStatePath();
3968
- const dir = path14.dirname(sp);
3969
- if (!existsSync13(dir)) mkdirSync7(dir, { recursive: true });
4906
+ const dir = path16.dirname(sp);
4907
+ if (!existsSync15(dir)) mkdirSync7(dir, { recursive: true });
3970
4908
  const state = Y.encodeStateAsUpdate(doc);
3971
- writeFileSync7(sp, Buffer.from(state));
4909
+ writeFileSync8(sp, Buffer.from(state));
3972
4910
  } catch {
3973
4911
  }
3974
4912
  }
@@ -4009,7 +4947,7 @@ var DEFAULT_STATE_PATH, _statePathOverride, doc;
4009
4947
  var init_crdt_sync = __esm({
4010
4948
  "src/lib/crdt-sync.ts"() {
4011
4949
  "use strict";
4012
- DEFAULT_STATE_PATH = path14.join(homedir(), ".exe-os", "crdt-state.bin");
4950
+ DEFAULT_STATE_PATH = path16.join(homedir(), ".exe-os", "crdt-state.bin");
4013
4951
  _statePathOverride = null;
4014
4952
  doc = null;
4015
4953
  }
@@ -4041,39 +4979,107 @@ __export(cloud_sync_exports, {
4041
4979
  cloudSync: () => cloudSync,
4042
4980
  mergeConfig: () => mergeConfig,
4043
4981
  mergeRosterFromRemote: () => mergeRosterFromRemote,
4982
+ pushToPostgres: () => pushToPostgres,
4044
4983
  recordRosterDeletion: () => recordRosterDeletion
4045
4984
  });
4046
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync8, existsSync as existsSync14, readdirSync as readdirSync5, mkdirSync as mkdirSync8, appendFileSync as appendFileSync2, unlinkSync as unlinkSync6, openSync as openSync2, closeSync as closeSync2 } from "fs";
4047
- import crypto3 from "crypto";
4048
- import path15 from "path";
4985
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync9, existsSync as existsSync16, readdirSync as readdirSync5, mkdirSync as mkdirSync8, appendFileSync as appendFileSync2, unlinkSync as unlinkSync6, openSync as openSync2, closeSync as closeSync2 } from "fs";
4986
+ import crypto4 from "crypto";
4987
+ import path17 from "path";
4049
4988
  import { homedir as homedir2 } from "os";
4050
4989
  function sqlSafe(v) {
4051
4990
  return v === void 0 ? null : v;
4052
4991
  }
4053
4992
  function logError(msg) {
4054
4993
  try {
4055
- const logPath = path15.join(homedir2(), ".exe-os", "workers.log");
4994
+ const logPath = path17.join(homedir2(), ".exe-os", "workers.log");
4056
4995
  appendFileSync2(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
4057
4996
  `);
4058
4997
  } catch {
4059
4998
  }
4060
4999
  }
5000
+ function loadPgClient() {
5001
+ if (_pgFailed) return null;
5002
+ const postgresUrl = process.env.DATABASE_URL;
5003
+ const configPath = path17.join(EXE_AI_DIR, "config.json");
5004
+ let cloudPostgresUrl;
5005
+ try {
5006
+ if (existsSync16(configPath)) {
5007
+ const cfg = JSON.parse(readFileSync12(configPath, "utf8"));
5008
+ cloudPostgresUrl = cfg.cloud?.postgresUrl;
5009
+ if (cfg.cloud?.syncToPostgres === false) {
5010
+ _pgFailed = true;
5011
+ return null;
5012
+ }
5013
+ }
5014
+ } catch {
5015
+ }
5016
+ const url = postgresUrl || cloudPostgresUrl;
5017
+ if (!url) {
5018
+ _pgFailed = true;
5019
+ return null;
5020
+ }
5021
+ if (!_pgPromise) {
5022
+ _pgPromise = (async () => {
5023
+ const { createRequire: createRequire3 } = await import("module");
5024
+ const { pathToFileURL: pathToFileURL3 } = await import("url");
5025
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path17.join(homedir2(), "exe-db");
5026
+ const req = createRequire3(path17.join(exeDbRoot, "package.json"));
5027
+ const entry = req.resolve("@prisma/client");
5028
+ const mod = await import(pathToFileURL3(entry).href);
5029
+ const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
5030
+ if (!Ctor) throw new Error("No PrismaClient");
5031
+ return new Ctor();
5032
+ })().catch(() => {
5033
+ _pgFailed = true;
5034
+ _pgPromise = null;
5035
+ throw new Error("pg_unavailable");
5036
+ });
5037
+ }
5038
+ return _pgPromise;
5039
+ }
5040
+ async function pushToPostgres(records) {
5041
+ const loader = loadPgClient();
5042
+ if (!loader) return 0;
5043
+ let prisma;
5044
+ try {
5045
+ prisma = await loader;
5046
+ } catch {
5047
+ return 0;
5048
+ }
5049
+ let inserted = 0;
5050
+ for (const rec of records) {
5051
+ try {
5052
+ await prisma.$executeRawUnsafe(
5053
+ `INSERT INTO raw.raw_events (id, source, source_id, event_type, payload, metadata, timestamp)
5054
+ VALUES (gen_random_uuid(), 'cloud_sync', $1, 'memory', $2::jsonb, $3::jsonb, $4)
5055
+ ON CONFLICT (source, source_id, event_type) DO NOTHING`,
5056
+ String(rec.id ?? ""),
5057
+ JSON.stringify(rec),
5058
+ JSON.stringify({ agent_id: rec.agent_id, project_name: rec.project_name, tool_name: rec.tool_name }),
5059
+ rec.timestamp ? new Date(String(rec.timestamp)) : /* @__PURE__ */ new Date()
5060
+ );
5061
+ inserted++;
5062
+ } catch {
5063
+ }
5064
+ }
5065
+ return inserted;
5066
+ }
4061
5067
  async function withRosterLock(fn) {
4062
5068
  try {
4063
5069
  const fd = openSync2(ROSTER_LOCK_PATH, "wx");
4064
5070
  closeSync2(fd);
4065
- writeFileSync8(ROSTER_LOCK_PATH, String(Date.now()));
5071
+ writeFileSync9(ROSTER_LOCK_PATH, String(Date.now()));
4066
5072
  } catch (err) {
4067
5073
  if (err.code === "EEXIST") {
4068
5074
  try {
4069
- const ts = parseInt(readFileSync11(ROSTER_LOCK_PATH, "utf-8"), 10);
5075
+ const ts = parseInt(readFileSync12(ROSTER_LOCK_PATH, "utf-8"), 10);
4070
5076
  if (Date.now() - ts < LOCK_STALE_MS) {
4071
5077
  throw new Error("Roster merge already in progress \u2014 another sync is running");
4072
5078
  }
4073
5079
  unlinkSync6(ROSTER_LOCK_PATH);
4074
5080
  const fd = openSync2(ROSTER_LOCK_PATH, "wx");
4075
5081
  closeSync2(fd);
4076
- writeFileSync8(ROSTER_LOCK_PATH, String(Date.now()));
5082
+ writeFileSync9(ROSTER_LOCK_PATH, String(Date.now()));
4077
5083
  } catch (retryErr) {
4078
5084
  if (retryErr instanceof Error && retryErr.message.includes("already in progress")) throw retryErr;
4079
5085
  throw new Error("Roster merge already in progress \u2014 another sync is running");
@@ -4343,6 +5349,10 @@ async function cloudSync(config) {
4343
5349
  const maxVersion = Number(records[records.length - 1].version);
4344
5350
  const pushOk = await cloudPush(records, maxVersion, config);
4345
5351
  if (!pushOk) break;
5352
+ try {
5353
+ await pushToPostgres(records);
5354
+ } catch {
5355
+ }
4346
5356
  await client.execute({
4347
5357
  sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_cloud_push_version', ?)",
4348
5358
  args: [String(maxVersion)]
@@ -4447,8 +5457,8 @@ async function cloudSync(config) {
4447
5457
  try {
4448
5458
  const employees = await loadEmployees();
4449
5459
  rosterResult.employees = employees.length;
4450
- const idDir = path15.join(EXE_AI_DIR, "identity");
4451
- if (existsSync14(idDir)) {
5460
+ const idDir = path17.join(EXE_AI_DIR, "identity");
5461
+ if (existsSync16(idDir)) {
4452
5462
  rosterResult.identities = readdirSync5(idDir).filter((f) => f.endsWith(".md")).length;
4453
5463
  }
4454
5464
  } catch {
@@ -4469,62 +5479,62 @@ async function cloudSync(config) {
4469
5479
  function recordRosterDeletion(name) {
4470
5480
  let deletions = [];
4471
5481
  try {
4472
- if (existsSync14(ROSTER_DELETIONS_PATH)) {
4473
- deletions = JSON.parse(readFileSync11(ROSTER_DELETIONS_PATH, "utf-8"));
5482
+ if (existsSync16(ROSTER_DELETIONS_PATH)) {
5483
+ deletions = JSON.parse(readFileSync12(ROSTER_DELETIONS_PATH, "utf-8"));
4474
5484
  }
4475
5485
  } catch {
4476
5486
  }
4477
5487
  if (!deletions.includes(name)) deletions.push(name);
4478
- writeFileSync8(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
5488
+ writeFileSync9(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
4479
5489
  }
4480
5490
  function consumeRosterDeletions() {
4481
5491
  try {
4482
- if (!existsSync14(ROSTER_DELETIONS_PATH)) return [];
4483
- const deletions = JSON.parse(readFileSync11(ROSTER_DELETIONS_PATH, "utf-8"));
4484
- writeFileSync8(ROSTER_DELETIONS_PATH, "[]");
5492
+ if (!existsSync16(ROSTER_DELETIONS_PATH)) return [];
5493
+ const deletions = JSON.parse(readFileSync12(ROSTER_DELETIONS_PATH, "utf-8"));
5494
+ writeFileSync9(ROSTER_DELETIONS_PATH, "[]");
4485
5495
  return deletions;
4486
5496
  } catch {
4487
5497
  return [];
4488
5498
  }
4489
5499
  }
4490
5500
  function buildRosterBlob(paths) {
4491
- const rosterPath = paths?.rosterPath ?? path15.join(EXE_AI_DIR, "exe-employees.json");
4492
- const identityDir = paths?.identityDir ?? path15.join(EXE_AI_DIR, "identity");
4493
- const configPath = paths?.configPath ?? path15.join(EXE_AI_DIR, "config.json");
5501
+ const rosterPath = paths?.rosterPath ?? path17.join(EXE_AI_DIR, "exe-employees.json");
5502
+ const identityDir = paths?.identityDir ?? path17.join(EXE_AI_DIR, "identity");
5503
+ const configPath = paths?.configPath ?? path17.join(EXE_AI_DIR, "config.json");
4494
5504
  let roster = [];
4495
- if (existsSync14(rosterPath)) {
5505
+ if (existsSync16(rosterPath)) {
4496
5506
  try {
4497
- roster = JSON.parse(readFileSync11(rosterPath, "utf-8"));
5507
+ roster = JSON.parse(readFileSync12(rosterPath, "utf-8"));
4498
5508
  } catch {
4499
5509
  }
4500
5510
  }
4501
5511
  const identities = {};
4502
- if (existsSync14(identityDir)) {
5512
+ if (existsSync16(identityDir)) {
4503
5513
  for (const file of readdirSync5(identityDir).filter((f) => f.endsWith(".md"))) {
4504
5514
  try {
4505
- identities[file] = readFileSync11(path15.join(identityDir, file), "utf-8");
5515
+ identities[file] = readFileSync12(path17.join(identityDir, file), "utf-8");
4506
5516
  } catch {
4507
5517
  }
4508
5518
  }
4509
5519
  }
4510
5520
  let config;
4511
- if (existsSync14(configPath)) {
5521
+ if (existsSync16(configPath)) {
4512
5522
  try {
4513
- config = JSON.parse(readFileSync11(configPath, "utf-8"));
5523
+ config = JSON.parse(readFileSync12(configPath, "utf-8"));
4514
5524
  } catch {
4515
5525
  }
4516
5526
  }
4517
5527
  let agentConfig;
4518
- const agentConfigPath = path15.join(EXE_AI_DIR, "agent-config.json");
4519
- if (existsSync14(agentConfigPath)) {
5528
+ const agentConfigPath = path17.join(EXE_AI_DIR, "agent-config.json");
5529
+ if (existsSync16(agentConfigPath)) {
4520
5530
  try {
4521
- agentConfig = JSON.parse(readFileSync11(agentConfigPath, "utf-8"));
5531
+ agentConfig = JSON.parse(readFileSync12(agentConfigPath, "utf-8"));
4522
5532
  } catch {
4523
5533
  }
4524
5534
  }
4525
5535
  const deletedNames = consumeRosterDeletions();
4526
5536
  const content = JSON.stringify({ roster, identities, config, agentConfig, deletedNames });
4527
- const hash = crypto3.createHash("sha256").update(content).digest("hex").slice(0, 16);
5537
+ const hash = crypto4.createHash("sha256").update(content).digest("hex").slice(0, 16);
4528
5538
  return { roster, identities, config, agentConfig, deletedNames, version: hash };
4529
5539
  }
4530
5540
  async function cloudPushRoster(config) {
@@ -4594,23 +5604,24 @@ async function cloudPullRoster(config) {
4594
5604
  }
4595
5605
  }
4596
5606
  function mergeConfig(remoteConfig, configPath) {
4597
- const cfgPath = configPath ?? path15.join(EXE_AI_DIR, "config.json");
5607
+ const cfgPath = configPath ?? path17.join(EXE_AI_DIR, "config.json");
4598
5608
  let local = {};
4599
- if (existsSync14(cfgPath)) {
5609
+ if (existsSync16(cfgPath)) {
4600
5610
  try {
4601
- local = JSON.parse(readFileSync11(cfgPath, "utf-8"));
5611
+ local = JSON.parse(readFileSync12(cfgPath, "utf-8"));
4602
5612
  } catch {
4603
5613
  }
4604
5614
  }
4605
5615
  const merged = { ...remoteConfig, ...local };
4606
- const dir = path15.dirname(cfgPath);
4607
- if (!existsSync14(dir)) mkdirSync8(dir, { recursive: true });
4608
- writeFileSync8(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
5616
+ const dir = path17.dirname(cfgPath);
5617
+ ensurePrivateDirSync(dir);
5618
+ writeFileSync9(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
5619
+ enforcePrivateFileSync(cfgPath);
4609
5620
  }
4610
5621
  async function mergeRosterFromRemote(remote, paths) {
4611
5622
  return withRosterLock(async () => {
4612
5623
  const rosterPath = paths?.rosterPath ?? void 0;
4613
- const identityDir = paths?.identityDir ?? path15.join(EXE_AI_DIR, "identity");
5624
+ const identityDir = paths?.identityDir ?? path17.join(EXE_AI_DIR, "identity");
4614
5625
  const localEmployees = await loadEmployees(rosterPath);
4615
5626
  const localNames = new Set(localEmployees.map((e) => e.name));
4616
5627
  let added = 0;
@@ -4631,15 +5642,15 @@ async function mergeRosterFromRemote(remote, paths) {
4631
5642
  ) ?? lookupKey;
4632
5643
  const remoteIdentity = remote.identities[matchedKey];
4633
5644
  if (remoteIdentity) {
4634
- if (!existsSync14(identityDir)) mkdirSync8(identityDir, { recursive: true });
4635
- const idPath = path15.join(identityDir, `${remoteEmp.name}.md`);
5645
+ if (!existsSync16(identityDir)) mkdirSync8(identityDir, { recursive: true });
5646
+ const idPath = path17.join(identityDir, `${remoteEmp.name}.md`);
4636
5647
  let localIdentity = null;
4637
5648
  try {
4638
- localIdentity = existsSync14(idPath) ? readFileSync11(idPath, "utf-8") : null;
5649
+ localIdentity = existsSync16(idPath) ? readFileSync12(idPath, "utf-8") : null;
4639
5650
  } catch {
4640
5651
  }
4641
5652
  if (localIdentity !== remoteIdentity) {
4642
- writeFileSync8(idPath, remoteIdentity, "utf-8");
5653
+ writeFileSync9(idPath, remoteIdentity, "utf-8");
4643
5654
  identitiesUpdated++;
4644
5655
  }
4645
5656
  }
@@ -4665,16 +5676,18 @@ async function mergeRosterFromRemote(remote, paths) {
4665
5676
  }
4666
5677
  if (remote.agentConfig && Object.keys(remote.agentConfig).length > 0) {
4667
5678
  try {
4668
- const agentConfigPath = path15.join(EXE_AI_DIR, "agent-config.json");
5679
+ const agentConfigPath = path17.join(EXE_AI_DIR, "agent-config.json");
4669
5680
  let local = {};
4670
- if (existsSync14(agentConfigPath)) {
5681
+ if (existsSync16(agentConfigPath)) {
4671
5682
  try {
4672
- local = JSON.parse(readFileSync11(agentConfigPath, "utf-8"));
5683
+ local = JSON.parse(readFileSync12(agentConfigPath, "utf-8"));
4673
5684
  } catch {
4674
5685
  }
4675
5686
  }
4676
5687
  const merged = { ...remote.agentConfig, ...local };
4677
- writeFileSync8(agentConfigPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
5688
+ ensurePrivateDirSync(path17.dirname(agentConfigPath));
5689
+ writeFileSync9(agentConfigPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
5690
+ enforcePrivateFileSync(agentConfigPath);
4678
5691
  } catch {
4679
5692
  }
4680
5693
  }
@@ -5098,7 +6111,7 @@ async function cloudPullDocuments(config) {
5098
6111
  }
5099
6112
  return { pulled };
5100
6113
  }
5101
- var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS, PUSH_BATCH_SIZE, ROSTER_LOCK_PATH, LOCK_STALE_MS, ROSTER_DELETIONS_PATH;
6114
+ var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS, PUSH_BATCH_SIZE, ROSTER_LOCK_PATH, LOCK_STALE_MS, _pgPromise, _pgFailed, ROSTER_DELETIONS_PATH;
5102
6115
  var init_cloud_sync = __esm({
5103
6116
  "src/lib/cloud-sync.ts"() {
5104
6117
  "use strict";
@@ -5109,12 +6122,15 @@ var init_cloud_sync = __esm({
5109
6122
  init_config();
5110
6123
  init_crdt_sync();
5111
6124
  init_employees();
6125
+ init_secure_files();
5112
6126
  LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
5113
6127
  FETCH_TIMEOUT_MS = 3e4;
5114
6128
  PUSH_BATCH_SIZE = 5e3;
5115
- ROSTER_LOCK_PATH = path15.join(EXE_AI_DIR, "roster-merge.lock");
6129
+ ROSTER_LOCK_PATH = path17.join(EXE_AI_DIR, "roster-merge.lock");
5116
6130
  LOCK_STALE_MS = 3e4;
5117
- ROSTER_DELETIONS_PATH = path15.join(EXE_AI_DIR, "roster-deletions.json");
6131
+ _pgPromise = null;
6132
+ _pgFailed = false;
6133
+ ROSTER_DELETIONS_PATH = path17.join(EXE_AI_DIR, "roster-deletions.json");
5118
6134
  }
5119
6135
  });
5120
6136
 
@@ -5481,10 +6497,10 @@ init_database();
5481
6497
  init_notifications();
5482
6498
  init_task_scope();
5483
6499
  init_employees();
5484
- import crypto4 from "crypto";
6500
+ import crypto5 from "crypto";
5485
6501
  import { execSync as execSync4 } from "child_process";
5486
- import { existsSync as existsSync15, mkdirSync as mkdirSync9, openSync as openSync3, closeSync as closeSync3 } from "fs";
5487
- import path16 from "path";
6502
+ import { existsSync as existsSync17, mkdirSync as mkdirSync9, openSync as openSync3, closeSync as closeSync3 } from "fs";
6503
+ import path18 from "path";
5488
6504
  async function main() {
5489
6505
  const agentId = process.env.AGENT_ID ?? "default";
5490
6506
  const agentRole = process.env.AGENT_ROLE ?? "employee";
@@ -5564,7 +6580,7 @@ async function main() {
5564
6580
  if (limitReached) {
5565
6581
  } else {
5566
6582
  await writeMemory({
5567
- id: crypto4.randomUUID(),
6583
+ id: crypto5.randomUUID(),
5568
6584
  agent_id: agentId,
5569
6585
  agent_role: agentRole,
5570
6586
  session_id: `auto-summary-${Date.now()}`,
@@ -5619,8 +6635,8 @@ async function main() {
5619
6635
  }
5620
6636
  try {
5621
6637
  const { EXE_AI_DIR: EXE_AI_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
5622
- const flagPath = path16.join(EXE_AI_DIR2, "session-cache", "needs-backfill");
5623
- if (existsSync15(flagPath)) {
6638
+ const flagPath = path18.join(EXE_AI_DIR2, "session-cache", "needs-backfill");
6639
+ if (existsSync17(flagPath)) {
5624
6640
  const { tryAcquireWorkerSlot: tryAcquireWorkerSlot2, registerWorkerPid: registerWorkerPid2 } = await Promise.resolve().then(() => (init_worker_gate(), worker_gate_exports));
5625
6641
  if (!tryAcquireWorkerSlot2()) {
5626
6642
  process.stderr.write("[summary-worker] Backfill needed but worker gate full \u2014 skipping\n");
@@ -5628,11 +6644,11 @@ async function main() {
5628
6644
  const { spawn: spawn2 } = await import("child_process");
5629
6645
  const { fileURLToPath: fileURLToPath3 } = await import("url");
5630
6646
  const thisFile = fileURLToPath3(import.meta.url);
5631
- const backfillPath = path16.resolve(path16.dirname(thisFile), "backfill-vectors.js");
5632
- if (existsSync15(backfillPath)) {
6647
+ const backfillPath = path18.resolve(path18.dirname(thisFile), "backfill-vectors.js");
6648
+ if (existsSync17(backfillPath)) {
5633
6649
  const { EXE_AI_DIR: exeDir2 } = await Promise.resolve().then(() => (init_config(), config_exports));
5634
- const bLogPath = path16.join(exeDir2, "workers.log");
5635
- mkdirSync9(path16.dirname(bLogPath), { recursive: true });
6650
+ const bLogPath = path18.join(exeDir2, "workers.log");
6651
+ mkdirSync9(path18.dirname(bLogPath), { recursive: true });
5636
6652
  const bLogFd = openSync3(bLogPath, "a");
5637
6653
  const child = spawn2(process.execPath, [backfillPath], {
5638
6654
  detached: true,