@askexenow/exe-os 0.9.8 → 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 +222 -49
  2. package/dist/bin/backfill-responses.js +221 -48
  3. package/dist/bin/backfill-vectors.js +225 -52
  4. package/dist/bin/cleanup-stale-review-tasks.js +150 -28
  5. package/dist/bin/cli.js +1295 -856
  6. package/dist/bin/exe-agent-config.js +36 -8
  7. package/dist/bin/exe-agent.js +14 -4
  8. package/dist/bin/exe-assign.js +221 -48
  9. package/dist/bin/exe-boot.js +778 -427
  10. package/dist/bin/exe-call.js +41 -13
  11. package/dist/bin/exe-cloud.js +163 -58
  12. package/dist/bin/exe-dispatch.js +276 -139
  13. package/dist/bin/exe-doctor.js +145 -27
  14. package/dist/bin/exe-export-behaviors.js +141 -23
  15. package/dist/bin/exe-forget.js +137 -19
  16. package/dist/bin/exe-gateway.js +677 -388
  17. package/dist/bin/exe-heartbeat.js +227 -108
  18. package/dist/bin/exe-kill.js +138 -20
  19. package/dist/bin/exe-launch-agent.js +172 -39
  20. package/dist/bin/exe-link.js +291 -100
  21. package/dist/bin/exe-new-employee.js +214 -106
  22. package/dist/bin/exe-pending-messages.js +395 -33
  23. package/dist/bin/exe-pending-notifications.js +684 -99
  24. package/dist/bin/exe-pending-reviews.js +420 -74
  25. package/dist/bin/exe-rename.js +147 -49
  26. package/dist/bin/exe-review.js +138 -20
  27. package/dist/bin/exe-search.js +240 -69
  28. package/dist/bin/exe-session-cleanup.js +440 -250
  29. package/dist/bin/exe-settings.js +61 -17
  30. package/dist/bin/exe-start-codex.js +158 -39
  31. package/dist/bin/exe-start-opencode.js +157 -38
  32. package/dist/bin/exe-status.js +151 -29
  33. package/dist/bin/exe-team.js +138 -20
  34. package/dist/bin/git-sweep.js +404 -212
  35. package/dist/bin/graph-backfill.js +137 -19
  36. package/dist/bin/graph-export.js +140 -22
  37. package/dist/bin/install.js +90 -61
  38. package/dist/bin/scan-tasks.js +412 -220
  39. package/dist/bin/setup.js +564 -293
  40. package/dist/bin/shard-migrate.js +139 -21
  41. package/dist/bin/update.js +138 -49
  42. package/dist/bin/wiki-sync.js +137 -19
  43. package/dist/gateway/index.js +533 -320
  44. package/dist/hooks/bug-report-worker.js +344 -193
  45. package/dist/hooks/codex-stop-task-finalizer.js +4678 -0
  46. package/dist/hooks/commit-complete.js +402 -210
  47. package/dist/hooks/error-recall.js +245 -74
  48. package/dist/hooks/exe-heartbeat-hook.js +16 -6
  49. package/dist/hooks/ingest-worker.js +3423 -3157
  50. package/dist/hooks/ingest.js +832 -97
  51. package/dist/hooks/instructions-loaded.js +227 -54
  52. package/dist/hooks/notification.js +216 -43
  53. package/dist/hooks/post-compact.js +239 -62
  54. package/dist/hooks/pre-compact.js +408 -216
  55. package/dist/hooks/pre-tool-use.js +268 -90
  56. package/dist/hooks/prompt-ingest-worker.js +352 -102
  57. package/dist/hooks/prompt-submit.js +541 -328
  58. package/dist/hooks/response-ingest-worker.js +372 -122
  59. package/dist/hooks/session-end.js +443 -240
  60. package/dist/hooks/session-start.js +313 -127
  61. package/dist/hooks/stop.js +293 -98
  62. package/dist/hooks/subagent-stop.js +239 -62
  63. package/dist/hooks/summary-worker.js +568 -236
  64. package/dist/index.js +538 -324
  65. package/dist/lib/agent-config.js +28 -6
  66. package/dist/lib/cloud-sync.js +284 -105
  67. package/dist/lib/config.js +30 -10
  68. package/dist/lib/consolidation.js +16 -6
  69. package/dist/lib/database.js +123 -25
  70. package/dist/lib/db-daemon-client.js +73 -19
  71. package/dist/lib/db.js +123 -25
  72. package/dist/lib/device-registry.js +133 -35
  73. package/dist/lib/embedder.js +107 -32
  74. package/dist/lib/employee-templates.js +14 -4
  75. package/dist/lib/employees.js +41 -13
  76. package/dist/lib/exe-daemon-client.js +88 -22
  77. package/dist/lib/exe-daemon.js +935 -587
  78. package/dist/lib/hybrid-search.js +240 -69
  79. package/dist/lib/identity.js +18 -8
  80. package/dist/lib/license.js +133 -48
  81. package/dist/lib/messaging.js +116 -56
  82. package/dist/lib/reminders.js +14 -4
  83. package/dist/lib/schedules.js +137 -19
  84. package/dist/lib/skill-learning.js +33 -6
  85. package/dist/lib/store.js +137 -19
  86. package/dist/lib/task-router.js +14 -4
  87. package/dist/lib/tasks.js +280 -234
  88. package/dist/lib/tmux-routing.js +172 -125
  89. package/dist/lib/token-spend.js +26 -8
  90. package/dist/mcp/server.js +1326 -609
  91. package/dist/mcp/tools/complete-reminder.js +14 -4
  92. package/dist/mcp/tools/create-reminder.js +14 -4
  93. package/dist/mcp/tools/create-task.js +306 -248
  94. package/dist/mcp/tools/deactivate-behavior.js +16 -6
  95. package/dist/mcp/tools/list-reminders.js +14 -4
  96. package/dist/mcp/tools/list-tasks.js +123 -107
  97. package/dist/mcp/tools/send-message.js +75 -29
  98. package/dist/mcp/tools/update-task.js +1848 -199
  99. package/dist/runtime/index.js +441 -248
  100. package/dist/tui/App.js +761 -424
  101. package/package.json +1 -1
@@ -63,9 +63,34 @@ var init_db_retry = __esm({
63
63
  }
64
64
  });
65
65
 
66
+ // src/lib/secure-files.ts
67
+ import { chmodSync, existsSync, mkdirSync } from "fs";
68
+ import { chmod, mkdir } from "fs/promises";
69
+ function ensurePrivateDirSync(dirPath) {
70
+ mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
71
+ try {
72
+ chmodSync(dirPath, PRIVATE_DIR_MODE);
73
+ } catch {
74
+ }
75
+ }
76
+ function enforcePrivateFileSync(filePath) {
77
+ try {
78
+ if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
79
+ } catch {
80
+ }
81
+ }
82
+ var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
83
+ var init_secure_files = __esm({
84
+ "src/lib/secure-files.ts"() {
85
+ "use strict";
86
+ PRIVATE_DIR_MODE = 448;
87
+ PRIVATE_FILE_MODE = 384;
88
+ }
89
+ });
90
+
66
91
  // src/lib/config.ts
67
- import { readFile, writeFile, mkdir, chmod } from "fs/promises";
68
- import { readFileSync, existsSync, renameSync } from "fs";
92
+ import { readFile, writeFile } from "fs/promises";
93
+ import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
69
94
  import path from "path";
70
95
  import os from "os";
71
96
  function resolveDataDir() {
@@ -73,7 +98,7 @@ function resolveDataDir() {
73
98
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
74
99
  const newDir = path.join(os.homedir(), ".exe-os");
75
100
  const legacyDir = path.join(os.homedir(), ".exe-mem");
76
- if (!existsSync(newDir) && existsSync(legacyDir)) {
101
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
77
102
  try {
78
103
  renameSync(legacyDir, newDir);
79
104
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -88,6 +113,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
88
113
  var init_config = __esm({
89
114
  "src/lib/config.ts"() {
90
115
  "use strict";
116
+ init_secure_files();
91
117
  EXE_AI_DIR = resolveDataDir();
92
118
  DB_PATH = path.join(EXE_AI_DIR, "memories.db");
93
119
  MODELS_DIR = path.join(EXE_AI_DIR, "models");
@@ -156,7 +182,7 @@ var init_config = __esm({
156
182
 
157
183
  // src/lib/employees.ts
158
184
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
159
- import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
185
+ import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
160
186
  import { execSync } from "child_process";
161
187
  import path2 from "path";
162
188
  import os2 from "os";
@@ -173,7 +199,7 @@ function getCoordinatorName(employees = loadEmployeesSync()) {
173
199
  return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
174
200
  }
175
201
  async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
176
- if (!existsSync2(employeesPath)) {
202
+ if (!existsSync3(employeesPath)) {
177
203
  return [];
178
204
  }
179
205
  const raw = await readFile2(employeesPath, "utf-8");
@@ -188,7 +214,7 @@ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
188
214
  await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
189
215
  }
190
216
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
191
- if (!existsSync2(employeesPath)) return [];
217
+ if (!existsSync3(employeesPath)) return [];
192
218
  try {
193
219
  return JSON.parse(readFileSync2(employeesPath, "utf-8"));
194
220
  } catch {
@@ -222,7 +248,7 @@ function registerBinSymlinks(name) {
222
248
  for (const suffix of ["", "-opencode"]) {
223
249
  const linkName = `${name}${suffix}`;
224
250
  const linkPath = path2.join(binDir, linkName);
225
- if (existsSync2(linkPath)) {
251
+ if (existsSync3(linkPath)) {
226
252
  skipped.push(linkName);
227
253
  continue;
228
254
  }
@@ -831,13 +857,50 @@ var init_database_adapter = __esm({
831
857
  }
832
858
  });
833
859
 
860
+ // src/lib/daemon-auth.ts
861
+ import crypto from "crypto";
862
+ import path4 from "path";
863
+ import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
864
+ function normalizeToken(token) {
865
+ if (!token) return null;
866
+ const trimmed = token.trim();
867
+ return trimmed.length > 0 ? trimmed : null;
868
+ }
869
+ function readDaemonToken() {
870
+ try {
871
+ if (!existsSync4(DAEMON_TOKEN_PATH)) return null;
872
+ return normalizeToken(readFileSync3(DAEMON_TOKEN_PATH, "utf8"));
873
+ } catch {
874
+ return null;
875
+ }
876
+ }
877
+ function ensureDaemonToken(seed) {
878
+ const existing = readDaemonToken();
879
+ if (existing) return existing;
880
+ const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
881
+ ensurePrivateDirSync(EXE_AI_DIR);
882
+ writeFileSync2(DAEMON_TOKEN_PATH, `${token}
883
+ `, "utf8");
884
+ enforcePrivateFileSync(DAEMON_TOKEN_PATH);
885
+ return token;
886
+ }
887
+ var DAEMON_TOKEN_PATH;
888
+ var init_daemon_auth = __esm({
889
+ "src/lib/daemon-auth.ts"() {
890
+ "use strict";
891
+ init_config();
892
+ init_secure_files();
893
+ DAEMON_TOKEN_PATH = path4.join(EXE_AI_DIR, "exed.token");
894
+ }
895
+ });
896
+
834
897
  // src/lib/exe-daemon-client.ts
835
898
  import net from "net";
836
899
  import os4 from "os";
837
900
  import { spawn } from "child_process";
838
901
  import { randomUUID } from "crypto";
839
- import { existsSync as existsSync3, unlinkSync as unlinkSync2, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
840
- import path4 from "path";
902
+ import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
903
+ import path5 from "path";
841
904
  import { fileURLToPath } from "url";
842
905
  function handleData(chunk) {
843
906
  _buffer += chunk.toString();
@@ -865,9 +928,9 @@ function handleData(chunk) {
865
928
  }
866
929
  }
867
930
  function cleanupStaleFiles() {
868
- if (existsSync3(PID_PATH)) {
931
+ if (existsSync5(PID_PATH)) {
869
932
  try {
870
- const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
933
+ const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
871
934
  if (pid > 0) {
872
935
  try {
873
936
  process.kill(pid, 0);
@@ -888,11 +951,11 @@ function cleanupStaleFiles() {
888
951
  }
889
952
  }
890
953
  function findPackageRoot() {
891
- let dir = path4.dirname(fileURLToPath(import.meta.url));
892
- const { root } = path4.parse(dir);
954
+ let dir = path5.dirname(fileURLToPath(import.meta.url));
955
+ const { root } = path5.parse(dir);
893
956
  while (dir !== root) {
894
- if (existsSync3(path4.join(dir, "package.json"))) return dir;
895
- dir = path4.dirname(dir);
957
+ if (existsSync5(path5.join(dir, "package.json"))) return dir;
958
+ dir = path5.dirname(dir);
896
959
  }
897
960
  return null;
898
961
  }
@@ -918,16 +981,17 @@ function spawnDaemon() {
918
981
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
919
982
  return;
920
983
  }
921
- const daemonPath = path4.join(pkgRoot, "dist", "lib", "exe-daemon.js");
922
- if (!existsSync3(daemonPath)) {
984
+ const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
985
+ if (!existsSync5(daemonPath)) {
923
986
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
924
987
  `);
925
988
  return;
926
989
  }
927
990
  const resolvedPath = daemonPath;
991
+ const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
928
992
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
929
993
  `);
930
- const logPath = path4.join(path4.dirname(SOCKET_PATH), "exed.log");
994
+ const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
931
995
  let stderrFd = "ignore";
932
996
  try {
933
997
  stderrFd = openSync(logPath, "a");
@@ -945,7 +1009,8 @@ function spawnDaemon() {
945
1009
  TMUX_PANE: void 0,
946
1010
  // Prevents resolveExeSession() from scoping to one session
947
1011
  EXE_DAEMON_SOCK: SOCKET_PATH,
948
- EXE_DAEMON_PID: PID_PATH
1012
+ EXE_DAEMON_PID: PID_PATH,
1013
+ [DAEMON_TOKEN_ENV]: daemonToken
949
1014
  }
950
1015
  });
951
1016
  child.unref();
@@ -1052,13 +1117,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
1052
1117
  return;
1053
1118
  }
1054
1119
  const id = randomUUID();
1120
+ const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
1055
1121
  const timer = setTimeout(() => {
1056
1122
  _pending.delete(id);
1057
1123
  resolve({ error: "Request timeout" });
1058
1124
  }, timeoutMs);
1059
1125
  _pending.set(id, { resolve, timer });
1060
1126
  try {
1061
- _socket.write(JSON.stringify({ id, ...payload }) + "\n");
1127
+ _socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
1062
1128
  } catch {
1063
1129
  clearTimeout(timer);
1064
1130
  _pending.delete(id);
@@ -1069,17 +1135,19 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
1069
1135
  function isClientConnected() {
1070
1136
  return _connected;
1071
1137
  }
1072
- var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
1138
+ 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;
1073
1139
  var init_exe_daemon_client = __esm({
1074
1140
  "src/lib/exe-daemon-client.ts"() {
1075
1141
  "use strict";
1076
1142
  init_config();
1077
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path4.join(EXE_AI_DIR, "exed.sock");
1078
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path4.join(EXE_AI_DIR, "exed.pid");
1079
- SPAWN_LOCK_PATH = path4.join(EXE_AI_DIR, "exed-spawn.lock");
1143
+ init_daemon_auth();
1144
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path5.join(EXE_AI_DIR, "exed.sock");
1145
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path5.join(EXE_AI_DIR, "exed.pid");
1146
+ SPAWN_LOCK_PATH = path5.join(EXE_AI_DIR, "exed-spawn.lock");
1080
1147
  SPAWN_LOCK_STALE_MS = 3e4;
1081
1148
  CONNECT_TIMEOUT_MS = 15e3;
1082
1149
  REQUEST_TIMEOUT_MS = 3e4;
1150
+ DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
1083
1151
  _socket = null;
1084
1152
  _connected = false;
1085
1153
  _buffer = "";
@@ -1658,6 +1726,7 @@ async function ensureSchema() {
1658
1726
  project TEXT NOT NULL,
1659
1727
  summary TEXT NOT NULL,
1660
1728
  task_file TEXT,
1729
+ session_scope TEXT,
1661
1730
  read INTEGER NOT NULL DEFAULT 0,
1662
1731
  created_at TEXT NOT NULL
1663
1732
  );
@@ -1666,7 +1735,7 @@ async function ensureSchema() {
1666
1735
  ON notifications(read);
1667
1736
 
1668
1737
  CREATE INDEX IF NOT EXISTS idx_notifications_agent
1669
- ON notifications(agent_id);
1738
+ ON notifications(agent_id, session_scope);
1670
1739
 
1671
1740
  CREATE INDEX IF NOT EXISTS idx_notifications_task_file
1672
1741
  ON notifications(task_file);
@@ -1704,6 +1773,7 @@ async function ensureSchema() {
1704
1773
  target_agent TEXT NOT NULL,
1705
1774
  target_project TEXT,
1706
1775
  target_device TEXT NOT NULL DEFAULT 'local',
1776
+ session_scope TEXT,
1707
1777
  content TEXT NOT NULL,
1708
1778
  priority TEXT DEFAULT 'normal',
1709
1779
  status TEXT DEFAULT 'pending',
@@ -1717,10 +1787,31 @@ async function ensureSchema() {
1717
1787
  );
1718
1788
 
1719
1789
  CREATE INDEX IF NOT EXISTS idx_messages_target
1720
- ON messages(target_agent, status);
1790
+ ON messages(target_agent, session_scope, status);
1721
1791
 
1722
1792
  CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
1723
- ON messages(target_agent, from_agent, server_seq);
1793
+ ON messages(target_agent, session_scope, from_agent, server_seq);
1794
+ `);
1795
+ try {
1796
+ await client.execute({
1797
+ sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
1798
+ args: []
1799
+ });
1800
+ } catch {
1801
+ }
1802
+ try {
1803
+ await client.execute({
1804
+ sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
1805
+ args: []
1806
+ });
1807
+ } catch {
1808
+ }
1809
+ await client.executeMultiple(`
1810
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
1811
+ ON notifications(agent_id, session_scope, read, created_at);
1812
+
1813
+ CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
1814
+ ON messages(target_agent, session_scope, status, created_at);
1724
1815
  `);
1725
1816
  try {
1726
1817
  await client.execute({
@@ -2304,6 +2395,13 @@ async function ensureSchema() {
2304
2395
  } catch {
2305
2396
  }
2306
2397
  }
2398
+ try {
2399
+ await client.execute({
2400
+ sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
2401
+ args: []
2402
+ });
2403
+ } catch {
2404
+ }
2307
2405
  }
2308
2406
  async function disposeDatabase() {
2309
2407
  if (_walCheckpointTimer) {
@@ -2360,8 +2458,8 @@ __export(crdt_sync_exports, {
2360
2458
  rebuildFromDb: () => rebuildFromDb
2361
2459
  });
2362
2460
  import * as Y from "yjs";
2363
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync5, mkdirSync as mkdirSync2, unlinkSync as unlinkSync3 } from "fs";
2364
- import path6 from "path";
2461
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync7, mkdirSync as mkdirSync3, unlinkSync as unlinkSync3 } from "fs";
2462
+ import path7 from "path";
2365
2463
  import { homedir } from "os";
2366
2464
  function getStatePath() {
2367
2465
  return _statePathOverride ?? DEFAULT_STATE_PATH;
@@ -2373,9 +2471,9 @@ function initCrdtDoc() {
2373
2471
  if (doc) return doc;
2374
2472
  doc = new Y.Doc();
2375
2473
  const sp = getStatePath();
2376
- if (existsSync5(sp)) {
2474
+ if (existsSync7(sp)) {
2377
2475
  try {
2378
- const state = readFileSync5(sp);
2476
+ const state = readFileSync6(sp);
2379
2477
  Y.applyUpdate(doc, new Uint8Array(state));
2380
2478
  } catch {
2381
2479
  console.warn("[crdt-sync] WARN: corrupted state file, rebuilding from DB");
@@ -2517,10 +2615,10 @@ function persistState() {
2517
2615
  if (!doc) return;
2518
2616
  try {
2519
2617
  const sp = getStatePath();
2520
- const dir = path6.dirname(sp);
2521
- if (!existsSync5(dir)) mkdirSync2(dir, { recursive: true });
2618
+ const dir = path7.dirname(sp);
2619
+ if (!existsSync7(dir)) mkdirSync3(dir, { recursive: true });
2522
2620
  const state = Y.encodeStateAsUpdate(doc);
2523
- writeFileSync3(sp, Buffer.from(state));
2621
+ writeFileSync4(sp, Buffer.from(state));
2524
2622
  } catch {
2525
2623
  }
2526
2624
  }
@@ -2561,7 +2659,7 @@ var DEFAULT_STATE_PATH, _statePathOverride, doc;
2561
2659
  var init_crdt_sync = __esm({
2562
2660
  "src/lib/crdt-sync.ts"() {
2563
2661
  "use strict";
2564
- DEFAULT_STATE_PATH = path6.join(homedir(), ".exe-os", "crdt-state.bin");
2662
+ DEFAULT_STATE_PATH = path7.join(homedir(), ".exe-os", "crdt-state.bin");
2565
2663
  _statePathOverride = null;
2566
2664
  doc = null;
2567
2665
  }
@@ -2577,14 +2675,14 @@ __export(keychain_exports, {
2577
2675
  setMasterKey: () => setMasterKey
2578
2676
  });
2579
2677
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
2580
- import { existsSync as existsSync6 } from "fs";
2581
- import path7 from "path";
2582
- import os5 from "os";
2678
+ import { existsSync as existsSync8 } from "fs";
2679
+ import path8 from "path";
2680
+ import os6 from "os";
2583
2681
  function getKeyDir() {
2584
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path7.join(os5.homedir(), ".exe-os");
2682
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path8.join(os6.homedir(), ".exe-os");
2585
2683
  }
2586
2684
  function getKeyPath() {
2587
- return path7.join(getKeyDir(), "master.key");
2685
+ return path8.join(getKeyDir(), "master.key");
2588
2686
  }
2589
2687
  async function tryKeytar() {
2590
2688
  try {
@@ -2605,9 +2703,9 @@ async function getMasterKey() {
2605
2703
  }
2606
2704
  }
2607
2705
  const keyPath = getKeyPath();
2608
- if (!existsSync6(keyPath)) {
2706
+ if (!existsSync8(keyPath)) {
2609
2707
  process.stderr.write(
2610
- `[keychain] Key not found at ${keyPath} (HOME=${os5.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
2708
+ `[keychain] Key not found at ${keyPath} (HOME=${os6.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
2611
2709
  `
2612
2710
  );
2613
2711
  return null;
@@ -2648,7 +2746,7 @@ async function deleteMasterKey() {
2648
2746
  }
2649
2747
  }
2650
2748
  const keyPath = getKeyPath();
2651
- if (existsSync6(keyPath)) {
2749
+ if (existsSync8(keyPath)) {
2652
2750
  await unlink(keyPath);
2653
2751
  }
2654
2752
  }
@@ -2692,13 +2790,13 @@ var init_keychain = __esm({
2692
2790
 
2693
2791
  // src/lib/cloud-sync.ts
2694
2792
  init_database();
2695
- 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";
2696
- import crypto2 from "crypto";
2697
- import path8 from "path";
2793
+ 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";
2794
+ import crypto3 from "crypto";
2795
+ import path9 from "path";
2698
2796
  import { homedir as homedir2 } from "os";
2699
2797
 
2700
2798
  // src/lib/crypto.ts
2701
- import crypto from "crypto";
2799
+ import crypto2 from "crypto";
2702
2800
  var ALGORITHM = "aes-256-gcm";
2703
2801
  var IV_LENGTH = 12;
2704
2802
  var TAG_LENGTH = 16;
@@ -2709,7 +2807,7 @@ function initSyncCrypto(masterKey) {
2709
2807
  throw new Error(`Master key must be 32 bytes, got ${masterKey.length}`);
2710
2808
  }
2711
2809
  _syncKey = Buffer.from(
2712
- crypto.hkdfSync("sha256", masterKey, "", SYNC_HKDF_INFO, 32)
2810
+ crypto2.hkdfSync("sha256", masterKey, "", SYNC_HKDF_INFO, 32)
2713
2811
  );
2714
2812
  }
2715
2813
  function isSyncCryptoInitialized() {
@@ -2723,8 +2821,8 @@ function requireSyncKey() {
2723
2821
  }
2724
2822
  function encryptSyncBlob(data) {
2725
2823
  const key = requireSyncKey();
2726
- const iv = crypto.randomBytes(IV_LENGTH);
2727
- const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
2824
+ const iv = crypto2.randomBytes(IV_LENGTH);
2825
+ const cipher = crypto2.createCipheriv(ALGORITHM, key, iv);
2728
2826
  const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
2729
2827
  const tag = cipher.getAuthTag();
2730
2828
  return Buffer.concat([iv, encrypted, tag]).toString("base64");
@@ -2738,7 +2836,7 @@ function decryptSyncBlob(ciphertext) {
2738
2836
  const iv = combined.subarray(0, IV_LENGTH);
2739
2837
  const tag = combined.subarray(combined.length - TAG_LENGTH);
2740
2838
  const encrypted = combined.subarray(IV_LENGTH, combined.length - TAG_LENGTH);
2741
- const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
2839
+ const decipher = crypto2.createDecipheriv(ALGORITHM, key, iv);
2742
2840
  decipher.setAuthTag(tag);
2743
2841
  return Buffer.concat([decipher.update(encrypted), decipher.final()]);
2744
2842
  }
@@ -2760,32 +2858,35 @@ function decompress(input) {
2760
2858
 
2761
2859
  // src/lib/license.ts
2762
2860
  init_config();
2763
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync4, mkdirSync } from "fs";
2861
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync6, mkdirSync as mkdirSync2 } from "fs";
2764
2862
  import { randomUUID as randomUUID2 } from "crypto";
2765
- import path5 from "path";
2863
+ import { createRequire as createRequire2 } from "module";
2864
+ import { pathToFileURL as pathToFileURL2 } from "url";
2865
+ import os5 from "os";
2866
+ import path6 from "path";
2766
2867
  import { jwtVerify, importSPKI } from "jose";
2767
- var LICENSE_PATH = path5.join(EXE_AI_DIR, "license.key");
2768
- var CACHE_PATH = path5.join(EXE_AI_DIR, "license-cache.json");
2769
- var DEVICE_ID_PATH = path5.join(EXE_AI_DIR, "device-id");
2868
+ var LICENSE_PATH = path6.join(EXE_AI_DIR, "license.key");
2869
+ var CACHE_PATH = path6.join(EXE_AI_DIR, "license-cache.json");
2870
+ var DEVICE_ID_PATH = path6.join(EXE_AI_DIR, "device-id");
2770
2871
  function loadDeviceId() {
2771
- const deviceJsonPath = path5.join(EXE_AI_DIR, "device.json");
2872
+ const deviceJsonPath = path6.join(EXE_AI_DIR, "device.json");
2772
2873
  try {
2773
- if (existsSync4(deviceJsonPath)) {
2774
- const data = JSON.parse(readFileSync4(deviceJsonPath, "utf8"));
2874
+ if (existsSync6(deviceJsonPath)) {
2875
+ const data = JSON.parse(readFileSync5(deviceJsonPath, "utf8"));
2775
2876
  if (data.deviceId) return data.deviceId;
2776
2877
  }
2777
2878
  } catch {
2778
2879
  }
2779
2880
  try {
2780
- if (existsSync4(DEVICE_ID_PATH)) {
2781
- const id2 = readFileSync4(DEVICE_ID_PATH, "utf8").trim();
2881
+ if (existsSync6(DEVICE_ID_PATH)) {
2882
+ const id2 = readFileSync5(DEVICE_ID_PATH, "utf8").trim();
2782
2883
  if (id2) return id2;
2783
2884
  }
2784
2885
  } catch {
2785
2886
  }
2786
2887
  const id = randomUUID2();
2787
- mkdirSync(EXE_AI_DIR, { recursive: true });
2788
- writeFileSync2(DEVICE_ID_PATH, id, "utf8");
2888
+ mkdirSync2(EXE_AI_DIR, { recursive: true });
2889
+ writeFileSync3(DEVICE_ID_PATH, id, "utf8");
2789
2890
  return id;
2790
2891
  }
2791
2892
 
@@ -2793,12 +2894,13 @@ function loadDeviceId() {
2793
2894
  init_config();
2794
2895
  init_crdt_sync();
2795
2896
  init_employees();
2897
+ init_secure_files();
2796
2898
  function sqlSafe(v) {
2797
2899
  return v === void 0 ? null : v;
2798
2900
  }
2799
2901
  function logError(msg) {
2800
2902
  try {
2801
- const logPath = path8.join(homedir2(), ".exe-os", "workers.log");
2903
+ const logPath = path9.join(homedir2(), ".exe-os", "workers.log");
2802
2904
  appendFileSync(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
2803
2905
  `);
2804
2906
  } catch {
@@ -2807,24 +2909,93 @@ function logError(msg) {
2807
2909
  var LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
2808
2910
  var FETCH_TIMEOUT_MS = 3e4;
2809
2911
  var PUSH_BATCH_SIZE = 5e3;
2810
- var ROSTER_LOCK_PATH = path8.join(EXE_AI_DIR, "roster-merge.lock");
2912
+ var ROSTER_LOCK_PATH = path9.join(EXE_AI_DIR, "roster-merge.lock");
2811
2913
  var LOCK_STALE_MS = 3e4;
2914
+ var _pgPromise = null;
2915
+ var _pgFailed = false;
2916
+ function loadPgClient() {
2917
+ if (_pgFailed) return null;
2918
+ const postgresUrl = process.env.DATABASE_URL;
2919
+ const configPath = path9.join(EXE_AI_DIR, "config.json");
2920
+ let cloudPostgresUrl;
2921
+ try {
2922
+ if (existsSync9(configPath)) {
2923
+ const cfg = JSON.parse(readFileSync7(configPath, "utf8"));
2924
+ cloudPostgresUrl = cfg.cloud?.postgresUrl;
2925
+ if (cfg.cloud?.syncToPostgres === false) {
2926
+ _pgFailed = true;
2927
+ return null;
2928
+ }
2929
+ }
2930
+ } catch {
2931
+ }
2932
+ const url = postgresUrl || cloudPostgresUrl;
2933
+ if (!url) {
2934
+ _pgFailed = true;
2935
+ return null;
2936
+ }
2937
+ if (!_pgPromise) {
2938
+ _pgPromise = (async () => {
2939
+ const { createRequire: createRequire3 } = await import("module");
2940
+ const { pathToFileURL: pathToFileURL3 } = await import("url");
2941
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path9.join(homedir2(), "exe-db");
2942
+ const req = createRequire3(path9.join(exeDbRoot, "package.json"));
2943
+ const entry = req.resolve("@prisma/client");
2944
+ const mod = await import(pathToFileURL3(entry).href);
2945
+ const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
2946
+ if (!Ctor) throw new Error("No PrismaClient");
2947
+ return new Ctor();
2948
+ })().catch(() => {
2949
+ _pgFailed = true;
2950
+ _pgPromise = null;
2951
+ throw new Error("pg_unavailable");
2952
+ });
2953
+ }
2954
+ return _pgPromise;
2955
+ }
2956
+ async function pushToPostgres(records) {
2957
+ const loader = loadPgClient();
2958
+ if (!loader) return 0;
2959
+ let prisma;
2960
+ try {
2961
+ prisma = await loader;
2962
+ } catch {
2963
+ return 0;
2964
+ }
2965
+ let inserted = 0;
2966
+ for (const rec of records) {
2967
+ try {
2968
+ await prisma.$executeRawUnsafe(
2969
+ `INSERT INTO raw.raw_events (id, source, source_id, event_type, payload, metadata, timestamp)
2970
+ VALUES (gen_random_uuid(), 'cloud_sync', $1, 'memory', $2::jsonb, $3::jsonb, $4)
2971
+ ON CONFLICT (source, source_id, event_type) DO NOTHING`,
2972
+ String(rec.id ?? ""),
2973
+ JSON.stringify(rec),
2974
+ JSON.stringify({ agent_id: rec.agent_id, project_name: rec.project_name, tool_name: rec.tool_name }),
2975
+ rec.timestamp ? new Date(String(rec.timestamp)) : /* @__PURE__ */ new Date()
2976
+ );
2977
+ inserted++;
2978
+ } catch {
2979
+ }
2980
+ }
2981
+ return inserted;
2982
+ }
2812
2983
  async function withRosterLock(fn) {
2813
2984
  try {
2814
2985
  const fd = openSync2(ROSTER_LOCK_PATH, "wx");
2815
2986
  closeSync2(fd);
2816
- writeFileSync4(ROSTER_LOCK_PATH, String(Date.now()));
2987
+ writeFileSync5(ROSTER_LOCK_PATH, String(Date.now()));
2817
2988
  } catch (err) {
2818
2989
  if (err.code === "EEXIST") {
2819
2990
  try {
2820
- const ts = parseInt(readFileSync6(ROSTER_LOCK_PATH, "utf-8"), 10);
2991
+ const ts = parseInt(readFileSync7(ROSTER_LOCK_PATH, "utf-8"), 10);
2821
2992
  if (Date.now() - ts < LOCK_STALE_MS) {
2822
2993
  throw new Error("Roster merge already in progress \u2014 another sync is running");
2823
2994
  }
2824
2995
  unlinkSync4(ROSTER_LOCK_PATH);
2825
2996
  const fd = openSync2(ROSTER_LOCK_PATH, "wx");
2826
2997
  closeSync2(fd);
2827
- writeFileSync4(ROSTER_LOCK_PATH, String(Date.now()));
2998
+ writeFileSync5(ROSTER_LOCK_PATH, String(Date.now()));
2828
2999
  } catch (retryErr) {
2829
3000
  if (retryErr instanceof Error && retryErr.message.includes("already in progress")) throw retryErr;
2830
3001
  throw new Error("Roster merge already in progress \u2014 another sync is running");
@@ -3094,6 +3265,10 @@ async function cloudSync(config) {
3094
3265
  const maxVersion = Number(records[records.length - 1].version);
3095
3266
  const pushOk = await cloudPush(records, maxVersion, config);
3096
3267
  if (!pushOk) break;
3268
+ try {
3269
+ await pushToPostgres(records);
3270
+ } catch {
3271
+ }
3097
3272
  await client.execute({
3098
3273
  sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_cloud_push_version', ?)",
3099
3274
  args: [String(maxVersion)]
@@ -3198,8 +3373,8 @@ async function cloudSync(config) {
3198
3373
  try {
3199
3374
  const employees = await loadEmployees();
3200
3375
  rosterResult.employees = employees.length;
3201
- const idDir = path8.join(EXE_AI_DIR, "identity");
3202
- if (existsSync7(idDir)) {
3376
+ const idDir = path9.join(EXE_AI_DIR, "identity");
3377
+ if (existsSync9(idDir)) {
3203
3378
  rosterResult.identities = readdirSync(idDir).filter((f) => f.endsWith(".md")).length;
3204
3379
  }
3205
3380
  } catch {
@@ -3217,66 +3392,66 @@ async function cloudSync(config) {
3217
3392
  roster: rosterResult
3218
3393
  };
3219
3394
  }
3220
- var ROSTER_DELETIONS_PATH = path8.join(EXE_AI_DIR, "roster-deletions.json");
3395
+ var ROSTER_DELETIONS_PATH = path9.join(EXE_AI_DIR, "roster-deletions.json");
3221
3396
  function recordRosterDeletion(name) {
3222
3397
  let deletions = [];
3223
3398
  try {
3224
- if (existsSync7(ROSTER_DELETIONS_PATH)) {
3225
- deletions = JSON.parse(readFileSync6(ROSTER_DELETIONS_PATH, "utf-8"));
3399
+ if (existsSync9(ROSTER_DELETIONS_PATH)) {
3400
+ deletions = JSON.parse(readFileSync7(ROSTER_DELETIONS_PATH, "utf-8"));
3226
3401
  }
3227
3402
  } catch {
3228
3403
  }
3229
3404
  if (!deletions.includes(name)) deletions.push(name);
3230
- writeFileSync4(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
3405
+ writeFileSync5(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
3231
3406
  }
3232
3407
  function consumeRosterDeletions() {
3233
3408
  try {
3234
- if (!existsSync7(ROSTER_DELETIONS_PATH)) return [];
3235
- const deletions = JSON.parse(readFileSync6(ROSTER_DELETIONS_PATH, "utf-8"));
3236
- writeFileSync4(ROSTER_DELETIONS_PATH, "[]");
3409
+ if (!existsSync9(ROSTER_DELETIONS_PATH)) return [];
3410
+ const deletions = JSON.parse(readFileSync7(ROSTER_DELETIONS_PATH, "utf-8"));
3411
+ writeFileSync5(ROSTER_DELETIONS_PATH, "[]");
3237
3412
  return deletions;
3238
3413
  } catch {
3239
3414
  return [];
3240
3415
  }
3241
3416
  }
3242
3417
  function buildRosterBlob(paths) {
3243
- const rosterPath = paths?.rosterPath ?? path8.join(EXE_AI_DIR, "exe-employees.json");
3244
- const identityDir = paths?.identityDir ?? path8.join(EXE_AI_DIR, "identity");
3245
- const configPath = paths?.configPath ?? path8.join(EXE_AI_DIR, "config.json");
3418
+ const rosterPath = paths?.rosterPath ?? path9.join(EXE_AI_DIR, "exe-employees.json");
3419
+ const identityDir = paths?.identityDir ?? path9.join(EXE_AI_DIR, "identity");
3420
+ const configPath = paths?.configPath ?? path9.join(EXE_AI_DIR, "config.json");
3246
3421
  let roster = [];
3247
- if (existsSync7(rosterPath)) {
3422
+ if (existsSync9(rosterPath)) {
3248
3423
  try {
3249
- roster = JSON.parse(readFileSync6(rosterPath, "utf-8"));
3424
+ roster = JSON.parse(readFileSync7(rosterPath, "utf-8"));
3250
3425
  } catch {
3251
3426
  }
3252
3427
  }
3253
3428
  const identities = {};
3254
- if (existsSync7(identityDir)) {
3429
+ if (existsSync9(identityDir)) {
3255
3430
  for (const file of readdirSync(identityDir).filter((f) => f.endsWith(".md"))) {
3256
3431
  try {
3257
- identities[file] = readFileSync6(path8.join(identityDir, file), "utf-8");
3432
+ identities[file] = readFileSync7(path9.join(identityDir, file), "utf-8");
3258
3433
  } catch {
3259
3434
  }
3260
3435
  }
3261
3436
  }
3262
3437
  let config;
3263
- if (existsSync7(configPath)) {
3438
+ if (existsSync9(configPath)) {
3264
3439
  try {
3265
- config = JSON.parse(readFileSync6(configPath, "utf-8"));
3440
+ config = JSON.parse(readFileSync7(configPath, "utf-8"));
3266
3441
  } catch {
3267
3442
  }
3268
3443
  }
3269
3444
  let agentConfig;
3270
- const agentConfigPath = path8.join(EXE_AI_DIR, "agent-config.json");
3271
- if (existsSync7(agentConfigPath)) {
3445
+ const agentConfigPath = path9.join(EXE_AI_DIR, "agent-config.json");
3446
+ if (existsSync9(agentConfigPath)) {
3272
3447
  try {
3273
- agentConfig = JSON.parse(readFileSync6(agentConfigPath, "utf-8"));
3448
+ agentConfig = JSON.parse(readFileSync7(agentConfigPath, "utf-8"));
3274
3449
  } catch {
3275
3450
  }
3276
3451
  }
3277
3452
  const deletedNames = consumeRosterDeletions();
3278
3453
  const content = JSON.stringify({ roster, identities, config, agentConfig, deletedNames });
3279
- const hash = crypto2.createHash("sha256").update(content).digest("hex").slice(0, 16);
3454
+ const hash = crypto3.createHash("sha256").update(content).digest("hex").slice(0, 16);
3280
3455
  return { roster, identities, config, agentConfig, deletedNames, version: hash };
3281
3456
  }
3282
3457
  async function cloudPushRoster(config) {
@@ -3346,23 +3521,24 @@ async function cloudPullRoster(config) {
3346
3521
  }
3347
3522
  }
3348
3523
  function mergeConfig(remoteConfig, configPath) {
3349
- const cfgPath = configPath ?? path8.join(EXE_AI_DIR, "config.json");
3524
+ const cfgPath = configPath ?? path9.join(EXE_AI_DIR, "config.json");
3350
3525
  let local = {};
3351
- if (existsSync7(cfgPath)) {
3526
+ if (existsSync9(cfgPath)) {
3352
3527
  try {
3353
- local = JSON.parse(readFileSync6(cfgPath, "utf-8"));
3528
+ local = JSON.parse(readFileSync7(cfgPath, "utf-8"));
3354
3529
  } catch {
3355
3530
  }
3356
3531
  }
3357
3532
  const merged = { ...remoteConfig, ...local };
3358
- const dir = path8.dirname(cfgPath);
3359
- if (!existsSync7(dir)) mkdirSync3(dir, { recursive: true });
3360
- writeFileSync4(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
3533
+ const dir = path9.dirname(cfgPath);
3534
+ ensurePrivateDirSync(dir);
3535
+ writeFileSync5(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
3536
+ enforcePrivateFileSync(cfgPath);
3361
3537
  }
3362
3538
  async function mergeRosterFromRemote(remote, paths) {
3363
3539
  return withRosterLock(async () => {
3364
3540
  const rosterPath = paths?.rosterPath ?? void 0;
3365
- const identityDir = paths?.identityDir ?? path8.join(EXE_AI_DIR, "identity");
3541
+ const identityDir = paths?.identityDir ?? path9.join(EXE_AI_DIR, "identity");
3366
3542
  const localEmployees = await loadEmployees(rosterPath);
3367
3543
  const localNames = new Set(localEmployees.map((e) => e.name));
3368
3544
  let added = 0;
@@ -3383,15 +3559,15 @@ async function mergeRosterFromRemote(remote, paths) {
3383
3559
  ) ?? lookupKey;
3384
3560
  const remoteIdentity = remote.identities[matchedKey];
3385
3561
  if (remoteIdentity) {
3386
- if (!existsSync7(identityDir)) mkdirSync3(identityDir, { recursive: true });
3387
- const idPath = path8.join(identityDir, `${remoteEmp.name}.md`);
3562
+ if (!existsSync9(identityDir)) mkdirSync4(identityDir, { recursive: true });
3563
+ const idPath = path9.join(identityDir, `${remoteEmp.name}.md`);
3388
3564
  let localIdentity = null;
3389
3565
  try {
3390
- localIdentity = existsSync7(idPath) ? readFileSync6(idPath, "utf-8") : null;
3566
+ localIdentity = existsSync9(idPath) ? readFileSync7(idPath, "utf-8") : null;
3391
3567
  } catch {
3392
3568
  }
3393
3569
  if (localIdentity !== remoteIdentity) {
3394
- writeFileSync4(idPath, remoteIdentity, "utf-8");
3570
+ writeFileSync5(idPath, remoteIdentity, "utf-8");
3395
3571
  identitiesUpdated++;
3396
3572
  }
3397
3573
  }
@@ -3417,16 +3593,18 @@ async function mergeRosterFromRemote(remote, paths) {
3417
3593
  }
3418
3594
  if (remote.agentConfig && Object.keys(remote.agentConfig).length > 0) {
3419
3595
  try {
3420
- const agentConfigPath = path8.join(EXE_AI_DIR, "agent-config.json");
3596
+ const agentConfigPath = path9.join(EXE_AI_DIR, "agent-config.json");
3421
3597
  let local = {};
3422
- if (existsSync7(agentConfigPath)) {
3598
+ if (existsSync9(agentConfigPath)) {
3423
3599
  try {
3424
- local = JSON.parse(readFileSync6(agentConfigPath, "utf-8"));
3600
+ local = JSON.parse(readFileSync7(agentConfigPath, "utf-8"));
3425
3601
  } catch {
3426
3602
  }
3427
3603
  }
3428
3604
  const merged = { ...remote.agentConfig, ...local };
3429
- writeFileSync4(agentConfigPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
3605
+ ensurePrivateDirSync(path9.dirname(agentConfigPath));
3606
+ writeFileSync5(agentConfigPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
3607
+ enforcePrivateFileSync(agentConfigPath);
3430
3608
  } catch {
3431
3609
  }
3432
3610
  }
@@ -3874,5 +4052,6 @@ export {
3874
4052
  cloudSync,
3875
4053
  mergeConfig,
3876
4054
  mergeRosterFromRemote,
4055
+ pushToPostgres,
3877
4056
  recordRosterDeletion
3878
4057
  };