@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
@@ -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
  }
@@ -1163,13 +1201,50 @@ var init_database_adapter = __esm({
1163
1201
  }
1164
1202
  });
1165
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");
1238
+ }
1239
+ });
1240
+
1166
1241
  // src/lib/exe-daemon-client.ts
1167
1242
  import net from "net";
1168
1243
  import os5 from "os";
1169
1244
  import { spawn } from "child_process";
1170
1245
  import { randomUUID } from "crypto";
1171
- import { existsSync as existsSync4, unlinkSync as unlinkSync2, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
1172
- import path5 from "path";
1246
+ import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
1247
+ import path6 from "path";
1173
1248
  import { fileURLToPath as fileURLToPath2 } from "url";
1174
1249
  function handleData(chunk) {
1175
1250
  _buffer += chunk.toString();
@@ -1197,9 +1272,9 @@ function handleData(chunk) {
1197
1272
  }
1198
1273
  }
1199
1274
  function cleanupStaleFiles() {
1200
- if (existsSync4(PID_PATH)) {
1275
+ if (existsSync6(PID_PATH)) {
1201
1276
  try {
1202
- const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
1277
+ const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
1203
1278
  if (pid > 0) {
1204
1279
  try {
1205
1280
  process.kill(pid, 0);
@@ -1220,11 +1295,11 @@ function cleanupStaleFiles() {
1220
1295
  }
1221
1296
  }
1222
1297
  function findPackageRoot() {
1223
- let dir = path5.dirname(fileURLToPath2(import.meta.url));
1224
- const { root } = path5.parse(dir);
1298
+ let dir = path6.dirname(fileURLToPath2(import.meta.url));
1299
+ const { root } = path6.parse(dir);
1225
1300
  while (dir !== root) {
1226
- if (existsSync4(path5.join(dir, "package.json"))) return dir;
1227
- dir = path5.dirname(dir);
1301
+ if (existsSync6(path6.join(dir, "package.json"))) return dir;
1302
+ dir = path6.dirname(dir);
1228
1303
  }
1229
1304
  return null;
1230
1305
  }
@@ -1250,16 +1325,17 @@ function spawnDaemon() {
1250
1325
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
1251
1326
  return;
1252
1327
  }
1253
- const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1254
- if (!existsSync4(daemonPath)) {
1328
+ const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1329
+ if (!existsSync6(daemonPath)) {
1255
1330
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
1256
1331
  `);
1257
1332
  return;
1258
1333
  }
1259
1334
  const resolvedPath = daemonPath;
1335
+ const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
1260
1336
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
1261
1337
  `);
1262
- const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
1338
+ const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
1263
1339
  let stderrFd = "ignore";
1264
1340
  try {
1265
1341
  stderrFd = openSync(logPath, "a");
@@ -1277,7 +1353,8 @@ function spawnDaemon() {
1277
1353
  TMUX_PANE: void 0,
1278
1354
  // Prevents resolveExeSession() from scoping to one session
1279
1355
  EXE_DAEMON_SOCK: SOCKET_PATH,
1280
- EXE_DAEMON_PID: PID_PATH
1356
+ EXE_DAEMON_PID: PID_PATH,
1357
+ [DAEMON_TOKEN_ENV]: daemonToken
1281
1358
  }
1282
1359
  });
1283
1360
  child.unref();
@@ -1384,13 +1461,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
1384
1461
  return;
1385
1462
  }
1386
1463
  const id = randomUUID();
1464
+ const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
1387
1465
  const timer = setTimeout(() => {
1388
1466
  _pending.delete(id);
1389
1467
  resolve({ error: "Request timeout" });
1390
1468
  }, timeoutMs);
1391
1469
  _pending.set(id, { resolve, timer });
1392
1470
  try {
1393
- _socket.write(JSON.stringify({ id, ...payload }) + "\n");
1471
+ _socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
1394
1472
  } catch {
1395
1473
  clearTimeout(timer);
1396
1474
  _pending.delete(id);
@@ -1401,17 +1479,19 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
1401
1479
  function isClientConnected() {
1402
1480
  return _connected;
1403
1481
  }
1404
- 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;
1405
1483
  var init_exe_daemon_client = __esm({
1406
1484
  "src/lib/exe-daemon-client.ts"() {
1407
1485
  "use strict";
1408
1486
  init_config();
1409
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path5.join(EXE_AI_DIR, "exed.sock");
1410
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path5.join(EXE_AI_DIR, "exed.pid");
1411
- SPAWN_LOCK_PATH = path5.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");
1412
1491
  SPAWN_LOCK_STALE_MS = 3e4;
1413
1492
  CONNECT_TIMEOUT_MS = 15e3;
1414
1493
  REQUEST_TIMEOUT_MS = 3e4;
1494
+ DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
1415
1495
  _socket = null;
1416
1496
  _connected = false;
1417
1497
  _buffer = "";
@@ -1990,6 +2070,7 @@ async function ensureSchema() {
1990
2070
  project TEXT NOT NULL,
1991
2071
  summary TEXT NOT NULL,
1992
2072
  task_file TEXT,
2073
+ session_scope TEXT,
1993
2074
  read INTEGER NOT NULL DEFAULT 0,
1994
2075
  created_at TEXT NOT NULL
1995
2076
  );
@@ -1998,7 +2079,7 @@ async function ensureSchema() {
1998
2079
  ON notifications(read);
1999
2080
 
2000
2081
  CREATE INDEX IF NOT EXISTS idx_notifications_agent
2001
- ON notifications(agent_id);
2082
+ ON notifications(agent_id, session_scope);
2002
2083
 
2003
2084
  CREATE INDEX IF NOT EXISTS idx_notifications_task_file
2004
2085
  ON notifications(task_file);
@@ -2036,6 +2117,7 @@ async function ensureSchema() {
2036
2117
  target_agent TEXT NOT NULL,
2037
2118
  target_project TEXT,
2038
2119
  target_device TEXT NOT NULL DEFAULT 'local',
2120
+ session_scope TEXT,
2039
2121
  content TEXT NOT NULL,
2040
2122
  priority TEXT DEFAULT 'normal',
2041
2123
  status TEXT DEFAULT 'pending',
@@ -2049,10 +2131,31 @@ async function ensureSchema() {
2049
2131
  );
2050
2132
 
2051
2133
  CREATE INDEX IF NOT EXISTS idx_messages_target
2052
- ON messages(target_agent, status);
2134
+ ON messages(target_agent, session_scope, status);
2053
2135
 
2054
2136
  CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
2055
- 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);
2056
2159
  `);
2057
2160
  try {
2058
2161
  await client.execute({
@@ -2636,6 +2739,13 @@ async function ensureSchema() {
2636
2739
  } catch {
2637
2740
  }
2638
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
+ }
2639
2749
  }
2640
2750
  async function disposeDatabase() {
2641
2751
  if (_walCheckpointTimer) {
@@ -2694,29 +2804,32 @@ var init_compress = __esm({
2694
2804
  });
2695
2805
 
2696
2806
  // src/lib/license.ts
2697
- 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";
2698
2808
  import { randomUUID as randomUUID2 } from "crypto";
2699
- import path6 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";
2700
2813
  import { jwtVerify, importSPKI } from "jose";
2701
2814
  function loadDeviceId() {
2702
- const deviceJsonPath = path6.join(EXE_AI_DIR, "device.json");
2815
+ const deviceJsonPath = path7.join(EXE_AI_DIR, "device.json");
2703
2816
  try {
2704
- if (existsSync5(deviceJsonPath)) {
2705
- const data = JSON.parse(readFileSync4(deviceJsonPath, "utf8"));
2817
+ if (existsSync7(deviceJsonPath)) {
2818
+ const data = JSON.parse(readFileSync5(deviceJsonPath, "utf8"));
2706
2819
  if (data.deviceId) return data.deviceId;
2707
2820
  }
2708
2821
  } catch {
2709
2822
  }
2710
2823
  try {
2711
- if (existsSync5(DEVICE_ID_PATH)) {
2712
- const id2 = readFileSync4(DEVICE_ID_PATH, "utf8").trim();
2824
+ if (existsSync7(DEVICE_ID_PATH)) {
2825
+ const id2 = readFileSync5(DEVICE_ID_PATH, "utf8").trim();
2713
2826
  if (id2) return id2;
2714
2827
  }
2715
2828
  } catch {
2716
2829
  }
2717
2830
  const id = randomUUID2();
2718
- mkdirSync(EXE_AI_DIR, { recursive: true });
2719
- writeFileSync2(DEVICE_ID_PATH, id, "utf8");
2831
+ mkdirSync2(EXE_AI_DIR, { recursive: true });
2832
+ writeFileSync3(DEVICE_ID_PATH, id, "utf8");
2720
2833
  return id;
2721
2834
  }
2722
2835
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH;
@@ -2724,9 +2837,9 @@ var init_license = __esm({
2724
2837
  "src/lib/license.ts"() {
2725
2838
  "use strict";
2726
2839
  init_config();
2727
- LICENSE_PATH = path6.join(EXE_AI_DIR, "license.key");
2728
- CACHE_PATH = path6.join(EXE_AI_DIR, "license-cache.json");
2729
- DEVICE_ID_PATH = path6.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");
2730
2843
  }
2731
2844
  });
2732
2845
 
@@ -2749,8 +2862,8 @@ __export(crdt_sync_exports, {
2749
2862
  rebuildFromDb: () => rebuildFromDb
2750
2863
  });
2751
2864
  import * as Y from "yjs";
2752
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync6, mkdirSync as mkdirSync2, unlinkSync as unlinkSync3 } from "fs";
2753
- import path7 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";
2754
2867
  import { homedir } from "os";
2755
2868
  function getStatePath() {
2756
2869
  return _statePathOverride ?? DEFAULT_STATE_PATH;
@@ -2762,9 +2875,9 @@ function initCrdtDoc() {
2762
2875
  if (doc) return doc;
2763
2876
  doc = new Y.Doc();
2764
2877
  const sp = getStatePath();
2765
- if (existsSync6(sp)) {
2878
+ if (existsSync8(sp)) {
2766
2879
  try {
2767
- const state = readFileSync5(sp);
2880
+ const state = readFileSync6(sp);
2768
2881
  Y.applyUpdate(doc, new Uint8Array(state));
2769
2882
  } catch {
2770
2883
  console.warn("[crdt-sync] WARN: corrupted state file, rebuilding from DB");
@@ -2906,10 +3019,10 @@ function persistState() {
2906
3019
  if (!doc) return;
2907
3020
  try {
2908
3021
  const sp = getStatePath();
2909
- const dir = path7.dirname(sp);
2910
- if (!existsSync6(dir)) mkdirSync2(dir, { recursive: true });
3022
+ const dir = path8.dirname(sp);
3023
+ if (!existsSync8(dir)) mkdirSync3(dir, { recursive: true });
2911
3024
  const state = Y.encodeStateAsUpdate(doc);
2912
- writeFileSync3(sp, Buffer.from(state));
3025
+ writeFileSync4(sp, Buffer.from(state));
2913
3026
  } catch {
2914
3027
  }
2915
3028
  }
@@ -2950,7 +3063,7 @@ var DEFAULT_STATE_PATH, _statePathOverride, doc;
2950
3063
  var init_crdt_sync = __esm({
2951
3064
  "src/lib/crdt-sync.ts"() {
2952
3065
  "use strict";
2953
- DEFAULT_STATE_PATH = path7.join(homedir(), ".exe-os", "crdt-state.bin");
3066
+ DEFAULT_STATE_PATH = path8.join(homedir(), ".exe-os", "crdt-state.bin");
2954
3067
  _statePathOverride = null;
2955
3068
  doc = null;
2956
3069
  }
@@ -2982,39 +3095,107 @@ __export(cloud_sync_exports, {
2982
3095
  cloudSync: () => cloudSync,
2983
3096
  mergeConfig: () => mergeConfig,
2984
3097
  mergeRosterFromRemote: () => mergeRosterFromRemote,
3098
+ pushToPostgres: () => pushToPostgres,
2985
3099
  recordRosterDeletion: () => recordRosterDeletion
2986
3100
  });
2987
- 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";
2988
- import crypto2 from "crypto";
2989
- import path8 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";
2990
3104
  import { homedir as homedir2 } from "os";
2991
3105
  function sqlSafe(v) {
2992
3106
  return v === void 0 ? null : v;
2993
3107
  }
2994
3108
  function logError(msg) {
2995
3109
  try {
2996
- const logPath = path8.join(homedir2(), ".exe-os", "workers.log");
3110
+ const logPath = path9.join(homedir2(), ".exe-os", "workers.log");
2997
3111
  appendFileSync(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
2998
3112
  `);
2999
3113
  } catch {
3000
3114
  }
3001
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
+ }
3002
3183
  async function withRosterLock(fn) {
3003
3184
  try {
3004
3185
  const fd = openSync2(ROSTER_LOCK_PATH, "wx");
3005
3186
  closeSync2(fd);
3006
- writeFileSync4(ROSTER_LOCK_PATH, String(Date.now()));
3187
+ writeFileSync5(ROSTER_LOCK_PATH, String(Date.now()));
3007
3188
  } catch (err) {
3008
3189
  if (err.code === "EEXIST") {
3009
3190
  try {
3010
- const ts = parseInt(readFileSync6(ROSTER_LOCK_PATH, "utf-8"), 10);
3191
+ const ts = parseInt(readFileSync7(ROSTER_LOCK_PATH, "utf-8"), 10);
3011
3192
  if (Date.now() - ts < LOCK_STALE_MS) {
3012
3193
  throw new Error("Roster merge already in progress \u2014 another sync is running");
3013
3194
  }
3014
3195
  unlinkSync4(ROSTER_LOCK_PATH);
3015
3196
  const fd = openSync2(ROSTER_LOCK_PATH, "wx");
3016
3197
  closeSync2(fd);
3017
- writeFileSync4(ROSTER_LOCK_PATH, String(Date.now()));
3198
+ writeFileSync5(ROSTER_LOCK_PATH, String(Date.now()));
3018
3199
  } catch (retryErr) {
3019
3200
  if (retryErr instanceof Error && retryErr.message.includes("already in progress")) throw retryErr;
3020
3201
  throw new Error("Roster merge already in progress \u2014 another sync is running");
@@ -3284,6 +3465,10 @@ async function cloudSync(config) {
3284
3465
  const maxVersion = Number(records[records.length - 1].version);
3285
3466
  const pushOk = await cloudPush(records, maxVersion, config);
3286
3467
  if (!pushOk) break;
3468
+ try {
3469
+ await pushToPostgres(records);
3470
+ } catch {
3471
+ }
3287
3472
  await client.execute({
3288
3473
  sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_cloud_push_version', ?)",
3289
3474
  args: [String(maxVersion)]
@@ -3388,8 +3573,8 @@ async function cloudSync(config) {
3388
3573
  try {
3389
3574
  const employees = await loadEmployees();
3390
3575
  rosterResult.employees = employees.length;
3391
- const idDir = path8.join(EXE_AI_DIR, "identity");
3392
- if (existsSync7(idDir)) {
3576
+ const idDir = path9.join(EXE_AI_DIR, "identity");
3577
+ if (existsSync9(idDir)) {
3393
3578
  rosterResult.identities = readdirSync(idDir).filter((f) => f.endsWith(".md")).length;
3394
3579
  }
3395
3580
  } catch {
@@ -3410,62 +3595,62 @@ async function cloudSync(config) {
3410
3595
  function recordRosterDeletion(name) {
3411
3596
  let deletions = [];
3412
3597
  try {
3413
- if (existsSync7(ROSTER_DELETIONS_PATH)) {
3414
- 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"));
3415
3600
  }
3416
3601
  } catch {
3417
3602
  }
3418
3603
  if (!deletions.includes(name)) deletions.push(name);
3419
- writeFileSync4(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
3604
+ writeFileSync5(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
3420
3605
  }
3421
3606
  function consumeRosterDeletions() {
3422
3607
  try {
3423
- if (!existsSync7(ROSTER_DELETIONS_PATH)) return [];
3424
- const deletions = JSON.parse(readFileSync6(ROSTER_DELETIONS_PATH, "utf-8"));
3425
- 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, "[]");
3426
3611
  return deletions;
3427
3612
  } catch {
3428
3613
  return [];
3429
3614
  }
3430
3615
  }
3431
3616
  function buildRosterBlob(paths) {
3432
- const rosterPath = paths?.rosterPath ?? path8.join(EXE_AI_DIR, "exe-employees.json");
3433
- const identityDir = paths?.identityDir ?? path8.join(EXE_AI_DIR, "identity");
3434
- const configPath = paths?.configPath ?? path8.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");
3435
3620
  let roster = [];
3436
- if (existsSync7(rosterPath)) {
3621
+ if (existsSync9(rosterPath)) {
3437
3622
  try {
3438
- roster = JSON.parse(readFileSync6(rosterPath, "utf-8"));
3623
+ roster = JSON.parse(readFileSync7(rosterPath, "utf-8"));
3439
3624
  } catch {
3440
3625
  }
3441
3626
  }
3442
3627
  const identities = {};
3443
- if (existsSync7(identityDir)) {
3628
+ if (existsSync9(identityDir)) {
3444
3629
  for (const file of readdirSync(identityDir).filter((f) => f.endsWith(".md"))) {
3445
3630
  try {
3446
- identities[file] = readFileSync6(path8.join(identityDir, file), "utf-8");
3631
+ identities[file] = readFileSync7(path9.join(identityDir, file), "utf-8");
3447
3632
  } catch {
3448
3633
  }
3449
3634
  }
3450
3635
  }
3451
3636
  let config;
3452
- if (existsSync7(configPath)) {
3637
+ if (existsSync9(configPath)) {
3453
3638
  try {
3454
- config = JSON.parse(readFileSync6(configPath, "utf-8"));
3639
+ config = JSON.parse(readFileSync7(configPath, "utf-8"));
3455
3640
  } catch {
3456
3641
  }
3457
3642
  }
3458
3643
  let agentConfig;
3459
- const agentConfigPath = path8.join(EXE_AI_DIR, "agent-config.json");
3460
- if (existsSync7(agentConfigPath)) {
3644
+ const agentConfigPath = path9.join(EXE_AI_DIR, "agent-config.json");
3645
+ if (existsSync9(agentConfigPath)) {
3461
3646
  try {
3462
- agentConfig = JSON.parse(readFileSync6(agentConfigPath, "utf-8"));
3647
+ agentConfig = JSON.parse(readFileSync7(agentConfigPath, "utf-8"));
3463
3648
  } catch {
3464
3649
  }
3465
3650
  }
3466
3651
  const deletedNames = consumeRosterDeletions();
3467
3652
  const content = JSON.stringify({ roster, identities, config, agentConfig, deletedNames });
3468
- 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);
3469
3654
  return { roster, identities, config, agentConfig, deletedNames, version: hash };
3470
3655
  }
3471
3656
  async function cloudPushRoster(config) {
@@ -3535,23 +3720,24 @@ async function cloudPullRoster(config) {
3535
3720
  }
3536
3721
  }
3537
3722
  function mergeConfig(remoteConfig, configPath) {
3538
- const cfgPath = configPath ?? path8.join(EXE_AI_DIR, "config.json");
3723
+ const cfgPath = configPath ?? path9.join(EXE_AI_DIR, "config.json");
3539
3724
  let local = {};
3540
- if (existsSync7(cfgPath)) {
3725
+ if (existsSync9(cfgPath)) {
3541
3726
  try {
3542
- local = JSON.parse(readFileSync6(cfgPath, "utf-8"));
3727
+ local = JSON.parse(readFileSync7(cfgPath, "utf-8"));
3543
3728
  } catch {
3544
3729
  }
3545
3730
  }
3546
3731
  const merged = { ...remoteConfig, ...local };
3547
- const dir = path8.dirname(cfgPath);
3548
- if (!existsSync7(dir)) mkdirSync3(dir, { recursive: true });
3549
- 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);
3550
3736
  }
3551
3737
  async function mergeRosterFromRemote(remote, paths) {
3552
3738
  return withRosterLock(async () => {
3553
3739
  const rosterPath = paths?.rosterPath ?? void 0;
3554
- const identityDir = paths?.identityDir ?? path8.join(EXE_AI_DIR, "identity");
3740
+ const identityDir = paths?.identityDir ?? path9.join(EXE_AI_DIR, "identity");
3555
3741
  const localEmployees = await loadEmployees(rosterPath);
3556
3742
  const localNames = new Set(localEmployees.map((e) => e.name));
3557
3743
  let added = 0;
@@ -3572,15 +3758,15 @@ async function mergeRosterFromRemote(remote, paths) {
3572
3758
  ) ?? lookupKey;
3573
3759
  const remoteIdentity = remote.identities[matchedKey];
3574
3760
  if (remoteIdentity) {
3575
- if (!existsSync7(identityDir)) mkdirSync3(identityDir, { recursive: true });
3576
- const idPath = path8.join(identityDir, `${remoteEmp.name}.md`);
3761
+ if (!existsSync9(identityDir)) mkdirSync4(identityDir, { recursive: true });
3762
+ const idPath = path9.join(identityDir, `${remoteEmp.name}.md`);
3577
3763
  let localIdentity = null;
3578
3764
  try {
3579
- localIdentity = existsSync7(idPath) ? readFileSync6(idPath, "utf-8") : null;
3765
+ localIdentity = existsSync9(idPath) ? readFileSync7(idPath, "utf-8") : null;
3580
3766
  } catch {
3581
3767
  }
3582
3768
  if (localIdentity !== remoteIdentity) {
3583
- writeFileSync4(idPath, remoteIdentity, "utf-8");
3769
+ writeFileSync5(idPath, remoteIdentity, "utf-8");
3584
3770
  identitiesUpdated++;
3585
3771
  }
3586
3772
  }
@@ -3606,16 +3792,18 @@ async function mergeRosterFromRemote(remote, paths) {
3606
3792
  }
3607
3793
  if (remote.agentConfig && Object.keys(remote.agentConfig).length > 0) {
3608
3794
  try {
3609
- const agentConfigPath = path8.join(EXE_AI_DIR, "agent-config.json");
3795
+ const agentConfigPath = path9.join(EXE_AI_DIR, "agent-config.json");
3610
3796
  let local = {};
3611
- if (existsSync7(agentConfigPath)) {
3797
+ if (existsSync9(agentConfigPath)) {
3612
3798
  try {
3613
- local = JSON.parse(readFileSync6(agentConfigPath, "utf-8"));
3799
+ local = JSON.parse(readFileSync7(agentConfigPath, "utf-8"));
3614
3800
  } catch {
3615
3801
  }
3616
3802
  }
3617
3803
  const merged = { ...remote.agentConfig, ...local };
3618
- 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);
3619
3807
  } catch {
3620
3808
  }
3621
3809
  }
@@ -4039,7 +4227,7 @@ async function cloudPullDocuments(config) {
4039
4227
  }
4040
4228
  return { pulled };
4041
4229
  }
4042
- 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;
4043
4231
  var init_cloud_sync = __esm({
4044
4232
  "src/lib/cloud-sync.ts"() {
4045
4233
  "use strict";
@@ -4050,12 +4238,15 @@ var init_cloud_sync = __esm({
4050
4238
  init_config();
4051
4239
  init_crdt_sync();
4052
4240
  init_employees();
4241
+ init_secure_files();
4053
4242
  LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
4054
4243
  FETCH_TIMEOUT_MS = 3e4;
4055
4244
  PUSH_BATCH_SIZE = 5e3;
4056
- ROSTER_LOCK_PATH = path8.join(EXE_AI_DIR, "roster-merge.lock");
4245
+ ROSTER_LOCK_PATH = path9.join(EXE_AI_DIR, "roster-merge.lock");
4057
4246
  LOCK_STALE_MS = 3e4;
4058
- ROSTER_DELETIONS_PATH = path8.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");
4059
4250
  }
4060
4251
  });
4061
4252