@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
@@ -132,6 +132,44 @@ var init_keychain = __esm({
132
132
  }
133
133
  });
134
134
 
135
+ // src/lib/secure-files.ts
136
+ import { chmodSync, existsSync as existsSync2, mkdirSync } from "fs";
137
+ import { chmod as chmod2, mkdir as mkdir2 } from "fs/promises";
138
+ async function ensurePrivateDir(dirPath) {
139
+ await mkdir2(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
140
+ try {
141
+ await chmod2(dirPath, PRIVATE_DIR_MODE);
142
+ } catch {
143
+ }
144
+ }
145
+ function ensurePrivateDirSync(dirPath) {
146
+ mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
147
+ try {
148
+ chmodSync(dirPath, PRIVATE_DIR_MODE);
149
+ } catch {
150
+ }
151
+ }
152
+ async function enforcePrivateFile(filePath) {
153
+ try {
154
+ await chmod2(filePath, PRIVATE_FILE_MODE);
155
+ } catch {
156
+ }
157
+ }
158
+ function enforcePrivateFileSync(filePath) {
159
+ try {
160
+ if (existsSync2(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
161
+ } catch {
162
+ }
163
+ }
164
+ var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
165
+ var init_secure_files = __esm({
166
+ "src/lib/secure-files.ts"() {
167
+ "use strict";
168
+ PRIVATE_DIR_MODE = 448;
169
+ PRIVATE_FILE_MODE = 384;
170
+ }
171
+ });
172
+
135
173
  // src/lib/config.ts
136
174
  var config_exports = {};
137
175
  __export(config_exports, {
@@ -148,8 +186,8 @@ __export(config_exports, {
148
186
  migrateConfig: () => migrateConfig,
149
187
  saveConfig: () => saveConfig
150
188
  });
151
- import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, chmod as chmod2 } from "fs/promises";
152
- import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
189
+ import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
190
+ import { readFileSync, existsSync as existsSync3, renameSync } from "fs";
153
191
  import path2 from "path";
154
192
  import os2 from "os";
155
193
  function resolveDataDir() {
@@ -157,7 +195,7 @@ function resolveDataDir() {
157
195
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
158
196
  const newDir = path2.join(os2.homedir(), ".exe-os");
159
197
  const legacyDir = path2.join(os2.homedir(), ".exe-mem");
160
- if (!existsSync2(newDir) && existsSync2(legacyDir)) {
198
+ if (!existsSync3(newDir) && existsSync3(legacyDir)) {
161
199
  try {
162
200
  renameSync(legacyDir, newDir);
163
201
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -220,9 +258,9 @@ function normalizeAutoUpdate(raw) {
220
258
  }
221
259
  async function loadConfig() {
222
260
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
223
- await mkdir2(dir, { recursive: true });
261
+ await ensurePrivateDir(dir);
224
262
  const configPath = path2.join(dir, "config.json");
225
- if (!existsSync2(configPath)) {
263
+ if (!existsSync3(configPath)) {
226
264
  return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
227
265
  }
228
266
  const raw = await readFile2(configPath, "utf-8");
@@ -235,6 +273,7 @@ async function loadConfig() {
235
273
  `);
236
274
  try {
237
275
  await writeFile2(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
276
+ await enforcePrivateFile(configPath);
238
277
  } catch {
239
278
  }
240
279
  }
@@ -253,7 +292,7 @@ async function loadConfig() {
253
292
  function loadConfigSync() {
254
293
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
255
294
  const configPath = path2.join(dir, "config.json");
256
- if (!existsSync2(configPath)) {
295
+ if (!existsSync3(configPath)) {
257
296
  return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
258
297
  }
259
298
  try {
@@ -271,12 +310,10 @@ function loadConfigSync() {
271
310
  }
272
311
  async function saveConfig(config) {
273
312
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
274
- await mkdir2(dir, { recursive: true });
313
+ await ensurePrivateDir(dir);
275
314
  const configPath = path2.join(dir, "config.json");
276
315
  await writeFile2(configPath, JSON.stringify(config, null, 2) + "\n");
277
- if (config.cloud?.apiKey) {
278
- await chmod2(configPath, 384);
279
- }
316
+ await enforcePrivateFile(configPath);
280
317
  }
281
318
  async function loadConfigFrom(configPath) {
282
319
  const raw = await readFile2(configPath, "utf-8");
@@ -296,6 +333,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
296
333
  var init_config = __esm({
297
334
  "src/lib/config.ts"() {
298
335
  "use strict";
336
+ init_secure_files();
299
337
  EXE_AI_DIR = resolveDataDir();
300
338
  DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
301
339
  MODELS_DIR = path2.join(EXE_AI_DIR, "models");
@@ -488,7 +526,7 @@ var init_db_retry = __esm({
488
526
 
489
527
  // src/lib/employees.ts
490
528
  import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
491
- import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
529
+ import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
492
530
  import { execSync } from "child_process";
493
531
  import path3 from "path";
494
532
  import os3 from "os";
@@ -505,7 +543,7 @@ function getCoordinatorName(employees = loadEmployeesSync()) {
505
543
  return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
506
544
  }
507
545
  async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
508
- if (!existsSync3(employeesPath)) {
546
+ if (!existsSync4(employeesPath)) {
509
547
  return [];
510
548
  }
511
549
  const raw = await readFile3(employeesPath, "utf-8");
@@ -520,7 +558,7 @@ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
520
558
  await writeFile3(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
521
559
  }
522
560
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
523
- if (!existsSync3(employeesPath)) return [];
561
+ if (!existsSync4(employeesPath)) return [];
524
562
  try {
525
563
  return JSON.parse(readFileSync2(employeesPath, "utf-8"));
526
564
  } catch {
@@ -554,7 +592,7 @@ function registerBinSymlinks(name) {
554
592
  for (const suffix of ["", "-opencode"]) {
555
593
  const linkName = `${name}${suffix}`;
556
594
  const linkPath = path3.join(binDir, linkName);
557
- if (existsSync3(linkPath)) {
595
+ if (existsSync4(linkPath)) {
558
596
  skipped.push(linkName);
559
597
  continue;
560
598
  }
@@ -567,7 +605,7 @@ function registerBinSymlinks(name) {
567
605
  }
568
606
  return { created, skipped, errors };
569
607
  }
570
- var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE;
608
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, IDENTITY_DIR;
571
609
  var init_employees = __esm({
572
610
  "src/lib/employees.ts"() {
573
611
  "use strict";
@@ -575,16 +613,638 @@ var init_employees = __esm({
575
613
  EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
576
614
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
577
615
  COORDINATOR_ROLE = "COO";
616
+ IDENTITY_DIR = path3.join(EXE_AI_DIR, "identity");
617
+ }
618
+ });
619
+
620
+ // src/lib/database-adapter.ts
621
+ import os4 from "os";
622
+ import path4 from "path";
623
+ import { createRequire } from "module";
624
+ import { pathToFileURL } from "url";
625
+ function quotedIdentifier(identifier) {
626
+ return `"${identifier.replace(/"/g, '""')}"`;
627
+ }
628
+ function unqualifiedTableName(name) {
629
+ const raw = name.trim().replace(/^"|"$/g, "");
630
+ const parts = raw.split(".");
631
+ return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
632
+ }
633
+ function stripTrailingSemicolon(sql) {
634
+ return sql.trim().replace(/;+\s*$/u, "");
635
+ }
636
+ function appendClause(sql, clause) {
637
+ const trimmed = stripTrailingSemicolon(sql);
638
+ const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
639
+ if (!returningMatch) {
640
+ return `${trimmed}${clause}`;
641
+ }
642
+ const idx = returningMatch.index;
643
+ return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
644
+ }
645
+ function normalizeStatement(stmt) {
646
+ if (typeof stmt === "string") {
647
+ return { kind: "positional", sql: stmt, args: [] };
648
+ }
649
+ const sql = stmt.sql;
650
+ if (Array.isArray(stmt.args) || stmt.args === void 0) {
651
+ return { kind: "positional", sql, args: stmt.args ?? [] };
652
+ }
653
+ return { kind: "named", sql, args: stmt.args };
654
+ }
655
+ function rewriteBooleanLiterals(sql) {
656
+ let out = sql;
657
+ for (const column of BOOLEAN_COLUMN_NAMES) {
658
+ const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
659
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
660
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
661
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
662
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
663
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
664
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
665
+ }
666
+ return out;
667
+ }
668
+ function rewriteInsertOrIgnore(sql) {
669
+ if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
670
+ return sql;
671
+ }
672
+ const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
673
+ return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
674
+ }
675
+ function rewriteInsertOrReplace(sql) {
676
+ const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
677
+ if (!match) {
678
+ return sql;
679
+ }
680
+ const rawTable = match[1];
681
+ const rawColumns = match[2];
682
+ const remainder = match[3];
683
+ const tableName = unqualifiedTableName(rawTable);
684
+ const conflictKeys = UPSERT_KEYS[tableName];
685
+ if (!conflictKeys?.length) {
686
+ return sql;
687
+ }
688
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
689
+ const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
690
+ const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
691
+ const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
692
+ return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
693
+ }
694
+ function rewriteSql(sql) {
695
+ let out = sql;
696
+ out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
697
+ out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
698
+ out = rewriteBooleanLiterals(out);
699
+ out = rewriteInsertOrReplace(out);
700
+ out = rewriteInsertOrIgnore(out);
701
+ return stripTrailingSemicolon(out);
702
+ }
703
+ function toBoolean(value) {
704
+ if (value === null || value === void 0) return value;
705
+ if (typeof value === "boolean") return value;
706
+ if (typeof value === "number") return value !== 0;
707
+ if (typeof value === "bigint") return value !== 0n;
708
+ if (typeof value === "string") {
709
+ const normalized = value.trim().toLowerCase();
710
+ if (normalized === "0" || normalized === "false") return false;
711
+ if (normalized === "1" || normalized === "true") return true;
712
+ }
713
+ return Boolean(value);
714
+ }
715
+ function countQuestionMarks(sql, end) {
716
+ let count = 0;
717
+ let inSingle = false;
718
+ let inDouble = false;
719
+ let inLineComment = false;
720
+ let inBlockComment = false;
721
+ for (let i = 0; i < end; i++) {
722
+ const ch = sql[i];
723
+ const next = sql[i + 1];
724
+ if (inLineComment) {
725
+ if (ch === "\n") inLineComment = false;
726
+ continue;
727
+ }
728
+ if (inBlockComment) {
729
+ if (ch === "*" && next === "/") {
730
+ inBlockComment = false;
731
+ i += 1;
732
+ }
733
+ continue;
734
+ }
735
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
736
+ inLineComment = true;
737
+ i += 1;
738
+ continue;
739
+ }
740
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
741
+ inBlockComment = true;
742
+ i += 1;
743
+ continue;
744
+ }
745
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
746
+ inSingle = !inSingle;
747
+ continue;
748
+ }
749
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
750
+ inDouble = !inDouble;
751
+ continue;
752
+ }
753
+ if (!inSingle && !inDouble && ch === "?") {
754
+ count += 1;
755
+ }
756
+ }
757
+ return count;
758
+ }
759
+ function findBooleanPlaceholderIndexes(sql) {
760
+ const indexes = /* @__PURE__ */ new Set();
761
+ for (const column of BOOLEAN_COLUMN_NAMES) {
762
+ const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
763
+ for (const match of sql.matchAll(pattern)) {
764
+ const matchText = match[0];
765
+ const qIndex = match.index + matchText.lastIndexOf("?");
766
+ indexes.add(countQuestionMarks(sql, qIndex + 1));
767
+ }
768
+ }
769
+ return indexes;
770
+ }
771
+ function coerceInsertBooleanArgs(sql, args) {
772
+ const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
773
+ if (!match) return;
774
+ const rawTable = match[1];
775
+ const rawColumns = match[2];
776
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
777
+ if (!boolColumns?.size) return;
778
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
779
+ for (const [index, column] of columns.entries()) {
780
+ if (boolColumns.has(column) && index < args.length) {
781
+ args[index] = toBoolean(args[index]);
782
+ }
783
+ }
784
+ }
785
+ function coerceUpdateBooleanArgs(sql, args) {
786
+ const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
787
+ if (!match) return;
788
+ const rawTable = match[1];
789
+ const setClause = match[2];
790
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
791
+ if (!boolColumns?.size) return;
792
+ const assignments = setClause.split(",");
793
+ let placeholderIndex = 0;
794
+ for (const assignment of assignments) {
795
+ if (!assignment.includes("?")) continue;
796
+ placeholderIndex += 1;
797
+ const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
798
+ if (colMatch && boolColumns.has(colMatch[1])) {
799
+ args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
800
+ }
801
+ }
802
+ }
803
+ function coerceBooleanArgs(sql, args) {
804
+ const nextArgs = [...args];
805
+ coerceInsertBooleanArgs(sql, nextArgs);
806
+ coerceUpdateBooleanArgs(sql, nextArgs);
807
+ const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
808
+ for (const index of placeholderIndexes) {
809
+ if (index > 0 && index <= nextArgs.length) {
810
+ nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
811
+ }
812
+ }
813
+ return nextArgs;
814
+ }
815
+ function convertQuestionMarksToDollarParams(sql) {
816
+ let out = "";
817
+ let placeholder = 0;
818
+ let inSingle = false;
819
+ let inDouble = false;
820
+ let inLineComment = false;
821
+ let inBlockComment = false;
822
+ for (let i = 0; i < sql.length; i++) {
823
+ const ch = sql[i];
824
+ const next = sql[i + 1];
825
+ if (inLineComment) {
826
+ out += ch;
827
+ if (ch === "\n") inLineComment = false;
828
+ continue;
829
+ }
830
+ if (inBlockComment) {
831
+ out += ch;
832
+ if (ch === "*" && next === "/") {
833
+ out += next;
834
+ inBlockComment = false;
835
+ i += 1;
836
+ }
837
+ continue;
838
+ }
839
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
840
+ out += ch + next;
841
+ inLineComment = true;
842
+ i += 1;
843
+ continue;
844
+ }
845
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
846
+ out += ch + next;
847
+ inBlockComment = true;
848
+ i += 1;
849
+ continue;
850
+ }
851
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
852
+ inSingle = !inSingle;
853
+ out += ch;
854
+ continue;
855
+ }
856
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
857
+ inDouble = !inDouble;
858
+ out += ch;
859
+ continue;
860
+ }
861
+ if (!inSingle && !inDouble && ch === "?") {
862
+ placeholder += 1;
863
+ out += `$${placeholder}`;
864
+ continue;
865
+ }
866
+ out += ch;
867
+ }
868
+ return out;
869
+ }
870
+ function translateStatementForPostgres(stmt) {
871
+ const normalized = normalizeStatement(stmt);
872
+ if (normalized.kind === "named") {
873
+ throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
874
+ }
875
+ const rewrittenSql = rewriteSql(normalized.sql);
876
+ const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
877
+ return {
878
+ sql: convertQuestionMarksToDollarParams(rewrittenSql),
879
+ args: coercedArgs
880
+ };
881
+ }
882
+ function shouldBypassPostgres(stmt) {
883
+ const normalized = normalizeStatement(stmt);
884
+ if (normalized.kind === "named") {
885
+ return true;
886
+ }
887
+ return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
888
+ }
889
+ function shouldFallbackOnError(error) {
890
+ const message = error instanceof Error ? error.message : String(error);
891
+ return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
892
+ }
893
+ function isReadQuery(sql) {
894
+ const trimmed = sql.trimStart();
895
+ return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
896
+ }
897
+ function buildRow(row, columns) {
898
+ const values = columns.map((column) => row[column]);
899
+ return Object.assign(values, row);
900
+ }
901
+ function buildResultSet(rows, rowsAffected = 0) {
902
+ const columns = rows[0] ? Object.keys(rows[0]) : [];
903
+ const resultRows = rows.map((row) => buildRow(row, columns));
904
+ return {
905
+ columns,
906
+ columnTypes: columns.map(() => ""),
907
+ rows: resultRows,
908
+ rowsAffected,
909
+ lastInsertRowid: void 0,
910
+ toJSON() {
911
+ return {
912
+ columns,
913
+ columnTypes: columns.map(() => ""),
914
+ rows,
915
+ rowsAffected,
916
+ lastInsertRowid: void 0
917
+ };
918
+ }
919
+ };
920
+ }
921
+ async function loadPrismaClient() {
922
+ if (!prismaClientPromise) {
923
+ prismaClientPromise = (async () => {
924
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
925
+ if (explicitPath) {
926
+ const module2 = await import(pathToFileURL(explicitPath).href);
927
+ const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
928
+ if (!PrismaClient2) {
929
+ throw new Error(`No PrismaClient export found at ${explicitPath}`);
930
+ }
931
+ return new PrismaClient2();
932
+ }
933
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path4.join(os4.homedir(), "exe-db");
934
+ const requireFromExeDb = createRequire(path4.join(exeDbRoot, "package.json"));
935
+ const prismaEntry = requireFromExeDb.resolve("@prisma/client");
936
+ const module = await import(pathToFileURL(prismaEntry).href);
937
+ const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
938
+ if (!PrismaClient) {
939
+ throw new Error(`No PrismaClient export found in ${prismaEntry}`);
940
+ }
941
+ return new PrismaClient();
942
+ })();
943
+ }
944
+ return prismaClientPromise;
945
+ }
946
+ async function ensureCompatibilityViews(prisma) {
947
+ if (!compatibilityBootstrapPromise) {
948
+ compatibilityBootstrapPromise = (async () => {
949
+ for (const mapping of VIEW_MAPPINGS) {
950
+ const relation = mapping.source.replace(/"/g, "");
951
+ const rows = await prisma.$queryRawUnsafe(
952
+ "SELECT to_regclass($1) AS regclass",
953
+ relation
954
+ );
955
+ if (!rows[0]?.regclass) {
956
+ continue;
957
+ }
958
+ await prisma.$executeRawUnsafe(
959
+ `CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
960
+ );
961
+ }
962
+ })();
963
+ }
964
+ return compatibilityBootstrapPromise;
965
+ }
966
+ async function executeOnPrisma(executor, stmt) {
967
+ const translated = translateStatementForPostgres(stmt);
968
+ if (isReadQuery(translated.sql)) {
969
+ const rows = await executor.$queryRawUnsafe(
970
+ translated.sql,
971
+ ...translated.args
972
+ );
973
+ return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
974
+ }
975
+ const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
976
+ return buildResultSet([], rowsAffected);
977
+ }
978
+ function splitSqlStatements(sql) {
979
+ const parts = [];
980
+ let current = "";
981
+ let inSingle = false;
982
+ let inDouble = false;
983
+ let inLineComment = false;
984
+ let inBlockComment = false;
985
+ for (let i = 0; i < sql.length; i++) {
986
+ const ch = sql[i];
987
+ const next = sql[i + 1];
988
+ if (inLineComment) {
989
+ current += ch;
990
+ if (ch === "\n") inLineComment = false;
991
+ continue;
992
+ }
993
+ if (inBlockComment) {
994
+ current += ch;
995
+ if (ch === "*" && next === "/") {
996
+ current += next;
997
+ inBlockComment = false;
998
+ i += 1;
999
+ }
1000
+ continue;
1001
+ }
1002
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
1003
+ current += ch + next;
1004
+ inLineComment = true;
1005
+ i += 1;
1006
+ continue;
1007
+ }
1008
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
1009
+ current += ch + next;
1010
+ inBlockComment = true;
1011
+ i += 1;
1012
+ continue;
1013
+ }
1014
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
1015
+ inSingle = !inSingle;
1016
+ current += ch;
1017
+ continue;
1018
+ }
1019
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
1020
+ inDouble = !inDouble;
1021
+ current += ch;
1022
+ continue;
1023
+ }
1024
+ if (!inSingle && !inDouble && ch === ";") {
1025
+ if (current.trim()) {
1026
+ parts.push(current.trim());
1027
+ }
1028
+ current = "";
1029
+ continue;
1030
+ }
1031
+ current += ch;
1032
+ }
1033
+ if (current.trim()) {
1034
+ parts.push(current.trim());
1035
+ }
1036
+ return parts;
1037
+ }
1038
+ async function createPrismaDbAdapter(fallbackClient) {
1039
+ const prisma = await loadPrismaClient();
1040
+ await ensureCompatibilityViews(prisma);
1041
+ let closed = false;
1042
+ let adapter;
1043
+ const fallbackExecute = async (stmt, error) => {
1044
+ if (!fallbackClient) {
1045
+ if (error) throw error;
1046
+ throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
1047
+ }
1048
+ if (error) {
1049
+ process.stderr.write(
1050
+ `[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
1051
+ `
1052
+ );
1053
+ }
1054
+ return fallbackClient.execute(stmt);
1055
+ };
1056
+ adapter = {
1057
+ async execute(stmt) {
1058
+ if (shouldBypassPostgres(stmt)) {
1059
+ return fallbackExecute(stmt);
1060
+ }
1061
+ try {
1062
+ return await executeOnPrisma(prisma, stmt);
1063
+ } catch (error) {
1064
+ if (shouldFallbackOnError(error)) {
1065
+ return fallbackExecute(stmt, error);
1066
+ }
1067
+ throw error;
1068
+ }
1069
+ },
1070
+ async batch(stmts, mode) {
1071
+ if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
1072
+ if (!fallbackClient) {
1073
+ throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
1074
+ }
1075
+ return fallbackClient.batch(stmts, mode);
1076
+ }
1077
+ try {
1078
+ if (prisma.$transaction) {
1079
+ return await prisma.$transaction(async (tx) => {
1080
+ const results2 = [];
1081
+ for (const stmt of stmts) {
1082
+ results2.push(await executeOnPrisma(tx, stmt));
1083
+ }
1084
+ return results2;
1085
+ });
1086
+ }
1087
+ const results = [];
1088
+ for (const stmt of stmts) {
1089
+ results.push(await executeOnPrisma(prisma, stmt));
1090
+ }
1091
+ return results;
1092
+ } catch (error) {
1093
+ if (fallbackClient && shouldFallbackOnError(error)) {
1094
+ process.stderr.write(
1095
+ `[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
1096
+ `
1097
+ );
1098
+ return fallbackClient.batch(stmts, mode);
1099
+ }
1100
+ throw error;
1101
+ }
1102
+ },
1103
+ async migrate(stmts) {
1104
+ if (fallbackClient) {
1105
+ return fallbackClient.migrate(stmts);
1106
+ }
1107
+ return adapter.batch(stmts, "deferred");
1108
+ },
1109
+ async transaction(mode) {
1110
+ if (!fallbackClient) {
1111
+ throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
1112
+ }
1113
+ return fallbackClient.transaction(mode);
1114
+ },
1115
+ async executeMultiple(sql) {
1116
+ if (fallbackClient && shouldBypassPostgres(sql)) {
1117
+ return fallbackClient.executeMultiple(sql);
1118
+ }
1119
+ for (const statement of splitSqlStatements(sql)) {
1120
+ await adapter.execute(statement);
1121
+ }
1122
+ },
1123
+ async sync() {
1124
+ if (fallbackClient) {
1125
+ return fallbackClient.sync();
1126
+ }
1127
+ return { frame_no: 0, frames_synced: 0 };
1128
+ },
1129
+ close() {
1130
+ closed = true;
1131
+ prismaClientPromise = null;
1132
+ compatibilityBootstrapPromise = null;
1133
+ void prisma.$disconnect?.();
1134
+ },
1135
+ get closed() {
1136
+ return closed;
1137
+ },
1138
+ get protocol() {
1139
+ return "prisma-postgres";
1140
+ }
1141
+ };
1142
+ return adapter;
1143
+ }
1144
+ var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
1145
+ var init_database_adapter = __esm({
1146
+ "src/lib/database-adapter.ts"() {
1147
+ "use strict";
1148
+ VIEW_MAPPINGS = [
1149
+ { view: "memories", source: "memory.memory_records" },
1150
+ { view: "tasks", source: "memory.tasks" },
1151
+ { view: "behaviors", source: "memory.behaviors" },
1152
+ { view: "entities", source: "memory.entities" },
1153
+ { view: "relationships", source: "memory.relationships" },
1154
+ { view: "entity_memories", source: "memory.entity_memories" },
1155
+ { view: "entity_aliases", source: "memory.entity_aliases" },
1156
+ { view: "notifications", source: "memory.notifications" },
1157
+ { view: "messages", source: "memory.messages" },
1158
+ { view: "users", source: "wiki.users" },
1159
+ { view: "workspaces", source: "wiki.workspaces" },
1160
+ { view: "workspace_users", source: "wiki.workspace_users" },
1161
+ { view: "documents", source: "wiki.workspace_documents" },
1162
+ { view: "chats", source: "wiki.workspace_chats" }
1163
+ ];
1164
+ UPSERT_KEYS = {
1165
+ memories: ["id"],
1166
+ tasks: ["id"],
1167
+ behaviors: ["id"],
1168
+ entities: ["id"],
1169
+ relationships: ["id"],
1170
+ entity_aliases: ["alias"],
1171
+ notifications: ["id"],
1172
+ messages: ["id"],
1173
+ users: ["id"],
1174
+ workspaces: ["id"],
1175
+ workspace_users: ["id"],
1176
+ documents: ["id"],
1177
+ chats: ["id"]
1178
+ };
1179
+ BOOLEAN_COLUMNS_BY_TABLE = {
1180
+ memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
1181
+ behaviors: /* @__PURE__ */ new Set(["active"]),
1182
+ notifications: /* @__PURE__ */ new Set(["read"]),
1183
+ users: /* @__PURE__ */ new Set(["has_personal_memory"])
1184
+ };
1185
+ BOOLEAN_COLUMN_NAMES = new Set(
1186
+ Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
1187
+ );
1188
+ IMMEDIATE_FALLBACK_PATTERNS = [
1189
+ /\bPRAGMA\b/i,
1190
+ /\bsqlite_master\b/i,
1191
+ /(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
1192
+ /\bMATCH\b/i,
1193
+ /\bvector_distance_cos\s*\(/i,
1194
+ /\bjson_extract\s*\(/i,
1195
+ /\bjulianday\s*\(/i,
1196
+ /\bstrftime\s*\(/i,
1197
+ /\blast_insert_rowid\s*\(/i
1198
+ ];
1199
+ prismaClientPromise = null;
1200
+ compatibilityBootstrapPromise = null;
1201
+ }
1202
+ });
1203
+
1204
+ // src/lib/daemon-auth.ts
1205
+ import crypto2 from "crypto";
1206
+ import path5 from "path";
1207
+ import { existsSync as existsSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
1208
+ function normalizeToken(token) {
1209
+ if (!token) return null;
1210
+ const trimmed = token.trim();
1211
+ return trimmed.length > 0 ? trimmed : null;
1212
+ }
1213
+ function readDaemonToken() {
1214
+ try {
1215
+ if (!existsSync5(DAEMON_TOKEN_PATH)) return null;
1216
+ return normalizeToken(readFileSync3(DAEMON_TOKEN_PATH, "utf8"));
1217
+ } catch {
1218
+ return null;
1219
+ }
1220
+ }
1221
+ function ensureDaemonToken(seed) {
1222
+ const existing = readDaemonToken();
1223
+ if (existing) return existing;
1224
+ const token = normalizeToken(seed) ?? crypto2.randomBytes(32).toString("hex");
1225
+ ensurePrivateDirSync(EXE_AI_DIR);
1226
+ writeFileSync2(DAEMON_TOKEN_PATH, `${token}
1227
+ `, "utf8");
1228
+ enforcePrivateFileSync(DAEMON_TOKEN_PATH);
1229
+ return token;
1230
+ }
1231
+ var DAEMON_TOKEN_PATH;
1232
+ var init_daemon_auth = __esm({
1233
+ "src/lib/daemon-auth.ts"() {
1234
+ "use strict";
1235
+ init_config();
1236
+ init_secure_files();
1237
+ DAEMON_TOKEN_PATH = path5.join(EXE_AI_DIR, "exed.token");
578
1238
  }
579
1239
  });
580
1240
 
581
1241
  // src/lib/exe-daemon-client.ts
582
1242
  import net from "net";
583
- import os4 from "os";
1243
+ import os5 from "os";
584
1244
  import { spawn } from "child_process";
585
1245
  import { randomUUID } from "crypto";
586
- import { existsSync as existsSync4, unlinkSync as unlinkSync2, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
587
- import path4 from "path";
1246
+ import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
1247
+ import path6 from "path";
588
1248
  import { fileURLToPath as fileURLToPath2 } from "url";
589
1249
  function handleData(chunk) {
590
1250
  _buffer += chunk.toString();
@@ -612,9 +1272,9 @@ function handleData(chunk) {
612
1272
  }
613
1273
  }
614
1274
  function cleanupStaleFiles() {
615
- if (existsSync4(PID_PATH)) {
1275
+ if (existsSync6(PID_PATH)) {
616
1276
  try {
617
- const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
1277
+ const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
618
1278
  if (pid > 0) {
619
1279
  try {
620
1280
  process.kill(pid, 0);
@@ -635,17 +1295,17 @@ function cleanupStaleFiles() {
635
1295
  }
636
1296
  }
637
1297
  function findPackageRoot() {
638
- let dir = path4.dirname(fileURLToPath2(import.meta.url));
639
- const { root } = path4.parse(dir);
1298
+ let dir = path6.dirname(fileURLToPath2(import.meta.url));
1299
+ const { root } = path6.parse(dir);
640
1300
  while (dir !== root) {
641
- if (existsSync4(path4.join(dir, "package.json"))) return dir;
642
- dir = path4.dirname(dir);
1301
+ if (existsSync6(path6.join(dir, "package.json"))) return dir;
1302
+ dir = path6.dirname(dir);
643
1303
  }
644
1304
  return null;
645
1305
  }
646
1306
  function spawnDaemon() {
647
- const freeGB = os4.freemem() / (1024 * 1024 * 1024);
648
- const totalGB = os4.totalmem() / (1024 * 1024 * 1024);
1307
+ const freeGB = os5.freemem() / (1024 * 1024 * 1024);
1308
+ const totalGB = os5.totalmem() / (1024 * 1024 * 1024);
649
1309
  if (totalGB <= 8) {
650
1310
  process.stderr.write(
651
1311
  `[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
@@ -665,16 +1325,17 @@ function spawnDaemon() {
665
1325
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
666
1326
  return;
667
1327
  }
668
- const daemonPath = path4.join(pkgRoot, "dist", "lib", "exe-daemon.js");
669
- if (!existsSync4(daemonPath)) {
1328
+ const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1329
+ if (!existsSync6(daemonPath)) {
670
1330
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
671
1331
  `);
672
1332
  return;
673
1333
  }
674
1334
  const resolvedPath = daemonPath;
1335
+ const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
675
1336
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
676
1337
  `);
677
- const logPath = path4.join(path4.dirname(SOCKET_PATH), "exed.log");
1338
+ const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
678
1339
  let stderrFd = "ignore";
679
1340
  try {
680
1341
  stderrFd = openSync(logPath, "a");
@@ -692,7 +1353,8 @@ function spawnDaemon() {
692
1353
  TMUX_PANE: void 0,
693
1354
  // Prevents resolveExeSession() from scoping to one session
694
1355
  EXE_DAEMON_SOCK: SOCKET_PATH,
695
- EXE_DAEMON_PID: PID_PATH
1356
+ EXE_DAEMON_PID: PID_PATH,
1357
+ [DAEMON_TOKEN_ENV]: daemonToken
696
1358
  }
697
1359
  });
698
1360
  child.unref();
@@ -799,13 +1461,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
799
1461
  return;
800
1462
  }
801
1463
  const id = randomUUID();
1464
+ const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
802
1465
  const timer = setTimeout(() => {
803
1466
  _pending.delete(id);
804
1467
  resolve({ error: "Request timeout" });
805
1468
  }, timeoutMs);
806
1469
  _pending.set(id, { resolve, timer });
807
1470
  try {
808
- _socket.write(JSON.stringify({ id, ...payload }) + "\n");
1471
+ _socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
809
1472
  } catch {
810
1473
  clearTimeout(timer);
811
1474
  _pending.delete(id);
@@ -816,17 +1479,19 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
816
1479
  function isClientConnected() {
817
1480
  return _connected;
818
1481
  }
819
- var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
1482
+ var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, DAEMON_TOKEN_ENV, _socket, _connected, _buffer, _pending, MAX_BUFFER;
820
1483
  var init_exe_daemon_client = __esm({
821
1484
  "src/lib/exe-daemon-client.ts"() {
822
1485
  "use strict";
823
1486
  init_config();
824
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path4.join(EXE_AI_DIR, "exed.sock");
825
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path4.join(EXE_AI_DIR, "exed.pid");
826
- SPAWN_LOCK_PATH = path4.join(EXE_AI_DIR, "exed-spawn.lock");
1487
+ init_daemon_auth();
1488
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
1489
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
1490
+ SPAWN_LOCK_PATH = path6.join(EXE_AI_DIR, "exed-spawn.lock");
827
1491
  SPAWN_LOCK_STALE_MS = 3e4;
828
1492
  CONNECT_TIMEOUT_MS = 15e3;
829
1493
  REQUEST_TIMEOUT_MS = 3e4;
1494
+ DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
830
1495
  _socket = null;
831
1496
  _connected = false;
832
1497
  _buffer = "";
@@ -905,7 +1570,7 @@ __export(db_daemon_client_exports, {
905
1570
  createDaemonDbClient: () => createDaemonDbClient,
906
1571
  initDaemonDbClient: () => initDaemonDbClient
907
1572
  });
908
- function normalizeStatement(stmt) {
1573
+ function normalizeStatement2(stmt) {
909
1574
  if (typeof stmt === "string") {
910
1575
  return { sql: stmt, args: [] };
911
1576
  }
@@ -929,7 +1594,7 @@ function createDaemonDbClient(fallbackClient) {
929
1594
  if (!_useDaemon || !isClientConnected()) {
930
1595
  return fallbackClient.execute(stmt);
931
1596
  }
932
- const { sql, args } = normalizeStatement(stmt);
1597
+ const { sql, args } = normalizeStatement2(stmt);
933
1598
  const response = await sendDaemonRequest({
934
1599
  type: "db-execute",
935
1600
  sql,
@@ -954,7 +1619,7 @@ function createDaemonDbClient(fallbackClient) {
954
1619
  if (!_useDaemon || !isClientConnected()) {
955
1620
  return fallbackClient.batch(stmts, mode);
956
1621
  }
957
- const statements = stmts.map(normalizeStatement);
1622
+ const statements = stmts.map(normalizeStatement2);
958
1623
  const response = await sendDaemonRequest({
959
1624
  type: "db-batch",
960
1625
  statements,
@@ -1049,6 +1714,18 @@ __export(database_exports, {
1049
1714
  });
1050
1715
  import { createClient } from "@libsql/client";
1051
1716
  async function initDatabase(config) {
1717
+ if (_walCheckpointTimer) {
1718
+ clearInterval(_walCheckpointTimer);
1719
+ _walCheckpointTimer = null;
1720
+ }
1721
+ if (_daemonClient) {
1722
+ _daemonClient.close();
1723
+ _daemonClient = null;
1724
+ }
1725
+ if (_adapterClient && _adapterClient !== _resilientClient) {
1726
+ _adapterClient.close();
1727
+ }
1728
+ _adapterClient = null;
1052
1729
  if (_client) {
1053
1730
  _client.close();
1054
1731
  _client = null;
@@ -1062,6 +1739,7 @@ async function initDatabase(config) {
1062
1739
  }
1063
1740
  _client = createClient(opts);
1064
1741
  _resilientClient = wrapWithRetry(_client);
1742
+ _adapterClient = _resilientClient;
1065
1743
  _client.execute("PRAGMA busy_timeout = 30000").catch(() => {
1066
1744
  });
1067
1745
  _client.execute("PRAGMA journal_mode = WAL").catch(() => {
@@ -1072,14 +1750,20 @@ async function initDatabase(config) {
1072
1750
  });
1073
1751
  }, 3e4);
1074
1752
  _walCheckpointTimer.unref();
1753
+ if (process.env.DATABASE_URL) {
1754
+ _adapterClient = await createPrismaDbAdapter(_resilientClient);
1755
+ }
1075
1756
  }
1076
1757
  function isInitialized() {
1077
- return _client !== null;
1758
+ return _adapterClient !== null || _client !== null;
1078
1759
  }
1079
1760
  function getClient() {
1080
- if (!_resilientClient) {
1761
+ if (!_adapterClient) {
1081
1762
  throw new Error("Database client not initialized. Call initDatabase() first.");
1082
1763
  }
1764
+ if (process.env.DATABASE_URL) {
1765
+ return _adapterClient;
1766
+ }
1083
1767
  if (process.env.EXE_IS_DAEMON === "1") {
1084
1768
  return _resilientClient;
1085
1769
  }
@@ -1089,6 +1773,7 @@ function getClient() {
1089
1773
  return _resilientClient;
1090
1774
  }
1091
1775
  async function initDaemonClient() {
1776
+ if (process.env.DATABASE_URL) return;
1092
1777
  if (process.env.EXE_IS_DAEMON === "1") return;
1093
1778
  if (!_resilientClient) return;
1094
1779
  try {
@@ -1385,6 +2070,7 @@ async function ensureSchema() {
1385
2070
  project TEXT NOT NULL,
1386
2071
  summary TEXT NOT NULL,
1387
2072
  task_file TEXT,
2073
+ session_scope TEXT,
1388
2074
  read INTEGER NOT NULL DEFAULT 0,
1389
2075
  created_at TEXT NOT NULL
1390
2076
  );
@@ -1393,7 +2079,7 @@ async function ensureSchema() {
1393
2079
  ON notifications(read);
1394
2080
 
1395
2081
  CREATE INDEX IF NOT EXISTS idx_notifications_agent
1396
- ON notifications(agent_id);
2082
+ ON notifications(agent_id, session_scope);
1397
2083
 
1398
2084
  CREATE INDEX IF NOT EXISTS idx_notifications_task_file
1399
2085
  ON notifications(task_file);
@@ -1431,6 +2117,7 @@ async function ensureSchema() {
1431
2117
  target_agent TEXT NOT NULL,
1432
2118
  target_project TEXT,
1433
2119
  target_device TEXT NOT NULL DEFAULT 'local',
2120
+ session_scope TEXT,
1434
2121
  content TEXT NOT NULL,
1435
2122
  priority TEXT DEFAULT 'normal',
1436
2123
  status TEXT DEFAULT 'pending',
@@ -1444,10 +2131,31 @@ async function ensureSchema() {
1444
2131
  );
1445
2132
 
1446
2133
  CREATE INDEX IF NOT EXISTS idx_messages_target
1447
- ON messages(target_agent, status);
2134
+ ON messages(target_agent, session_scope, status);
1448
2135
 
1449
2136
  CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
1450
- ON messages(target_agent, from_agent, server_seq);
2137
+ ON messages(target_agent, session_scope, from_agent, server_seq);
2138
+ `);
2139
+ try {
2140
+ await client.execute({
2141
+ sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
2142
+ args: []
2143
+ });
2144
+ } catch {
2145
+ }
2146
+ try {
2147
+ await client.execute({
2148
+ sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
2149
+ args: []
2150
+ });
2151
+ } catch {
2152
+ }
2153
+ await client.executeMultiple(`
2154
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
2155
+ ON notifications(agent_id, session_scope, read, created_at);
2156
+
2157
+ CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
2158
+ ON messages(target_agent, session_scope, status, created_at);
1451
2159
  `);
1452
2160
  try {
1453
2161
  await client.execute({
@@ -2031,28 +2739,45 @@ async function ensureSchema() {
2031
2739
  } catch {
2032
2740
  }
2033
2741
  }
2742
+ try {
2743
+ await client.execute({
2744
+ sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
2745
+ args: []
2746
+ });
2747
+ } catch {
2748
+ }
2034
2749
  }
2035
2750
  async function disposeDatabase() {
2751
+ if (_walCheckpointTimer) {
2752
+ clearInterval(_walCheckpointTimer);
2753
+ _walCheckpointTimer = null;
2754
+ }
2036
2755
  if (_daemonClient) {
2037
2756
  _daemonClient.close();
2038
2757
  _daemonClient = null;
2039
2758
  }
2759
+ if (_adapterClient && _adapterClient !== _resilientClient) {
2760
+ _adapterClient.close();
2761
+ }
2762
+ _adapterClient = null;
2040
2763
  if (_client) {
2041
2764
  _client.close();
2042
2765
  _client = null;
2043
2766
  _resilientClient = null;
2044
2767
  }
2045
2768
  }
2046
- var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso, disposeTurso;
2769
+ var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
2047
2770
  var init_database = __esm({
2048
2771
  "src/lib/database.ts"() {
2049
2772
  "use strict";
2050
2773
  init_db_retry();
2051
2774
  init_employees();
2775
+ init_database_adapter();
2052
2776
  _client = null;
2053
2777
  _resilientClient = null;
2054
2778
  _walCheckpointTimer = null;
2055
2779
  _daemonClient = null;
2780
+ _adapterClient = null;
2056
2781
  initTurso = initDatabase;
2057
2782
  disposeTurso = disposeDatabase;
2058
2783
  }
@@ -2079,29 +2804,32 @@ var init_compress = __esm({
2079
2804
  });
2080
2805
 
2081
2806
  // src/lib/license.ts
2082
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync5, mkdirSync } from "fs";
2807
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync7, mkdirSync as mkdirSync2 } from "fs";
2083
2808
  import { randomUUID as randomUUID2 } from "crypto";
2084
- import path5 from "path";
2809
+ import { createRequire as createRequire2 } from "module";
2810
+ import { pathToFileURL as pathToFileURL2 } from "url";
2811
+ import os6 from "os";
2812
+ import path7 from "path";
2085
2813
  import { jwtVerify, importSPKI } from "jose";
2086
2814
  function loadDeviceId() {
2087
- const deviceJsonPath = path5.join(EXE_AI_DIR, "device.json");
2815
+ const deviceJsonPath = path7.join(EXE_AI_DIR, "device.json");
2088
2816
  try {
2089
- if (existsSync5(deviceJsonPath)) {
2090
- const data = JSON.parse(readFileSync4(deviceJsonPath, "utf8"));
2817
+ if (existsSync7(deviceJsonPath)) {
2818
+ const data = JSON.parse(readFileSync5(deviceJsonPath, "utf8"));
2091
2819
  if (data.deviceId) return data.deviceId;
2092
2820
  }
2093
2821
  } catch {
2094
2822
  }
2095
2823
  try {
2096
- if (existsSync5(DEVICE_ID_PATH)) {
2097
- const id2 = readFileSync4(DEVICE_ID_PATH, "utf8").trim();
2824
+ if (existsSync7(DEVICE_ID_PATH)) {
2825
+ const id2 = readFileSync5(DEVICE_ID_PATH, "utf8").trim();
2098
2826
  if (id2) return id2;
2099
2827
  }
2100
2828
  } catch {
2101
2829
  }
2102
2830
  const id = randomUUID2();
2103
- mkdirSync(EXE_AI_DIR, { recursive: true });
2104
- writeFileSync2(DEVICE_ID_PATH, id, "utf8");
2831
+ mkdirSync2(EXE_AI_DIR, { recursive: true });
2832
+ writeFileSync3(DEVICE_ID_PATH, id, "utf8");
2105
2833
  return id;
2106
2834
  }
2107
2835
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH;
@@ -2109,9 +2837,9 @@ var init_license = __esm({
2109
2837
  "src/lib/license.ts"() {
2110
2838
  "use strict";
2111
2839
  init_config();
2112
- LICENSE_PATH = path5.join(EXE_AI_DIR, "license.key");
2113
- CACHE_PATH = path5.join(EXE_AI_DIR, "license-cache.json");
2114
- DEVICE_ID_PATH = path5.join(EXE_AI_DIR, "device-id");
2840
+ LICENSE_PATH = path7.join(EXE_AI_DIR, "license.key");
2841
+ CACHE_PATH = path7.join(EXE_AI_DIR, "license-cache.json");
2842
+ DEVICE_ID_PATH = path7.join(EXE_AI_DIR, "device-id");
2115
2843
  }
2116
2844
  });
2117
2845
 
@@ -2134,8 +2862,8 @@ __export(crdt_sync_exports, {
2134
2862
  rebuildFromDb: () => rebuildFromDb
2135
2863
  });
2136
2864
  import * as Y from "yjs";
2137
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync6, mkdirSync as mkdirSync2, unlinkSync as unlinkSync3 } from "fs";
2138
- import path6 from "path";
2865
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync8, mkdirSync as mkdirSync3, unlinkSync as unlinkSync3 } from "fs";
2866
+ import path8 from "path";
2139
2867
  import { homedir } from "os";
2140
2868
  function getStatePath() {
2141
2869
  return _statePathOverride ?? DEFAULT_STATE_PATH;
@@ -2147,9 +2875,9 @@ function initCrdtDoc() {
2147
2875
  if (doc) return doc;
2148
2876
  doc = new Y.Doc();
2149
2877
  const sp = getStatePath();
2150
- if (existsSync6(sp)) {
2878
+ if (existsSync8(sp)) {
2151
2879
  try {
2152
- const state = readFileSync5(sp);
2880
+ const state = readFileSync6(sp);
2153
2881
  Y.applyUpdate(doc, new Uint8Array(state));
2154
2882
  } catch {
2155
2883
  console.warn("[crdt-sync] WARN: corrupted state file, rebuilding from DB");
@@ -2291,10 +3019,10 @@ function persistState() {
2291
3019
  if (!doc) return;
2292
3020
  try {
2293
3021
  const sp = getStatePath();
2294
- const dir = path6.dirname(sp);
2295
- if (!existsSync6(dir)) mkdirSync2(dir, { recursive: true });
3022
+ const dir = path8.dirname(sp);
3023
+ if (!existsSync8(dir)) mkdirSync3(dir, { recursive: true });
2296
3024
  const state = Y.encodeStateAsUpdate(doc);
2297
- writeFileSync3(sp, Buffer.from(state));
3025
+ writeFileSync4(sp, Buffer.from(state));
2298
3026
  } catch {
2299
3027
  }
2300
3028
  }
@@ -2335,7 +3063,7 @@ var DEFAULT_STATE_PATH, _statePathOverride, doc;
2335
3063
  var init_crdt_sync = __esm({
2336
3064
  "src/lib/crdt-sync.ts"() {
2337
3065
  "use strict";
2338
- DEFAULT_STATE_PATH = path6.join(homedir(), ".exe-os", "crdt-state.bin");
3066
+ DEFAULT_STATE_PATH = path8.join(homedir(), ".exe-os", "crdt-state.bin");
2339
3067
  _statePathOverride = null;
2340
3068
  doc = null;
2341
3069
  }
@@ -2367,39 +3095,107 @@ __export(cloud_sync_exports, {
2367
3095
  cloudSync: () => cloudSync,
2368
3096
  mergeConfig: () => mergeConfig,
2369
3097
  mergeRosterFromRemote: () => mergeRosterFromRemote,
3098
+ pushToPostgres: () => pushToPostgres,
2370
3099
  recordRosterDeletion: () => recordRosterDeletion
2371
3100
  });
2372
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync7, readdirSync, mkdirSync as mkdirSync3, appendFileSync, unlinkSync as unlinkSync4, openSync as openSync2, closeSync as closeSync2 } from "fs";
2373
- import crypto2 from "crypto";
2374
- import path7 from "path";
3101
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync9, readdirSync, mkdirSync as mkdirSync4, appendFileSync, unlinkSync as unlinkSync4, openSync as openSync2, closeSync as closeSync2 } from "fs";
3102
+ import crypto3 from "crypto";
3103
+ import path9 from "path";
2375
3104
  import { homedir as homedir2 } from "os";
2376
3105
  function sqlSafe(v) {
2377
3106
  return v === void 0 ? null : v;
2378
3107
  }
2379
3108
  function logError(msg) {
2380
3109
  try {
2381
- const logPath = path7.join(homedir2(), ".exe-os", "workers.log");
3110
+ const logPath = path9.join(homedir2(), ".exe-os", "workers.log");
2382
3111
  appendFileSync(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
2383
3112
  `);
2384
3113
  } catch {
2385
3114
  }
2386
3115
  }
3116
+ function loadPgClient() {
3117
+ if (_pgFailed) return null;
3118
+ const postgresUrl = process.env.DATABASE_URL;
3119
+ const configPath = path9.join(EXE_AI_DIR, "config.json");
3120
+ let cloudPostgresUrl;
3121
+ try {
3122
+ if (existsSync9(configPath)) {
3123
+ const cfg = JSON.parse(readFileSync7(configPath, "utf8"));
3124
+ cloudPostgresUrl = cfg.cloud?.postgresUrl;
3125
+ if (cfg.cloud?.syncToPostgres === false) {
3126
+ _pgFailed = true;
3127
+ return null;
3128
+ }
3129
+ }
3130
+ } catch {
3131
+ }
3132
+ const url = postgresUrl || cloudPostgresUrl;
3133
+ if (!url) {
3134
+ _pgFailed = true;
3135
+ return null;
3136
+ }
3137
+ if (!_pgPromise) {
3138
+ _pgPromise = (async () => {
3139
+ const { createRequire: createRequire3 } = await import("module");
3140
+ const { pathToFileURL: pathToFileURL3 } = await import("url");
3141
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path9.join(homedir2(), "exe-db");
3142
+ const req = createRequire3(path9.join(exeDbRoot, "package.json"));
3143
+ const entry = req.resolve("@prisma/client");
3144
+ const mod = await import(pathToFileURL3(entry).href);
3145
+ const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
3146
+ if (!Ctor) throw new Error("No PrismaClient");
3147
+ return new Ctor();
3148
+ })().catch(() => {
3149
+ _pgFailed = true;
3150
+ _pgPromise = null;
3151
+ throw new Error("pg_unavailable");
3152
+ });
3153
+ }
3154
+ return _pgPromise;
3155
+ }
3156
+ async function pushToPostgres(records) {
3157
+ const loader = loadPgClient();
3158
+ if (!loader) return 0;
3159
+ let prisma;
3160
+ try {
3161
+ prisma = await loader;
3162
+ } catch {
3163
+ return 0;
3164
+ }
3165
+ let inserted = 0;
3166
+ for (const rec of records) {
3167
+ try {
3168
+ await prisma.$executeRawUnsafe(
3169
+ `INSERT INTO raw.raw_events (id, source, source_id, event_type, payload, metadata, timestamp)
3170
+ VALUES (gen_random_uuid(), 'cloud_sync', $1, 'memory', $2::jsonb, $3::jsonb, $4)
3171
+ ON CONFLICT (source, source_id, event_type) DO NOTHING`,
3172
+ String(rec.id ?? ""),
3173
+ JSON.stringify(rec),
3174
+ JSON.stringify({ agent_id: rec.agent_id, project_name: rec.project_name, tool_name: rec.tool_name }),
3175
+ rec.timestamp ? new Date(String(rec.timestamp)) : /* @__PURE__ */ new Date()
3176
+ );
3177
+ inserted++;
3178
+ } catch {
3179
+ }
3180
+ }
3181
+ return inserted;
3182
+ }
2387
3183
  async function withRosterLock(fn) {
2388
3184
  try {
2389
3185
  const fd = openSync2(ROSTER_LOCK_PATH, "wx");
2390
3186
  closeSync2(fd);
2391
- writeFileSync4(ROSTER_LOCK_PATH, String(Date.now()));
3187
+ writeFileSync5(ROSTER_LOCK_PATH, String(Date.now()));
2392
3188
  } catch (err) {
2393
3189
  if (err.code === "EEXIST") {
2394
3190
  try {
2395
- const ts = parseInt(readFileSync6(ROSTER_LOCK_PATH, "utf-8"), 10);
3191
+ const ts = parseInt(readFileSync7(ROSTER_LOCK_PATH, "utf-8"), 10);
2396
3192
  if (Date.now() - ts < LOCK_STALE_MS) {
2397
3193
  throw new Error("Roster merge already in progress \u2014 another sync is running");
2398
3194
  }
2399
3195
  unlinkSync4(ROSTER_LOCK_PATH);
2400
3196
  const fd = openSync2(ROSTER_LOCK_PATH, "wx");
2401
3197
  closeSync2(fd);
2402
- writeFileSync4(ROSTER_LOCK_PATH, String(Date.now()));
3198
+ writeFileSync5(ROSTER_LOCK_PATH, String(Date.now()));
2403
3199
  } catch (retryErr) {
2404
3200
  if (retryErr instanceof Error && retryErr.message.includes("already in progress")) throw retryErr;
2405
3201
  throw new Error("Roster merge already in progress \u2014 another sync is running");
@@ -2669,6 +3465,10 @@ async function cloudSync(config) {
2669
3465
  const maxVersion = Number(records[records.length - 1].version);
2670
3466
  const pushOk = await cloudPush(records, maxVersion, config);
2671
3467
  if (!pushOk) break;
3468
+ try {
3469
+ await pushToPostgres(records);
3470
+ } catch {
3471
+ }
2672
3472
  await client.execute({
2673
3473
  sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_cloud_push_version', ?)",
2674
3474
  args: [String(maxVersion)]
@@ -2773,8 +3573,8 @@ async function cloudSync(config) {
2773
3573
  try {
2774
3574
  const employees = await loadEmployees();
2775
3575
  rosterResult.employees = employees.length;
2776
- const idDir = path7.join(EXE_AI_DIR, "identity");
2777
- if (existsSync7(idDir)) {
3576
+ const idDir = path9.join(EXE_AI_DIR, "identity");
3577
+ if (existsSync9(idDir)) {
2778
3578
  rosterResult.identities = readdirSync(idDir).filter((f) => f.endsWith(".md")).length;
2779
3579
  }
2780
3580
  } catch {
@@ -2795,62 +3595,62 @@ async function cloudSync(config) {
2795
3595
  function recordRosterDeletion(name) {
2796
3596
  let deletions = [];
2797
3597
  try {
2798
- if (existsSync7(ROSTER_DELETIONS_PATH)) {
2799
- deletions = JSON.parse(readFileSync6(ROSTER_DELETIONS_PATH, "utf-8"));
3598
+ if (existsSync9(ROSTER_DELETIONS_PATH)) {
3599
+ deletions = JSON.parse(readFileSync7(ROSTER_DELETIONS_PATH, "utf-8"));
2800
3600
  }
2801
3601
  } catch {
2802
3602
  }
2803
3603
  if (!deletions.includes(name)) deletions.push(name);
2804
- writeFileSync4(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
3604
+ writeFileSync5(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
2805
3605
  }
2806
3606
  function consumeRosterDeletions() {
2807
3607
  try {
2808
- if (!existsSync7(ROSTER_DELETIONS_PATH)) return [];
2809
- const deletions = JSON.parse(readFileSync6(ROSTER_DELETIONS_PATH, "utf-8"));
2810
- writeFileSync4(ROSTER_DELETIONS_PATH, "[]");
3608
+ if (!existsSync9(ROSTER_DELETIONS_PATH)) return [];
3609
+ const deletions = JSON.parse(readFileSync7(ROSTER_DELETIONS_PATH, "utf-8"));
3610
+ writeFileSync5(ROSTER_DELETIONS_PATH, "[]");
2811
3611
  return deletions;
2812
3612
  } catch {
2813
3613
  return [];
2814
3614
  }
2815
3615
  }
2816
3616
  function buildRosterBlob(paths) {
2817
- const rosterPath = paths?.rosterPath ?? path7.join(EXE_AI_DIR, "exe-employees.json");
2818
- const identityDir = paths?.identityDir ?? path7.join(EXE_AI_DIR, "identity");
2819
- const configPath = paths?.configPath ?? path7.join(EXE_AI_DIR, "config.json");
3617
+ const rosterPath = paths?.rosterPath ?? path9.join(EXE_AI_DIR, "exe-employees.json");
3618
+ const identityDir = paths?.identityDir ?? path9.join(EXE_AI_DIR, "identity");
3619
+ const configPath = paths?.configPath ?? path9.join(EXE_AI_DIR, "config.json");
2820
3620
  let roster = [];
2821
- if (existsSync7(rosterPath)) {
3621
+ if (existsSync9(rosterPath)) {
2822
3622
  try {
2823
- roster = JSON.parse(readFileSync6(rosterPath, "utf-8"));
3623
+ roster = JSON.parse(readFileSync7(rosterPath, "utf-8"));
2824
3624
  } catch {
2825
3625
  }
2826
3626
  }
2827
3627
  const identities = {};
2828
- if (existsSync7(identityDir)) {
3628
+ if (existsSync9(identityDir)) {
2829
3629
  for (const file of readdirSync(identityDir).filter((f) => f.endsWith(".md"))) {
2830
3630
  try {
2831
- identities[file] = readFileSync6(path7.join(identityDir, file), "utf-8");
3631
+ identities[file] = readFileSync7(path9.join(identityDir, file), "utf-8");
2832
3632
  } catch {
2833
3633
  }
2834
3634
  }
2835
3635
  }
2836
3636
  let config;
2837
- if (existsSync7(configPath)) {
3637
+ if (existsSync9(configPath)) {
2838
3638
  try {
2839
- config = JSON.parse(readFileSync6(configPath, "utf-8"));
3639
+ config = JSON.parse(readFileSync7(configPath, "utf-8"));
2840
3640
  } catch {
2841
3641
  }
2842
3642
  }
2843
3643
  let agentConfig;
2844
- const agentConfigPath = path7.join(EXE_AI_DIR, "agent-config.json");
2845
- if (existsSync7(agentConfigPath)) {
3644
+ const agentConfigPath = path9.join(EXE_AI_DIR, "agent-config.json");
3645
+ if (existsSync9(agentConfigPath)) {
2846
3646
  try {
2847
- agentConfig = JSON.parse(readFileSync6(agentConfigPath, "utf-8"));
3647
+ agentConfig = JSON.parse(readFileSync7(agentConfigPath, "utf-8"));
2848
3648
  } catch {
2849
3649
  }
2850
3650
  }
2851
3651
  const deletedNames = consumeRosterDeletions();
2852
3652
  const content = JSON.stringify({ roster, identities, config, agentConfig, deletedNames });
2853
- const hash = crypto2.createHash("sha256").update(content).digest("hex").slice(0, 16);
3653
+ const hash = crypto3.createHash("sha256").update(content).digest("hex").slice(0, 16);
2854
3654
  return { roster, identities, config, agentConfig, deletedNames, version: hash };
2855
3655
  }
2856
3656
  async function cloudPushRoster(config) {
@@ -2920,23 +3720,24 @@ async function cloudPullRoster(config) {
2920
3720
  }
2921
3721
  }
2922
3722
  function mergeConfig(remoteConfig, configPath) {
2923
- const cfgPath = configPath ?? path7.join(EXE_AI_DIR, "config.json");
3723
+ const cfgPath = configPath ?? path9.join(EXE_AI_DIR, "config.json");
2924
3724
  let local = {};
2925
- if (existsSync7(cfgPath)) {
3725
+ if (existsSync9(cfgPath)) {
2926
3726
  try {
2927
- local = JSON.parse(readFileSync6(cfgPath, "utf-8"));
3727
+ local = JSON.parse(readFileSync7(cfgPath, "utf-8"));
2928
3728
  } catch {
2929
3729
  }
2930
3730
  }
2931
3731
  const merged = { ...remoteConfig, ...local };
2932
- const dir = path7.dirname(cfgPath);
2933
- if (!existsSync7(dir)) mkdirSync3(dir, { recursive: true });
2934
- writeFileSync4(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
3732
+ const dir = path9.dirname(cfgPath);
3733
+ ensurePrivateDirSync(dir);
3734
+ writeFileSync5(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
3735
+ enforcePrivateFileSync(cfgPath);
2935
3736
  }
2936
3737
  async function mergeRosterFromRemote(remote, paths) {
2937
3738
  return withRosterLock(async () => {
2938
3739
  const rosterPath = paths?.rosterPath ?? void 0;
2939
- const identityDir = paths?.identityDir ?? path7.join(EXE_AI_DIR, "identity");
3740
+ const identityDir = paths?.identityDir ?? path9.join(EXE_AI_DIR, "identity");
2940
3741
  const localEmployees = await loadEmployees(rosterPath);
2941
3742
  const localNames = new Set(localEmployees.map((e) => e.name));
2942
3743
  let added = 0;
@@ -2957,15 +3758,15 @@ async function mergeRosterFromRemote(remote, paths) {
2957
3758
  ) ?? lookupKey;
2958
3759
  const remoteIdentity = remote.identities[matchedKey];
2959
3760
  if (remoteIdentity) {
2960
- if (!existsSync7(identityDir)) mkdirSync3(identityDir, { recursive: true });
2961
- const idPath = path7.join(identityDir, `${remoteEmp.name}.md`);
3761
+ if (!existsSync9(identityDir)) mkdirSync4(identityDir, { recursive: true });
3762
+ const idPath = path9.join(identityDir, `${remoteEmp.name}.md`);
2962
3763
  let localIdentity = null;
2963
3764
  try {
2964
- localIdentity = existsSync7(idPath) ? readFileSync6(idPath, "utf-8") : null;
3765
+ localIdentity = existsSync9(idPath) ? readFileSync7(idPath, "utf-8") : null;
2965
3766
  } catch {
2966
3767
  }
2967
3768
  if (localIdentity !== remoteIdentity) {
2968
- writeFileSync4(idPath, remoteIdentity, "utf-8");
3769
+ writeFileSync5(idPath, remoteIdentity, "utf-8");
2969
3770
  identitiesUpdated++;
2970
3771
  }
2971
3772
  }
@@ -2991,16 +3792,18 @@ async function mergeRosterFromRemote(remote, paths) {
2991
3792
  }
2992
3793
  if (remote.agentConfig && Object.keys(remote.agentConfig).length > 0) {
2993
3794
  try {
2994
- const agentConfigPath = path7.join(EXE_AI_DIR, "agent-config.json");
3795
+ const agentConfigPath = path9.join(EXE_AI_DIR, "agent-config.json");
2995
3796
  let local = {};
2996
- if (existsSync7(agentConfigPath)) {
3797
+ if (existsSync9(agentConfigPath)) {
2997
3798
  try {
2998
- local = JSON.parse(readFileSync6(agentConfigPath, "utf-8"));
3799
+ local = JSON.parse(readFileSync7(agentConfigPath, "utf-8"));
2999
3800
  } catch {
3000
3801
  }
3001
3802
  }
3002
3803
  const merged = { ...remote.agentConfig, ...local };
3003
- writeFileSync4(agentConfigPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
3804
+ ensurePrivateDirSync(path9.dirname(agentConfigPath));
3805
+ writeFileSync5(agentConfigPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
3806
+ enforcePrivateFileSync(agentConfigPath);
3004
3807
  } catch {
3005
3808
  }
3006
3809
  }
@@ -3424,7 +4227,7 @@ async function cloudPullDocuments(config) {
3424
4227
  }
3425
4228
  return { pulled };
3426
4229
  }
3427
- var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS, PUSH_BATCH_SIZE, ROSTER_LOCK_PATH, LOCK_STALE_MS, ROSTER_DELETIONS_PATH;
4230
+ var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS, PUSH_BATCH_SIZE, ROSTER_LOCK_PATH, LOCK_STALE_MS, _pgPromise, _pgFailed, ROSTER_DELETIONS_PATH;
3428
4231
  var init_cloud_sync = __esm({
3429
4232
  "src/lib/cloud-sync.ts"() {
3430
4233
  "use strict";
@@ -3435,12 +4238,15 @@ var init_cloud_sync = __esm({
3435
4238
  init_config();
3436
4239
  init_crdt_sync();
3437
4240
  init_employees();
4241
+ init_secure_files();
3438
4242
  LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
3439
4243
  FETCH_TIMEOUT_MS = 3e4;
3440
4244
  PUSH_BATCH_SIZE = 5e3;
3441
- ROSTER_LOCK_PATH = path7.join(EXE_AI_DIR, "roster-merge.lock");
4245
+ ROSTER_LOCK_PATH = path9.join(EXE_AI_DIR, "roster-merge.lock");
3442
4246
  LOCK_STALE_MS = 3e4;
3443
- ROSTER_DELETIONS_PATH = path7.join(EXE_AI_DIR, "roster-deletions.json");
4247
+ _pgPromise = null;
4248
+ _pgFailed = false;
4249
+ ROSTER_DELETIONS_PATH = path9.join(EXE_AI_DIR, "roster-deletions.json");
3444
4250
  }
3445
4251
  });
3446
4252