@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
@@ -89,6 +89,44 @@ var init_db_retry = __esm({
89
89
  }
90
90
  });
91
91
 
92
+ // src/lib/secure-files.ts
93
+ import { chmodSync, existsSync, mkdirSync } from "fs";
94
+ import { chmod, mkdir } from "fs/promises";
95
+ async function ensurePrivateDir(dirPath) {
96
+ await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
97
+ try {
98
+ await chmod(dirPath, PRIVATE_DIR_MODE);
99
+ } catch {
100
+ }
101
+ }
102
+ function ensurePrivateDirSync(dirPath) {
103
+ mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
104
+ try {
105
+ chmodSync(dirPath, PRIVATE_DIR_MODE);
106
+ } catch {
107
+ }
108
+ }
109
+ async function enforcePrivateFile(filePath) {
110
+ try {
111
+ await chmod(filePath, PRIVATE_FILE_MODE);
112
+ } catch {
113
+ }
114
+ }
115
+ function enforcePrivateFileSync(filePath) {
116
+ try {
117
+ if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
118
+ } catch {
119
+ }
120
+ }
121
+ var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
122
+ var init_secure_files = __esm({
123
+ "src/lib/secure-files.ts"() {
124
+ "use strict";
125
+ PRIVATE_DIR_MODE = 448;
126
+ PRIVATE_FILE_MODE = 384;
127
+ }
128
+ });
129
+
92
130
  // src/lib/config.ts
93
131
  var config_exports = {};
94
132
  __export(config_exports, {
@@ -105,8 +143,8 @@ __export(config_exports, {
105
143
  migrateConfig: () => migrateConfig,
106
144
  saveConfig: () => saveConfig
107
145
  });
108
- import { readFile, writeFile, mkdir, chmod } from "fs/promises";
109
- import { readFileSync, existsSync, renameSync } from "fs";
146
+ import { readFile, writeFile } from "fs/promises";
147
+ import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
110
148
  import path from "path";
111
149
  import os from "os";
112
150
  function resolveDataDir() {
@@ -114,7 +152,7 @@ function resolveDataDir() {
114
152
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
115
153
  const newDir = path.join(os.homedir(), ".exe-os");
116
154
  const legacyDir = path.join(os.homedir(), ".exe-mem");
117
- if (!existsSync(newDir) && existsSync(legacyDir)) {
155
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
118
156
  try {
119
157
  renameSync(legacyDir, newDir);
120
158
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -177,9 +215,9 @@ function normalizeAutoUpdate(raw) {
177
215
  }
178
216
  async function loadConfig() {
179
217
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
180
- await mkdir(dir, { recursive: true });
218
+ await ensurePrivateDir(dir);
181
219
  const configPath = path.join(dir, "config.json");
182
- if (!existsSync(configPath)) {
220
+ if (!existsSync2(configPath)) {
183
221
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
184
222
  }
185
223
  const raw = await readFile(configPath, "utf-8");
@@ -192,6 +230,7 @@ async function loadConfig() {
192
230
  `);
193
231
  try {
194
232
  await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
233
+ await enforcePrivateFile(configPath);
195
234
  } catch {
196
235
  }
197
236
  }
@@ -210,7 +249,7 @@ async function loadConfig() {
210
249
  function loadConfigSync() {
211
250
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
212
251
  const configPath = path.join(dir, "config.json");
213
- if (!existsSync(configPath)) {
252
+ if (!existsSync2(configPath)) {
214
253
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
215
254
  }
216
255
  try {
@@ -228,12 +267,10 @@ function loadConfigSync() {
228
267
  }
229
268
  async function saveConfig(config) {
230
269
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
231
- await mkdir(dir, { recursive: true });
270
+ await ensurePrivateDir(dir);
232
271
  const configPath = path.join(dir, "config.json");
233
272
  await writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
234
- if (config.cloud?.apiKey) {
235
- await chmod(configPath, 384);
236
- }
273
+ await enforcePrivateFile(configPath);
237
274
  }
238
275
  async function loadConfigFrom(configPath) {
239
276
  const raw = await readFile(configPath, "utf-8");
@@ -253,6 +290,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
253
290
  var init_config = __esm({
254
291
  "src/lib/config.ts"() {
255
292
  "use strict";
293
+ init_secure_files();
256
294
  EXE_AI_DIR = resolveDataDir();
257
295
  DB_PATH = path.join(EXE_AI_DIR, "memories.db");
258
296
  MODELS_DIR = path.join(EXE_AI_DIR, "models");
@@ -331,7 +369,7 @@ var init_config = __esm({
331
369
 
332
370
  // src/lib/employees.ts
333
371
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
334
- import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
372
+ import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
335
373
  import { execSync } from "child_process";
336
374
  import path2 from "path";
337
375
  import os2 from "os";
@@ -355,7 +393,7 @@ function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
355
393
  return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
356
394
  }
357
395
  async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
358
- if (!existsSync2(employeesPath)) {
396
+ if (!existsSync3(employeesPath)) {
359
397
  return [];
360
398
  }
361
399
  const raw = await readFile2(employeesPath, "utf-8");
@@ -370,7 +408,7 @@ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
370
408
  await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
371
409
  }
372
410
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
373
- if (!existsSync2(employeesPath)) return [];
411
+ if (!existsSync3(employeesPath)) return [];
374
412
  try {
375
413
  return JSON.parse(readFileSync2(employeesPath, "utf-8"));
376
414
  } catch {
@@ -404,7 +442,7 @@ function registerBinSymlinks(name) {
404
442
  for (const suffix of ["", "-opencode"]) {
405
443
  const linkName = `${name}${suffix}`;
406
444
  const linkPath = path2.join(binDir, linkName);
407
- if (existsSync2(linkPath)) {
445
+ if (existsSync3(linkPath)) {
408
446
  skipped.push(linkName);
409
447
  continue;
410
448
  }
@@ -1013,13 +1051,50 @@ var init_database_adapter = __esm({
1013
1051
  }
1014
1052
  });
1015
1053
 
1054
+ // src/lib/daemon-auth.ts
1055
+ import crypto from "crypto";
1056
+ import path4 from "path";
1057
+ import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
1058
+ function normalizeToken(token) {
1059
+ if (!token) return null;
1060
+ const trimmed = token.trim();
1061
+ return trimmed.length > 0 ? trimmed : null;
1062
+ }
1063
+ function readDaemonToken() {
1064
+ try {
1065
+ if (!existsSync4(DAEMON_TOKEN_PATH)) return null;
1066
+ return normalizeToken(readFileSync3(DAEMON_TOKEN_PATH, "utf8"));
1067
+ } catch {
1068
+ return null;
1069
+ }
1070
+ }
1071
+ function ensureDaemonToken(seed) {
1072
+ const existing = readDaemonToken();
1073
+ if (existing) return existing;
1074
+ const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
1075
+ ensurePrivateDirSync(EXE_AI_DIR);
1076
+ writeFileSync2(DAEMON_TOKEN_PATH, `${token}
1077
+ `, "utf8");
1078
+ enforcePrivateFileSync(DAEMON_TOKEN_PATH);
1079
+ return token;
1080
+ }
1081
+ var DAEMON_TOKEN_PATH;
1082
+ var init_daemon_auth = __esm({
1083
+ "src/lib/daemon-auth.ts"() {
1084
+ "use strict";
1085
+ init_config();
1086
+ init_secure_files();
1087
+ DAEMON_TOKEN_PATH = path4.join(EXE_AI_DIR, "exed.token");
1088
+ }
1089
+ });
1090
+
1016
1091
  // src/lib/exe-daemon-client.ts
1017
1092
  import net from "net";
1018
1093
  import os4 from "os";
1019
1094
  import { spawn } from "child_process";
1020
1095
  import { randomUUID } from "crypto";
1021
- import { existsSync as existsSync3, unlinkSync as unlinkSync2, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
1022
- import path4 from "path";
1096
+ import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
1097
+ import path5 from "path";
1023
1098
  import { fileURLToPath } from "url";
1024
1099
  function handleData(chunk) {
1025
1100
  _buffer += chunk.toString();
@@ -1047,9 +1122,9 @@ function handleData(chunk) {
1047
1122
  }
1048
1123
  }
1049
1124
  function cleanupStaleFiles() {
1050
- if (existsSync3(PID_PATH)) {
1125
+ if (existsSync5(PID_PATH)) {
1051
1126
  try {
1052
- const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
1127
+ const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
1053
1128
  if (pid > 0) {
1054
1129
  try {
1055
1130
  process.kill(pid, 0);
@@ -1070,11 +1145,11 @@ function cleanupStaleFiles() {
1070
1145
  }
1071
1146
  }
1072
1147
  function findPackageRoot() {
1073
- let dir = path4.dirname(fileURLToPath(import.meta.url));
1074
- const { root } = path4.parse(dir);
1148
+ let dir = path5.dirname(fileURLToPath(import.meta.url));
1149
+ const { root } = path5.parse(dir);
1075
1150
  while (dir !== root) {
1076
- if (existsSync3(path4.join(dir, "package.json"))) return dir;
1077
- dir = path4.dirname(dir);
1151
+ if (existsSync5(path5.join(dir, "package.json"))) return dir;
1152
+ dir = path5.dirname(dir);
1078
1153
  }
1079
1154
  return null;
1080
1155
  }
@@ -1100,16 +1175,17 @@ function spawnDaemon() {
1100
1175
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
1101
1176
  return;
1102
1177
  }
1103
- const daemonPath = path4.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1104
- if (!existsSync3(daemonPath)) {
1178
+ const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1179
+ if (!existsSync5(daemonPath)) {
1105
1180
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
1106
1181
  `);
1107
1182
  return;
1108
1183
  }
1109
1184
  const resolvedPath = daemonPath;
1185
+ const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
1110
1186
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
1111
1187
  `);
1112
- const logPath = path4.join(path4.dirname(SOCKET_PATH), "exed.log");
1188
+ const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
1113
1189
  let stderrFd = "ignore";
1114
1190
  try {
1115
1191
  stderrFd = openSync(logPath, "a");
@@ -1127,7 +1203,8 @@ function spawnDaemon() {
1127
1203
  TMUX_PANE: void 0,
1128
1204
  // Prevents resolveExeSession() from scoping to one session
1129
1205
  EXE_DAEMON_SOCK: SOCKET_PATH,
1130
- EXE_DAEMON_PID: PID_PATH
1206
+ EXE_DAEMON_PID: PID_PATH,
1207
+ [DAEMON_TOKEN_ENV]: daemonToken
1131
1208
  }
1132
1209
  });
1133
1210
  child.unref();
@@ -1237,13 +1314,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
1237
1314
  return;
1238
1315
  }
1239
1316
  const id = randomUUID();
1317
+ const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
1240
1318
  const timer = setTimeout(() => {
1241
1319
  _pending.delete(id);
1242
1320
  resolve({ error: "Request timeout" });
1243
1321
  }, timeoutMs);
1244
1322
  _pending.set(id, { resolve, timer });
1245
1323
  try {
1246
- _socket.write(JSON.stringify({ id, ...payload }) + "\n");
1324
+ _socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
1247
1325
  } catch {
1248
1326
  clearTimeout(timer);
1249
1327
  _pending.delete(id);
@@ -1272,9 +1350,9 @@ function killAndRespawnDaemon() {
1272
1350
  }
1273
1351
  try {
1274
1352
  process.stderr.write("[exed-client] Killing daemon for restart...\n");
1275
- if (existsSync3(PID_PATH)) {
1353
+ if (existsSync5(PID_PATH)) {
1276
1354
  try {
1277
- const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
1355
+ const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
1278
1356
  if (pid > 0) {
1279
1357
  try {
1280
1358
  process.kill(pid, "SIGKILL");
@@ -1394,17 +1472,19 @@ function disconnectClient() {
1394
1472
  function isClientConnected() {
1395
1473
  return _connected;
1396
1474
  }
1397
- var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _requestCount, _consecutiveFailures, HEALTH_CHECK_INTERVAL, MAX_RETRIES_BEFORE_RESTART, RETRY_DELAYS_MS, MIN_DAEMON_AGE_MS, _pending, MAX_BUFFER;
1475
+ var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, DAEMON_TOKEN_ENV, _socket, _connected, _buffer, _requestCount, _consecutiveFailures, HEALTH_CHECK_INTERVAL, MAX_RETRIES_BEFORE_RESTART, RETRY_DELAYS_MS, MIN_DAEMON_AGE_MS, _pending, MAX_BUFFER;
1398
1476
  var init_exe_daemon_client = __esm({
1399
1477
  "src/lib/exe-daemon-client.ts"() {
1400
1478
  "use strict";
1401
1479
  init_config();
1402
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path4.join(EXE_AI_DIR, "exed.sock");
1403
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path4.join(EXE_AI_DIR, "exed.pid");
1404
- SPAWN_LOCK_PATH = path4.join(EXE_AI_DIR, "exed-spawn.lock");
1480
+ init_daemon_auth();
1481
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path5.join(EXE_AI_DIR, "exed.sock");
1482
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path5.join(EXE_AI_DIR, "exed.pid");
1483
+ SPAWN_LOCK_PATH = path5.join(EXE_AI_DIR, "exed-spawn.lock");
1405
1484
  SPAWN_LOCK_STALE_MS = 3e4;
1406
1485
  CONNECT_TIMEOUT_MS = 15e3;
1407
1486
  REQUEST_TIMEOUT_MS = 3e4;
1487
+ DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
1408
1488
  _socket = null;
1409
1489
  _connected = false;
1410
1490
  _buffer = "";
@@ -1989,6 +2069,7 @@ async function ensureSchema() {
1989
2069
  project TEXT NOT NULL,
1990
2070
  summary TEXT NOT NULL,
1991
2071
  task_file TEXT,
2072
+ session_scope TEXT,
1992
2073
  read INTEGER NOT NULL DEFAULT 0,
1993
2074
  created_at TEXT NOT NULL
1994
2075
  );
@@ -1997,7 +2078,7 @@ async function ensureSchema() {
1997
2078
  ON notifications(read);
1998
2079
 
1999
2080
  CREATE INDEX IF NOT EXISTS idx_notifications_agent
2000
- ON notifications(agent_id);
2081
+ ON notifications(agent_id, session_scope);
2001
2082
 
2002
2083
  CREATE INDEX IF NOT EXISTS idx_notifications_task_file
2003
2084
  ON notifications(task_file);
@@ -2035,6 +2116,7 @@ async function ensureSchema() {
2035
2116
  target_agent TEXT NOT NULL,
2036
2117
  target_project TEXT,
2037
2118
  target_device TEXT NOT NULL DEFAULT 'local',
2119
+ session_scope TEXT,
2038
2120
  content TEXT NOT NULL,
2039
2121
  priority TEXT DEFAULT 'normal',
2040
2122
  status TEXT DEFAULT 'pending',
@@ -2048,10 +2130,31 @@ async function ensureSchema() {
2048
2130
  );
2049
2131
 
2050
2132
  CREATE INDEX IF NOT EXISTS idx_messages_target
2051
- ON messages(target_agent, status);
2133
+ ON messages(target_agent, session_scope, status);
2052
2134
 
2053
2135
  CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
2054
- ON messages(target_agent, from_agent, server_seq);
2136
+ ON messages(target_agent, session_scope, from_agent, server_seq);
2137
+ `);
2138
+ try {
2139
+ await client.execute({
2140
+ sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
2141
+ args: []
2142
+ });
2143
+ } catch {
2144
+ }
2145
+ try {
2146
+ await client.execute({
2147
+ sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
2148
+ args: []
2149
+ });
2150
+ } catch {
2151
+ }
2152
+ await client.executeMultiple(`
2153
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
2154
+ ON notifications(agent_id, session_scope, read, created_at);
2155
+
2156
+ CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
2157
+ ON messages(target_agent, session_scope, status, created_at);
2055
2158
  `);
2056
2159
  try {
2057
2160
  await client.execute({
@@ -2635,6 +2738,13 @@ async function ensureSchema() {
2635
2738
  } catch {
2636
2739
  }
2637
2740
  }
2741
+ try {
2742
+ await client.execute({
2743
+ sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
2744
+ args: []
2745
+ });
2746
+ } catch {
2747
+ }
2638
2748
  }
2639
2749
  async function disposeDatabase() {
2640
2750
  if (_walCheckpointTimer) {
@@ -2682,14 +2792,14 @@ __export(keychain_exports, {
2682
2792
  setMasterKey: () => setMasterKey
2683
2793
  });
2684
2794
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
2685
- import { existsSync as existsSync4 } from "fs";
2686
- import path5 from "path";
2795
+ import { existsSync as existsSync6 } from "fs";
2796
+ import path6 from "path";
2687
2797
  import os5 from "os";
2688
2798
  function getKeyDir() {
2689
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path5.join(os5.homedir(), ".exe-os");
2799
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path6.join(os5.homedir(), ".exe-os");
2690
2800
  }
2691
2801
  function getKeyPath() {
2692
- return path5.join(getKeyDir(), "master.key");
2802
+ return path6.join(getKeyDir(), "master.key");
2693
2803
  }
2694
2804
  async function tryKeytar() {
2695
2805
  try {
@@ -2710,7 +2820,7 @@ async function getMasterKey() {
2710
2820
  }
2711
2821
  }
2712
2822
  const keyPath = getKeyPath();
2713
- if (!existsSync4(keyPath)) {
2823
+ if (!existsSync6(keyPath)) {
2714
2824
  process.stderr.write(
2715
2825
  `[keychain] Key not found at ${keyPath} (HOME=${os5.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
2716
2826
  `
@@ -2753,7 +2863,7 @@ async function deleteMasterKey() {
2753
2863
  }
2754
2864
  }
2755
2865
  const keyPath = getKeyPath();
2756
- if (existsSync4(keyPath)) {
2866
+ if (existsSync6(keyPath)) {
2757
2867
  await unlink(keyPath);
2758
2868
  }
2759
2869
  }
@@ -2855,6 +2965,7 @@ var shard_manager_exports = {};
2855
2965
  __export(shard_manager_exports, {
2856
2966
  disposeShards: () => disposeShards,
2857
2967
  ensureShardSchema: () => ensureShardSchema,
2968
+ getOpenShardCount: () => getOpenShardCount,
2858
2969
  getReadyShardClient: () => getReadyShardClient,
2859
2970
  getShardClient: () => getShardClient,
2860
2971
  getShardsDir: () => getShardsDir,
@@ -2863,15 +2974,18 @@ __export(shard_manager_exports, {
2863
2974
  listShards: () => listShards,
2864
2975
  shardExists: () => shardExists
2865
2976
  });
2866
- import path6 from "path";
2867
- import { existsSync as existsSync5, mkdirSync, readdirSync } from "fs";
2977
+ import path7 from "path";
2978
+ import { existsSync as existsSync7, mkdirSync as mkdirSync2, readdirSync } from "fs";
2868
2979
  import { createClient as createClient2 } from "@libsql/client";
2869
2980
  function initShardManager(encryptionKey) {
2870
2981
  _encryptionKey = encryptionKey;
2871
- if (!existsSync5(SHARDS_DIR)) {
2872
- mkdirSync(SHARDS_DIR, { recursive: true });
2982
+ if (!existsSync7(SHARDS_DIR)) {
2983
+ mkdirSync2(SHARDS_DIR, { recursive: true });
2873
2984
  }
2874
2985
  _shardingEnabled = true;
2986
+ if (_evictionTimer) clearInterval(_evictionTimer);
2987
+ _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
2988
+ _evictionTimer.unref();
2875
2989
  }
2876
2990
  function isShardingEnabled() {
2877
2991
  return _shardingEnabled;
@@ -2888,21 +3002,28 @@ function getShardClient(projectName) {
2888
3002
  throw new Error(`Invalid project name for shard: "${projectName}"`);
2889
3003
  }
2890
3004
  const cached = _shards.get(safeName);
2891
- if (cached) return cached;
2892
- const dbPath = path6.join(SHARDS_DIR, `${safeName}.db`);
3005
+ if (cached) {
3006
+ _shardLastAccess.set(safeName, Date.now());
3007
+ return cached;
3008
+ }
3009
+ while (_shards.size >= MAX_OPEN_SHARDS) {
3010
+ evictLRU();
3011
+ }
3012
+ const dbPath = path7.join(SHARDS_DIR, `${safeName}.db`);
2893
3013
  const client = createClient2({
2894
3014
  url: `file:${dbPath}`,
2895
3015
  encryptionKey: _encryptionKey
2896
3016
  });
2897
3017
  _shards.set(safeName, client);
3018
+ _shardLastAccess.set(safeName, Date.now());
2898
3019
  return client;
2899
3020
  }
2900
3021
  function shardExists(projectName) {
2901
3022
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
2902
- return existsSync5(path6.join(SHARDS_DIR, `${safeName}.db`));
3023
+ return existsSync7(path7.join(SHARDS_DIR, `${safeName}.db`));
2903
3024
  }
2904
3025
  function listShards() {
2905
- if (!existsSync5(SHARDS_DIR)) return [];
3026
+ if (!existsSync7(SHARDS_DIR)) return [];
2906
3027
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
2907
3028
  }
2908
3029
  async function ensureShardSchema(client) {
@@ -2954,6 +3075,8 @@ async function ensureShardSchema(client) {
2954
3075
  for (const col of [
2955
3076
  "ALTER TABLE memories ADD COLUMN task_id TEXT",
2956
3077
  "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
3078
+ "ALTER TABLE memories ADD COLUMN author_device_id TEXT",
3079
+ "ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
2957
3080
  "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
2958
3081
  "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
2959
3082
  "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
@@ -3091,21 +3214,69 @@ async function getReadyShardClient(projectName) {
3091
3214
  await ensureShardSchema(client);
3092
3215
  return client;
3093
3216
  }
3217
+ function evictLRU() {
3218
+ let oldest = null;
3219
+ let oldestTime = Infinity;
3220
+ for (const [name, time] of _shardLastAccess) {
3221
+ if (time < oldestTime) {
3222
+ oldestTime = time;
3223
+ oldest = name;
3224
+ }
3225
+ }
3226
+ if (oldest) {
3227
+ const client = _shards.get(oldest);
3228
+ if (client) {
3229
+ client.close();
3230
+ }
3231
+ _shards.delete(oldest);
3232
+ _shardLastAccess.delete(oldest);
3233
+ }
3234
+ }
3235
+ function evictIdleShards() {
3236
+ const now = Date.now();
3237
+ const toEvict = [];
3238
+ for (const [name, lastAccess] of _shardLastAccess) {
3239
+ if (now - lastAccess > SHARD_IDLE_MS) {
3240
+ toEvict.push(name);
3241
+ }
3242
+ }
3243
+ for (const name of toEvict) {
3244
+ const client = _shards.get(name);
3245
+ if (client) {
3246
+ client.close();
3247
+ }
3248
+ _shards.delete(name);
3249
+ _shardLastAccess.delete(name);
3250
+ }
3251
+ }
3252
+ function getOpenShardCount() {
3253
+ return _shards.size;
3254
+ }
3094
3255
  function disposeShards() {
3256
+ if (_evictionTimer) {
3257
+ clearInterval(_evictionTimer);
3258
+ _evictionTimer = null;
3259
+ }
3095
3260
  for (const [, client] of _shards) {
3096
3261
  client.close();
3097
3262
  }
3098
3263
  _shards.clear();
3264
+ _shardLastAccess.clear();
3099
3265
  _shardingEnabled = false;
3100
3266
  _encryptionKey = null;
3101
3267
  }
3102
- var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
3268
+ var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
3103
3269
  var init_shard_manager = __esm({
3104
3270
  "src/lib/shard-manager.ts"() {
3105
3271
  "use strict";
3106
3272
  init_config();
3107
- SHARDS_DIR = path6.join(EXE_AI_DIR, "shards");
3273
+ SHARDS_DIR = path7.join(EXE_AI_DIR, "shards");
3274
+ SHARD_IDLE_MS = 5 * 60 * 1e3;
3275
+ MAX_OPEN_SHARDS = 10;
3276
+ EVICTION_INTERVAL_MS = 60 * 1e3;
3108
3277
  _shards = /* @__PURE__ */ new Map();
3278
+ _shardLastAccess = /* @__PURE__ */ new Map();
3279
+ _evictionTimer = null;
3109
3280
  _encryptionKey = null;
3110
3281
  _shardingEnabled = false;
3111
3282
  }
@@ -3298,56 +3469,14 @@ ${p.content}`).join("\n\n");
3298
3469
  }
3299
3470
  });
3300
3471
 
3301
- // src/lib/notifications.ts
3302
- import crypto from "crypto";
3303
- import path7 from "path";
3304
- import os6 from "os";
3305
- import {
3306
- readFileSync as readFileSync4,
3307
- readdirSync as readdirSync2,
3308
- unlinkSync as unlinkSync3,
3309
- existsSync as existsSync6,
3310
- rmdirSync
3311
- } from "fs";
3312
- async function writeNotification(notification) {
3313
- try {
3314
- const client = getClient();
3315
- const id = crypto.randomUUID();
3316
- const now = (/* @__PURE__ */ new Date()).toISOString();
3317
- await client.execute({
3318
- sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
3319
- VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
3320
- args: [
3321
- id,
3322
- notification.agentId,
3323
- notification.agentRole,
3324
- notification.event,
3325
- notification.project,
3326
- notification.summary,
3327
- notification.taskFile ?? null,
3328
- now
3329
- ]
3330
- });
3331
- } catch (err) {
3332
- process.stderr.write(`[notifications] WRITE FAILED: ${err instanceof Error ? err.message : String(err)}
3333
- `);
3334
- }
3335
- }
3336
- var init_notifications = __esm({
3337
- "src/lib/notifications.ts"() {
3338
- "use strict";
3339
- init_database();
3340
- }
3341
- });
3342
-
3343
3472
  // src/lib/session-registry.ts
3344
3473
  import path8 from "path";
3345
- import os7 from "os";
3474
+ import os6 from "os";
3346
3475
  var REGISTRY_PATH;
3347
3476
  var init_session_registry = __esm({
3348
3477
  "src/lib/session-registry.ts"() {
3349
3478
  "use strict";
3350
- REGISTRY_PATH = path8.join(os7.homedir(), ".exe-os", "session-registry.json");
3479
+ REGISTRY_PATH = path8.join(os6.homedir(), ".exe-os", "session-registry.json");
3351
3480
  }
3352
3481
  });
3353
3482
 
@@ -3582,7 +3711,7 @@ var init_runtime_table = __esm({
3582
3711
  });
3583
3712
 
3584
3713
  // src/lib/agent-config.ts
3585
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, existsSync as existsSync7, mkdirSync as mkdirSync2 } from "fs";
3714
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync8 } from "fs";
3586
3715
  import path9 from "path";
3587
3716
  var AGENT_CONFIG_PATH, DEFAULT_MODELS;
3588
3717
  var init_agent_config = __esm({
@@ -3590,6 +3719,7 @@ var init_agent_config = __esm({
3590
3719
  "use strict";
3591
3720
  init_config();
3592
3721
  init_runtime_table();
3722
+ init_secure_files();
3593
3723
  AGENT_CONFIG_PATH = path9.join(EXE_AI_DIR, "agent-config.json");
3594
3724
  DEFAULT_MODELS = {
3595
3725
  claude: "claude-opus-4",
@@ -3600,16 +3730,16 @@ var init_agent_config = __esm({
3600
3730
  });
3601
3731
 
3602
3732
  // src/lib/intercom-queue.ts
3603
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, renameSync as renameSync3, existsSync as existsSync8, mkdirSync as mkdirSync3 } from "fs";
3733
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync9, mkdirSync as mkdirSync3 } from "fs";
3604
3734
  import path10 from "path";
3605
- import os8 from "os";
3735
+ import os7 from "os";
3606
3736
  var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
3607
3737
  var init_intercom_queue = __esm({
3608
3738
  "src/lib/intercom-queue.ts"() {
3609
3739
  "use strict";
3610
- QUEUE_PATH = path10.join(os8.homedir(), ".exe-os", "intercom-queue.json");
3740
+ QUEUE_PATH = path10.join(os7.homedir(), ".exe-os", "intercom-queue.json");
3611
3741
  TTL_MS = 60 * 60 * 1e3;
3612
- INTERCOM_LOG = path10.join(os8.homedir(), ".exe-os", "intercom.log");
3742
+ INTERCOM_LOG = path10.join(os7.homedir(), ".exe-os", "intercom.log");
3613
3743
  }
3614
3744
  });
3615
3745
 
@@ -3630,8 +3760,11 @@ __export(license_exports, {
3630
3760
  stopLicenseRevalidation: () => stopLicenseRevalidation,
3631
3761
  validateLicense: () => validateLicense
3632
3762
  });
3633
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, existsSync as existsSync9, mkdirSync as mkdirSync4 } from "fs";
3763
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync10, mkdirSync as mkdirSync4 } from "fs";
3634
3764
  import { randomUUID as randomUUID3 } from "crypto";
3765
+ import { createRequire as createRequire2 } from "module";
3766
+ import { pathToFileURL as pathToFileURL2 } from "url";
3767
+ import os8 from "os";
3635
3768
  import path11 from "path";
3636
3769
  import { jwtVerify, importSPKI } from "jose";
3637
3770
  async function fetchRetry(url, init) {
@@ -3645,14 +3778,14 @@ async function fetchRetry(url, init) {
3645
3778
  function loadDeviceId() {
3646
3779
  const deviceJsonPath = path11.join(EXE_AI_DIR, "device.json");
3647
3780
  try {
3648
- if (existsSync9(deviceJsonPath)) {
3781
+ if (existsSync10(deviceJsonPath)) {
3649
3782
  const data = JSON.parse(readFileSync7(deviceJsonPath, "utf8"));
3650
3783
  if (data.deviceId) return data.deviceId;
3651
3784
  }
3652
3785
  } catch {
3653
3786
  }
3654
3787
  try {
3655
- if (existsSync9(DEVICE_ID_PATH)) {
3788
+ if (existsSync10(DEVICE_ID_PATH)) {
3656
3789
  const id2 = readFileSync7(DEVICE_ID_PATH, "utf8").trim();
3657
3790
  if (id2) return id2;
3658
3791
  }
@@ -3660,12 +3793,12 @@ function loadDeviceId() {
3660
3793
  }
3661
3794
  const id = randomUUID3();
3662
3795
  mkdirSync4(EXE_AI_DIR, { recursive: true });
3663
- writeFileSync4(DEVICE_ID_PATH, id, "utf8");
3796
+ writeFileSync5(DEVICE_ID_PATH, id, "utf8");
3664
3797
  return id;
3665
3798
  }
3666
3799
  function loadLicense() {
3667
3800
  try {
3668
- if (!existsSync9(LICENSE_PATH)) return null;
3801
+ if (!existsSync10(LICENSE_PATH)) return null;
3669
3802
  return readFileSync7(LICENSE_PATH, "utf8").trim();
3670
3803
  } catch {
3671
3804
  return null;
@@ -3673,7 +3806,7 @@ function loadLicense() {
3673
3806
  }
3674
3807
  function saveLicense(apiKey) {
3675
3808
  mkdirSync4(EXE_AI_DIR, { recursive: true });
3676
- writeFileSync4(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
3809
+ writeFileSync5(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
3677
3810
  }
3678
3811
  async function verifyLicenseJwt(token) {
3679
3812
  try {
@@ -3699,7 +3832,7 @@ async function verifyLicenseJwt(token) {
3699
3832
  }
3700
3833
  async function getCachedLicense() {
3701
3834
  try {
3702
- if (!existsSync9(CACHE_PATH)) return null;
3835
+ if (!existsSync10(CACHE_PATH)) return null;
3703
3836
  const raw = JSON.parse(readFileSync7(CACHE_PATH, "utf8"));
3704
3837
  if (!raw.token || typeof raw.token !== "string") return null;
3705
3838
  return await verifyLicenseJwt(raw.token);
@@ -3709,7 +3842,7 @@ async function getCachedLicense() {
3709
3842
  }
3710
3843
  function readCachedToken() {
3711
3844
  try {
3712
- if (!existsSync9(CACHE_PATH)) return null;
3845
+ if (!existsSync10(CACHE_PATH)) return null;
3713
3846
  const raw = JSON.parse(readFileSync7(CACHE_PATH, "utf8"));
3714
3847
  return typeof raw.token === "string" ? raw.token : null;
3715
3848
  } catch {
@@ -3744,56 +3877,130 @@ function getRawCachedPlan() {
3744
3877
  }
3745
3878
  function cacheResponse(token) {
3746
3879
  try {
3747
- writeFileSync4(CACHE_PATH, JSON.stringify({ token }), "utf8");
3880
+ writeFileSync5(CACHE_PATH, JSON.stringify({ token }), "utf8");
3748
3881
  } catch {
3749
3882
  }
3750
3883
  }
3751
- async function validateLicense(apiKey, deviceId) {
3752
- const did = deviceId ?? loadDeviceId();
3884
+ function loadPrismaForLicense() {
3885
+ if (_prismaFailed) return null;
3886
+ const dbUrl = process.env.DATABASE_URL;
3887
+ if (!dbUrl) {
3888
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path11.join(os8.homedir(), "exe-db");
3889
+ if (!existsSync10(path11.join(exeDbRoot, "package.json"))) {
3890
+ _prismaFailed = true;
3891
+ return null;
3892
+ }
3893
+ }
3894
+ if (!_prismaPromise) {
3895
+ _prismaPromise = (async () => {
3896
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
3897
+ if (explicitPath) {
3898
+ const mod2 = await import(pathToFileURL2(explicitPath).href);
3899
+ const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
3900
+ if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
3901
+ return new Ctor2();
3902
+ }
3903
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path11.join(os8.homedir(), "exe-db");
3904
+ const req = createRequire2(path11.join(exeDbRoot, "package.json"));
3905
+ const entry = req.resolve("@prisma/client");
3906
+ const mod = await import(pathToFileURL2(entry).href);
3907
+ const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
3908
+ if (!Ctor) throw new Error(`No PrismaClient in ${entry}`);
3909
+ return new Ctor();
3910
+ })().catch((err) => {
3911
+ _prismaFailed = true;
3912
+ _prismaPromise = null;
3913
+ throw err;
3914
+ });
3915
+ }
3916
+ return _prismaPromise;
3917
+ }
3918
+ async function validateViaPostgres(apiKey) {
3919
+ const loader = loadPrismaForLicense();
3920
+ if (!loader) return null;
3921
+ try {
3922
+ const prisma = await loader;
3923
+ const rows = await prisma.$queryRawUnsafe(
3924
+ `SELECT plan, email, status, device_limit, employee_limit, memory_limit, expires_at
3925
+ FROM billing.licenses WHERE key = $1 LIMIT 1`,
3926
+ apiKey
3927
+ );
3928
+ if (!rows || rows.length === 0) return null;
3929
+ const row = rows[0];
3930
+ if (row.status !== "active") return null;
3931
+ if (row.expires_at && new Date(row.expires_at) < /* @__PURE__ */ new Date()) return null;
3932
+ const plan = row.plan;
3933
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
3934
+ return {
3935
+ valid: true,
3936
+ plan,
3937
+ email: row.email,
3938
+ expiresAt: row.expires_at ? new Date(row.expires_at).toISOString() : null,
3939
+ deviceLimit: row.device_limit ?? limits.devices,
3940
+ employeeLimit: row.employee_limit ?? limits.employees,
3941
+ memoryLimit: row.memory_limit ?? limits.memories
3942
+ };
3943
+ } catch {
3944
+ return null;
3945
+ }
3946
+ }
3947
+ async function validateViaCFWorker(apiKey, deviceId) {
3753
3948
  try {
3754
3949
  const res = await fetchRetry(`${API_BASE}/auth/activate`, {
3755
3950
  method: "POST",
3756
3951
  headers: { "Content-Type": "application/json" },
3757
- body: JSON.stringify({ apiKey, deviceId: did }),
3952
+ body: JSON.stringify({ apiKey, deviceId }),
3758
3953
  signal: AbortSignal.timeout(1e4)
3759
3954
  });
3760
- if (res.ok) {
3761
- const data = await res.json();
3762
- if (data.error === "device_limit_exceeded") {
3763
- const cached2 = await getCachedLicense();
3764
- if (cached2) return cached2;
3765
- const raw2 = getRawCachedPlan();
3766
- if (raw2) return { ...raw2, valid: false };
3767
- return { ...FREE_LICENSE, valid: false, plan: "free" };
3768
- }
3769
- if (data.token) {
3770
- cacheResponse(data.token);
3771
- const verified = await verifyLicenseJwt(data.token);
3772
- if (verified) return verified;
3955
+ if (!res.ok) return null;
3956
+ const data = await res.json();
3957
+ if (data.error === "device_limit_exceeded") return null;
3958
+ if (!data.valid) return null;
3959
+ if (data.token) {
3960
+ cacheResponse(data.token);
3961
+ const verified = await verifyLicenseJwt(data.token);
3962
+ if (verified) return verified;
3963
+ }
3964
+ const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
3965
+ return {
3966
+ valid: data.valid,
3967
+ plan: data.plan,
3968
+ email: data.email,
3969
+ expiresAt: data.expiresAt,
3970
+ deviceLimit: limits.devices,
3971
+ employeeLimit: limits.employees,
3972
+ memoryLimit: limits.memories
3973
+ };
3974
+ } catch {
3975
+ return null;
3976
+ }
3977
+ }
3978
+ async function validateLicense(apiKey, deviceId) {
3979
+ const did = deviceId ?? loadDeviceId();
3980
+ const pgResult = await validateViaPostgres(apiKey);
3981
+ if (pgResult) {
3982
+ try {
3983
+ writeFileSync5(CACHE_PATH, JSON.stringify({ pgLicense: pgResult, ts: Date.now() }), "utf8");
3984
+ } catch {
3985
+ }
3986
+ return pgResult;
3987
+ }
3988
+ const cfResult = await validateViaCFWorker(apiKey, did);
3989
+ if (cfResult) return cfResult;
3990
+ const cached = await getCachedLicense();
3991
+ if (cached) return cached;
3992
+ try {
3993
+ if (existsSync10(CACHE_PATH)) {
3994
+ const raw = JSON.parse(readFileSync7(CACHE_PATH, "utf8"));
3995
+ if (raw.pgLicense && raw.ts && Date.now() - raw.ts < 7 * 24 * 60 * 60 * 1e3) {
3996
+ return raw.pgLicense;
3773
3997
  }
3774
- const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
3775
- return {
3776
- valid: data.valid,
3777
- plan: data.plan,
3778
- email: data.email,
3779
- expiresAt: data.expiresAt,
3780
- deviceLimit: limits.devices,
3781
- employeeLimit: limits.employees,
3782
- memoryLimit: limits.memories
3783
- };
3784
3998
  }
3785
- const cached = await getCachedLicense();
3786
- if (cached) return cached;
3787
- const raw = getRawCachedPlan();
3788
- if (raw) return raw;
3789
- return { ...FREE_LICENSE, valid: false, plan: "free" };
3790
3999
  } catch {
3791
- const cached = await getCachedLicense();
3792
- if (cached) return cached;
3793
- const rawFallback = getRawCachedPlan();
3794
- if (rawFallback) return rawFallback;
3795
- return { ...FREE_LICENSE, valid: false, error: "offline" };
3796
4000
  }
4001
+ const rawFallback = getRawCachedPlan();
4002
+ if (rawFallback) return rawFallback;
4003
+ return { ...FREE_LICENSE, valid: false };
3797
4004
  }
3798
4005
  function getCacheAgeMs() {
3799
4006
  try {
@@ -3809,7 +4016,7 @@ async function checkLicense() {
3809
4016
  if (!key) {
3810
4017
  try {
3811
4018
  const configPath = path11.join(EXE_AI_DIR, "config.json");
3812
- if (existsSync9(configPath)) {
4019
+ if (existsSync10(configPath)) {
3813
4020
  const raw = JSON.parse(readFileSync7(configPath, "utf8"));
3814
4021
  const cloud = raw.cloud;
3815
4022
  if (cloud?.apiKey) {
@@ -3964,7 +4171,7 @@ function stopLicenseRevalidation() {
3964
4171
  _revalTimer = null;
3965
4172
  }
3966
4173
  }
3967
- var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, RETRY_DELAY_MS, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE, CACHE_MAX_AGE_MS, _revalTimer;
4174
+ var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, RETRY_DELAY_MS, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE, _prismaPromise, _prismaFailed, CACHE_MAX_AGE_MS, _revalTimer;
3968
4175
  var init_license = __esm({
3969
4176
  "src/lib/license.ts"() {
3970
4177
  "use strict";
@@ -3995,6 +4202,8 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
3995
4202
  employeeLimit: 1,
3996
4203
  memoryLimit: 5e3
3997
4204
  };
4205
+ _prismaPromise = null;
4206
+ _prismaFailed = false;
3998
4207
  CACHE_MAX_AGE_MS = 36e5;
3999
4208
  _revalTimer = null;
4000
4209
  }
@@ -4011,11 +4220,11 @@ __export(plan_limits_exports, {
4011
4220
  countActiveMemories: () => countActiveMemories,
4012
4221
  getLicenseSync: () => getLicenseSync
4013
4222
  });
4014
- import { readFileSync as readFileSync8, existsSync as existsSync10 } from "fs";
4223
+ import { readFileSync as readFileSync8, existsSync as existsSync11 } from "fs";
4015
4224
  import path12 from "path";
4016
4225
  function getLicenseSync() {
4017
4226
  try {
4018
- if (!existsSync10(CACHE_PATH2)) return freeLicense();
4227
+ if (!existsSync11(CACHE_PATH2)) return freeLicense();
4019
4228
  const raw = JSON.parse(readFileSync8(CACHE_PATH2, "utf8"));
4020
4229
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
4021
4230
  const parts = raw.token.split(".");
@@ -4083,7 +4292,7 @@ function assertEmployeeLimitSync(rosterPath) {
4083
4292
  const filePath = rosterPath ?? EMPLOYEES_PATH;
4084
4293
  let count = 0;
4085
4294
  try {
4086
- if (existsSync10(filePath)) {
4295
+ if (existsSync11(filePath)) {
4087
4296
  const raw = readFileSync8(filePath, "utf8");
4088
4297
  const employees = JSON.parse(raw);
4089
4298
  count = Array.isArray(employees) ? employees.length : 0;
@@ -4126,7 +4335,7 @@ var init_plan_limits = __esm({
4126
4335
  });
4127
4336
 
4128
4337
  // src/lib/tmux-routing.ts
4129
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, existsSync as existsSync11, appendFileSync, readdirSync as readdirSync3 } from "fs";
4338
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, existsSync as existsSync12, appendFileSync, readdirSync as readdirSync2 } from "fs";
4130
4339
  import path13 from "path";
4131
4340
  import os9 from "os";
4132
4341
  import { fileURLToPath as fileURLToPath2 } from "url";
@@ -4216,6 +4425,51 @@ var init_task_scope = __esm({
4216
4425
  }
4217
4426
  });
4218
4427
 
4428
+ // src/lib/notifications.ts
4429
+ import crypto2 from "crypto";
4430
+ import path14 from "path";
4431
+ import os10 from "os";
4432
+ import {
4433
+ readFileSync as readFileSync10,
4434
+ readdirSync as readdirSync3,
4435
+ unlinkSync as unlinkSync3,
4436
+ existsSync as existsSync13,
4437
+ rmdirSync
4438
+ } from "fs";
4439
+ async function writeNotification(notification) {
4440
+ try {
4441
+ const client = getClient();
4442
+ const id = crypto2.randomUUID();
4443
+ const now = (/* @__PURE__ */ new Date()).toISOString();
4444
+ const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
4445
+ await client.execute({
4446
+ sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
4447
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
4448
+ args: [
4449
+ id,
4450
+ notification.agentId,
4451
+ notification.agentRole,
4452
+ notification.event,
4453
+ notification.project,
4454
+ notification.summary,
4455
+ notification.taskFile ?? null,
4456
+ sessionScope,
4457
+ now
4458
+ ]
4459
+ });
4460
+ } catch (err) {
4461
+ process.stderr.write(`[notifications] WRITE FAILED: ${err instanceof Error ? err.message : String(err)}
4462
+ `);
4463
+ }
4464
+ }
4465
+ var init_notifications = __esm({
4466
+ "src/lib/notifications.ts"() {
4467
+ "use strict";
4468
+ init_database();
4469
+ init_task_scope();
4470
+ }
4471
+ });
4472
+
4219
4473
  // src/lib/embedder.ts
4220
4474
  var embedder_exports = {};
4221
4475
  __export(embedder_exports, {
@@ -4253,10 +4507,10 @@ async function disposeEmbedder() {
4253
4507
  async function embedDirect(text) {
4254
4508
  const llamaCpp = await import("node-llama-cpp");
4255
4509
  const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
4256
- const { existsSync: existsSync16 } = await import("fs");
4257
- const path18 = await import("path");
4258
- const modelPath = path18.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
4259
- if (!existsSync16(modelPath)) {
4510
+ const { existsSync: existsSync18 } = await import("fs");
4511
+ const path19 = await import("path");
4512
+ const modelPath = path19.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
4513
+ if (!existsSync18(modelPath)) {
4260
4514
  throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
4261
4515
  }
4262
4516
  const llama = await llamaCpp.getLlama();
@@ -4294,14 +4548,14 @@ __export(worker_gate_exports, {
4294
4548
  tryAcquireBackfillLock: () => tryAcquireBackfillLock,
4295
4549
  tryAcquireWorkerSlot: () => tryAcquireWorkerSlot
4296
4550
  });
4297
- import { readdirSync as readdirSync4, writeFileSync as writeFileSync6, unlinkSync as unlinkSync4, mkdirSync as mkdirSync6, existsSync as existsSync12 } from "fs";
4298
- import path14 from "path";
4551
+ import { readdirSync as readdirSync4, writeFileSync as writeFileSync7, unlinkSync as unlinkSync4, mkdirSync as mkdirSync6, existsSync as existsSync14 } from "fs";
4552
+ import path15 from "path";
4299
4553
  function tryAcquireWorkerSlot() {
4300
4554
  try {
4301
4555
  mkdirSync6(WORKER_PID_DIR, { recursive: true });
4302
4556
  const reservationId = `res-${process.pid}-${Date.now()}`;
4303
- const reservationPath = path14.join(WORKER_PID_DIR, `${reservationId}.pid`);
4304
- writeFileSync6(reservationPath, String(process.pid));
4557
+ const reservationPath = path15.join(WORKER_PID_DIR, `${reservationId}.pid`);
4558
+ writeFileSync7(reservationPath, String(process.pid));
4305
4559
  const files = readdirSync4(WORKER_PID_DIR);
4306
4560
  let alive = 0;
4307
4561
  for (const f of files) {
@@ -4318,7 +4572,7 @@ function tryAcquireWorkerSlot() {
4318
4572
  alive++;
4319
4573
  } catch {
4320
4574
  try {
4321
- unlinkSync4(path14.join(WORKER_PID_DIR, f));
4575
+ unlinkSync4(path15.join(WORKER_PID_DIR, f));
4322
4576
  } catch {
4323
4577
  }
4324
4578
  }
@@ -4342,20 +4596,20 @@ function tryAcquireWorkerSlot() {
4342
4596
  function registerWorkerPid(pid) {
4343
4597
  try {
4344
4598
  mkdirSync6(WORKER_PID_DIR, { recursive: true });
4345
- writeFileSync6(path14.join(WORKER_PID_DIR, `worker-${pid}.pid`), String(pid));
4599
+ writeFileSync7(path15.join(WORKER_PID_DIR, `worker-${pid}.pid`), String(pid));
4346
4600
  } catch {
4347
4601
  }
4348
4602
  }
4349
4603
  function cleanupWorkerPid() {
4350
4604
  try {
4351
- unlinkSync4(path14.join(WORKER_PID_DIR, `worker-${process.pid}.pid`));
4605
+ unlinkSync4(path15.join(WORKER_PID_DIR, `worker-${process.pid}.pid`));
4352
4606
  } catch {
4353
4607
  }
4354
4608
  }
4355
4609
  function tryAcquireBackfillLock() {
4356
4610
  try {
4357
4611
  mkdirSync6(WORKER_PID_DIR, { recursive: true });
4358
- if (existsSync12(BACKFILL_LOCK)) {
4612
+ if (existsSync14(BACKFILL_LOCK)) {
4359
4613
  try {
4360
4614
  const pid = parseInt(
4361
4615
  __require("fs").readFileSync(BACKFILL_LOCK, "utf8").trim(),
@@ -4371,7 +4625,7 @@ function tryAcquireBackfillLock() {
4371
4625
  } catch {
4372
4626
  }
4373
4627
  }
4374
- writeFileSync6(BACKFILL_LOCK, String(process.pid));
4628
+ writeFileSync7(BACKFILL_LOCK, String(process.pid));
4375
4629
  return true;
4376
4630
  } catch {
4377
4631
  return true;
@@ -4388,9 +4642,9 @@ var init_worker_gate = __esm({
4388
4642
  "src/lib/worker-gate.ts"() {
4389
4643
  "use strict";
4390
4644
  init_config();
4391
- WORKER_PID_DIR = path14.join(EXE_AI_DIR, "worker-pids");
4645
+ WORKER_PID_DIR = path15.join(EXE_AI_DIR, "worker-pids");
4392
4646
  MAX_CONCURRENT_WORKERS = 3;
4393
- BACKFILL_LOCK = path14.join(WORKER_PID_DIR, "backfill.lock");
4647
+ BACKFILL_LOCK = path15.join(WORKER_PID_DIR, "backfill.lock");
4394
4648
  }
4395
4649
  });
4396
4650
 
@@ -4402,13 +4656,13 @@ __export(crypto_exports, {
4402
4656
  initSyncCrypto: () => initSyncCrypto,
4403
4657
  isSyncCryptoInitialized: () => isSyncCryptoInitialized
4404
4658
  });
4405
- import crypto2 from "crypto";
4659
+ import crypto3 from "crypto";
4406
4660
  function initSyncCrypto(masterKey) {
4407
4661
  if (masterKey.length !== 32) {
4408
4662
  throw new Error(`Master key must be 32 bytes, got ${masterKey.length}`);
4409
4663
  }
4410
4664
  _syncKey = Buffer.from(
4411
- crypto2.hkdfSync("sha256", masterKey, "", SYNC_HKDF_INFO, 32)
4665
+ crypto3.hkdfSync("sha256", masterKey, "", SYNC_HKDF_INFO, 32)
4412
4666
  );
4413
4667
  }
4414
4668
  function isSyncCryptoInitialized() {
@@ -4422,8 +4676,8 @@ function requireSyncKey() {
4422
4676
  }
4423
4677
  function encryptSyncBlob(data) {
4424
4678
  const key = requireSyncKey();
4425
- const iv = crypto2.randomBytes(IV_LENGTH);
4426
- const cipher = crypto2.createCipheriv(ALGORITHM, key, iv);
4679
+ const iv = crypto3.randomBytes(IV_LENGTH);
4680
+ const cipher = crypto3.createCipheriv(ALGORITHM, key, iv);
4427
4681
  const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
4428
4682
  const tag = cipher.getAuthTag();
4429
4683
  return Buffer.concat([iv, encrypted, tag]).toString("base64");
@@ -4437,7 +4691,7 @@ function decryptSyncBlob(ciphertext) {
4437
4691
  const iv = combined.subarray(0, IV_LENGTH);
4438
4692
  const tag = combined.subarray(combined.length - TAG_LENGTH);
4439
4693
  const encrypted = combined.subarray(IV_LENGTH, combined.length - TAG_LENGTH);
4440
- const decipher = crypto2.createDecipheriv(ALGORITHM, key, iv);
4694
+ const decipher = crypto3.createDecipheriv(ALGORITHM, key, iv);
4441
4695
  decipher.setAuthTag(tag);
4442
4696
  return Buffer.concat([decipher.update(encrypted), decipher.final()]);
4443
4697
  }
@@ -4492,8 +4746,8 @@ __export(crdt_sync_exports, {
4492
4746
  rebuildFromDb: () => rebuildFromDb
4493
4747
  });
4494
4748
  import * as Y from "yjs";
4495
- import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, existsSync as existsSync13, mkdirSync as mkdirSync7, unlinkSync as unlinkSync5 } from "fs";
4496
- import path15 from "path";
4749
+ import { readFileSync as readFileSync11, writeFileSync as writeFileSync8, existsSync as existsSync15, mkdirSync as mkdirSync7, unlinkSync as unlinkSync5 } from "fs";
4750
+ import path16 from "path";
4497
4751
  import { homedir } from "os";
4498
4752
  function getStatePath() {
4499
4753
  return _statePathOverride ?? DEFAULT_STATE_PATH;
@@ -4505,9 +4759,9 @@ function initCrdtDoc() {
4505
4759
  if (doc) return doc;
4506
4760
  doc = new Y.Doc();
4507
4761
  const sp = getStatePath();
4508
- if (existsSync13(sp)) {
4762
+ if (existsSync15(sp)) {
4509
4763
  try {
4510
- const state = readFileSync10(sp);
4764
+ const state = readFileSync11(sp);
4511
4765
  Y.applyUpdate(doc, new Uint8Array(state));
4512
4766
  } catch {
4513
4767
  console.warn("[crdt-sync] WARN: corrupted state file, rebuilding from DB");
@@ -4649,10 +4903,10 @@ function persistState() {
4649
4903
  if (!doc) return;
4650
4904
  try {
4651
4905
  const sp = getStatePath();
4652
- const dir = path15.dirname(sp);
4653
- if (!existsSync13(dir)) mkdirSync7(dir, { recursive: true });
4906
+ const dir = path16.dirname(sp);
4907
+ if (!existsSync15(dir)) mkdirSync7(dir, { recursive: true });
4654
4908
  const state = Y.encodeStateAsUpdate(doc);
4655
- writeFileSync7(sp, Buffer.from(state));
4909
+ writeFileSync8(sp, Buffer.from(state));
4656
4910
  } catch {
4657
4911
  }
4658
4912
  }
@@ -4693,7 +4947,7 @@ var DEFAULT_STATE_PATH, _statePathOverride, doc;
4693
4947
  var init_crdt_sync = __esm({
4694
4948
  "src/lib/crdt-sync.ts"() {
4695
4949
  "use strict";
4696
- DEFAULT_STATE_PATH = path15.join(homedir(), ".exe-os", "crdt-state.bin");
4950
+ DEFAULT_STATE_PATH = path16.join(homedir(), ".exe-os", "crdt-state.bin");
4697
4951
  _statePathOverride = null;
4698
4952
  doc = null;
4699
4953
  }
@@ -4725,39 +4979,107 @@ __export(cloud_sync_exports, {
4725
4979
  cloudSync: () => cloudSync,
4726
4980
  mergeConfig: () => mergeConfig,
4727
4981
  mergeRosterFromRemote: () => mergeRosterFromRemote,
4982
+ pushToPostgres: () => pushToPostgres,
4728
4983
  recordRosterDeletion: () => recordRosterDeletion
4729
4984
  });
4730
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync8, existsSync as existsSync14, readdirSync as readdirSync5, mkdirSync as mkdirSync8, appendFileSync as appendFileSync2, unlinkSync as unlinkSync6, openSync as openSync2, closeSync as closeSync2 } from "fs";
4731
- import crypto3 from "crypto";
4732
- import path16 from "path";
4985
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync9, existsSync as existsSync16, readdirSync as readdirSync5, mkdirSync as mkdirSync8, appendFileSync as appendFileSync2, unlinkSync as unlinkSync6, openSync as openSync2, closeSync as closeSync2 } from "fs";
4986
+ import crypto4 from "crypto";
4987
+ import path17 from "path";
4733
4988
  import { homedir as homedir2 } from "os";
4734
4989
  function sqlSafe(v) {
4735
4990
  return v === void 0 ? null : v;
4736
4991
  }
4737
4992
  function logError(msg) {
4738
4993
  try {
4739
- const logPath = path16.join(homedir2(), ".exe-os", "workers.log");
4994
+ const logPath = path17.join(homedir2(), ".exe-os", "workers.log");
4740
4995
  appendFileSync2(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
4741
4996
  `);
4742
4997
  } catch {
4743
4998
  }
4744
4999
  }
5000
+ function loadPgClient() {
5001
+ if (_pgFailed) return null;
5002
+ const postgresUrl = process.env.DATABASE_URL;
5003
+ const configPath = path17.join(EXE_AI_DIR, "config.json");
5004
+ let cloudPostgresUrl;
5005
+ try {
5006
+ if (existsSync16(configPath)) {
5007
+ const cfg = JSON.parse(readFileSync12(configPath, "utf8"));
5008
+ cloudPostgresUrl = cfg.cloud?.postgresUrl;
5009
+ if (cfg.cloud?.syncToPostgres === false) {
5010
+ _pgFailed = true;
5011
+ return null;
5012
+ }
5013
+ }
5014
+ } catch {
5015
+ }
5016
+ const url = postgresUrl || cloudPostgresUrl;
5017
+ if (!url) {
5018
+ _pgFailed = true;
5019
+ return null;
5020
+ }
5021
+ if (!_pgPromise) {
5022
+ _pgPromise = (async () => {
5023
+ const { createRequire: createRequire3 } = await import("module");
5024
+ const { pathToFileURL: pathToFileURL3 } = await import("url");
5025
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path17.join(homedir2(), "exe-db");
5026
+ const req = createRequire3(path17.join(exeDbRoot, "package.json"));
5027
+ const entry = req.resolve("@prisma/client");
5028
+ const mod = await import(pathToFileURL3(entry).href);
5029
+ const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
5030
+ if (!Ctor) throw new Error("No PrismaClient");
5031
+ return new Ctor();
5032
+ })().catch(() => {
5033
+ _pgFailed = true;
5034
+ _pgPromise = null;
5035
+ throw new Error("pg_unavailable");
5036
+ });
5037
+ }
5038
+ return _pgPromise;
5039
+ }
5040
+ async function pushToPostgres(records) {
5041
+ const loader = loadPgClient();
5042
+ if (!loader) return 0;
5043
+ let prisma;
5044
+ try {
5045
+ prisma = await loader;
5046
+ } catch {
5047
+ return 0;
5048
+ }
5049
+ let inserted = 0;
5050
+ for (const rec of records) {
5051
+ try {
5052
+ await prisma.$executeRawUnsafe(
5053
+ `INSERT INTO raw.raw_events (id, source, source_id, event_type, payload, metadata, timestamp)
5054
+ VALUES (gen_random_uuid(), 'cloud_sync', $1, 'memory', $2::jsonb, $3::jsonb, $4)
5055
+ ON CONFLICT (source, source_id, event_type) DO NOTHING`,
5056
+ String(rec.id ?? ""),
5057
+ JSON.stringify(rec),
5058
+ JSON.stringify({ agent_id: rec.agent_id, project_name: rec.project_name, tool_name: rec.tool_name }),
5059
+ rec.timestamp ? new Date(String(rec.timestamp)) : /* @__PURE__ */ new Date()
5060
+ );
5061
+ inserted++;
5062
+ } catch {
5063
+ }
5064
+ }
5065
+ return inserted;
5066
+ }
4745
5067
  async function withRosterLock(fn) {
4746
5068
  try {
4747
5069
  const fd = openSync2(ROSTER_LOCK_PATH, "wx");
4748
5070
  closeSync2(fd);
4749
- writeFileSync8(ROSTER_LOCK_PATH, String(Date.now()));
5071
+ writeFileSync9(ROSTER_LOCK_PATH, String(Date.now()));
4750
5072
  } catch (err) {
4751
5073
  if (err.code === "EEXIST") {
4752
5074
  try {
4753
- const ts = parseInt(readFileSync11(ROSTER_LOCK_PATH, "utf-8"), 10);
5075
+ const ts = parseInt(readFileSync12(ROSTER_LOCK_PATH, "utf-8"), 10);
4754
5076
  if (Date.now() - ts < LOCK_STALE_MS) {
4755
5077
  throw new Error("Roster merge already in progress \u2014 another sync is running");
4756
5078
  }
4757
5079
  unlinkSync6(ROSTER_LOCK_PATH);
4758
5080
  const fd = openSync2(ROSTER_LOCK_PATH, "wx");
4759
5081
  closeSync2(fd);
4760
- writeFileSync8(ROSTER_LOCK_PATH, String(Date.now()));
5082
+ writeFileSync9(ROSTER_LOCK_PATH, String(Date.now()));
4761
5083
  } catch (retryErr) {
4762
5084
  if (retryErr instanceof Error && retryErr.message.includes("already in progress")) throw retryErr;
4763
5085
  throw new Error("Roster merge already in progress \u2014 another sync is running");
@@ -5027,6 +5349,10 @@ async function cloudSync(config) {
5027
5349
  const maxVersion = Number(records[records.length - 1].version);
5028
5350
  const pushOk = await cloudPush(records, maxVersion, config);
5029
5351
  if (!pushOk) break;
5352
+ try {
5353
+ await pushToPostgres(records);
5354
+ } catch {
5355
+ }
5030
5356
  await client.execute({
5031
5357
  sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_cloud_push_version', ?)",
5032
5358
  args: [String(maxVersion)]
@@ -5131,8 +5457,8 @@ async function cloudSync(config) {
5131
5457
  try {
5132
5458
  const employees = await loadEmployees();
5133
5459
  rosterResult.employees = employees.length;
5134
- const idDir = path16.join(EXE_AI_DIR, "identity");
5135
- if (existsSync14(idDir)) {
5460
+ const idDir = path17.join(EXE_AI_DIR, "identity");
5461
+ if (existsSync16(idDir)) {
5136
5462
  rosterResult.identities = readdirSync5(idDir).filter((f) => f.endsWith(".md")).length;
5137
5463
  }
5138
5464
  } catch {
@@ -5153,62 +5479,62 @@ async function cloudSync(config) {
5153
5479
  function recordRosterDeletion(name) {
5154
5480
  let deletions = [];
5155
5481
  try {
5156
- if (existsSync14(ROSTER_DELETIONS_PATH)) {
5157
- deletions = JSON.parse(readFileSync11(ROSTER_DELETIONS_PATH, "utf-8"));
5482
+ if (existsSync16(ROSTER_DELETIONS_PATH)) {
5483
+ deletions = JSON.parse(readFileSync12(ROSTER_DELETIONS_PATH, "utf-8"));
5158
5484
  }
5159
5485
  } catch {
5160
5486
  }
5161
5487
  if (!deletions.includes(name)) deletions.push(name);
5162
- writeFileSync8(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
5488
+ writeFileSync9(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
5163
5489
  }
5164
5490
  function consumeRosterDeletions() {
5165
5491
  try {
5166
- if (!existsSync14(ROSTER_DELETIONS_PATH)) return [];
5167
- const deletions = JSON.parse(readFileSync11(ROSTER_DELETIONS_PATH, "utf-8"));
5168
- writeFileSync8(ROSTER_DELETIONS_PATH, "[]");
5492
+ if (!existsSync16(ROSTER_DELETIONS_PATH)) return [];
5493
+ const deletions = JSON.parse(readFileSync12(ROSTER_DELETIONS_PATH, "utf-8"));
5494
+ writeFileSync9(ROSTER_DELETIONS_PATH, "[]");
5169
5495
  return deletions;
5170
5496
  } catch {
5171
5497
  return [];
5172
5498
  }
5173
5499
  }
5174
5500
  function buildRosterBlob(paths) {
5175
- const rosterPath = paths?.rosterPath ?? path16.join(EXE_AI_DIR, "exe-employees.json");
5176
- const identityDir = paths?.identityDir ?? path16.join(EXE_AI_DIR, "identity");
5177
- const configPath = paths?.configPath ?? path16.join(EXE_AI_DIR, "config.json");
5501
+ const rosterPath = paths?.rosterPath ?? path17.join(EXE_AI_DIR, "exe-employees.json");
5502
+ const identityDir = paths?.identityDir ?? path17.join(EXE_AI_DIR, "identity");
5503
+ const configPath = paths?.configPath ?? path17.join(EXE_AI_DIR, "config.json");
5178
5504
  let roster = [];
5179
- if (existsSync14(rosterPath)) {
5505
+ if (existsSync16(rosterPath)) {
5180
5506
  try {
5181
- roster = JSON.parse(readFileSync11(rosterPath, "utf-8"));
5507
+ roster = JSON.parse(readFileSync12(rosterPath, "utf-8"));
5182
5508
  } catch {
5183
5509
  }
5184
5510
  }
5185
5511
  const identities = {};
5186
- if (existsSync14(identityDir)) {
5512
+ if (existsSync16(identityDir)) {
5187
5513
  for (const file of readdirSync5(identityDir).filter((f) => f.endsWith(".md"))) {
5188
5514
  try {
5189
- identities[file] = readFileSync11(path16.join(identityDir, file), "utf-8");
5515
+ identities[file] = readFileSync12(path17.join(identityDir, file), "utf-8");
5190
5516
  } catch {
5191
5517
  }
5192
5518
  }
5193
5519
  }
5194
5520
  let config;
5195
- if (existsSync14(configPath)) {
5521
+ if (existsSync16(configPath)) {
5196
5522
  try {
5197
- config = JSON.parse(readFileSync11(configPath, "utf-8"));
5523
+ config = JSON.parse(readFileSync12(configPath, "utf-8"));
5198
5524
  } catch {
5199
5525
  }
5200
5526
  }
5201
5527
  let agentConfig;
5202
- const agentConfigPath = path16.join(EXE_AI_DIR, "agent-config.json");
5203
- if (existsSync14(agentConfigPath)) {
5528
+ const agentConfigPath = path17.join(EXE_AI_DIR, "agent-config.json");
5529
+ if (existsSync16(agentConfigPath)) {
5204
5530
  try {
5205
- agentConfig = JSON.parse(readFileSync11(agentConfigPath, "utf-8"));
5531
+ agentConfig = JSON.parse(readFileSync12(agentConfigPath, "utf-8"));
5206
5532
  } catch {
5207
5533
  }
5208
5534
  }
5209
5535
  const deletedNames = consumeRosterDeletions();
5210
5536
  const content = JSON.stringify({ roster, identities, config, agentConfig, deletedNames });
5211
- const hash = crypto3.createHash("sha256").update(content).digest("hex").slice(0, 16);
5537
+ const hash = crypto4.createHash("sha256").update(content).digest("hex").slice(0, 16);
5212
5538
  return { roster, identities, config, agentConfig, deletedNames, version: hash };
5213
5539
  }
5214
5540
  async function cloudPushRoster(config) {
@@ -5278,23 +5604,24 @@ async function cloudPullRoster(config) {
5278
5604
  }
5279
5605
  }
5280
5606
  function mergeConfig(remoteConfig, configPath) {
5281
- const cfgPath = configPath ?? path16.join(EXE_AI_DIR, "config.json");
5607
+ const cfgPath = configPath ?? path17.join(EXE_AI_DIR, "config.json");
5282
5608
  let local = {};
5283
- if (existsSync14(cfgPath)) {
5609
+ if (existsSync16(cfgPath)) {
5284
5610
  try {
5285
- local = JSON.parse(readFileSync11(cfgPath, "utf-8"));
5611
+ local = JSON.parse(readFileSync12(cfgPath, "utf-8"));
5286
5612
  } catch {
5287
5613
  }
5288
5614
  }
5289
5615
  const merged = { ...remoteConfig, ...local };
5290
- const dir = path16.dirname(cfgPath);
5291
- if (!existsSync14(dir)) mkdirSync8(dir, { recursive: true });
5292
- writeFileSync8(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
5616
+ const dir = path17.dirname(cfgPath);
5617
+ ensurePrivateDirSync(dir);
5618
+ writeFileSync9(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
5619
+ enforcePrivateFileSync(cfgPath);
5293
5620
  }
5294
5621
  async function mergeRosterFromRemote(remote, paths) {
5295
5622
  return withRosterLock(async () => {
5296
5623
  const rosterPath = paths?.rosterPath ?? void 0;
5297
- const identityDir = paths?.identityDir ?? path16.join(EXE_AI_DIR, "identity");
5624
+ const identityDir = paths?.identityDir ?? path17.join(EXE_AI_DIR, "identity");
5298
5625
  const localEmployees = await loadEmployees(rosterPath);
5299
5626
  const localNames = new Set(localEmployees.map((e) => e.name));
5300
5627
  let added = 0;
@@ -5315,15 +5642,15 @@ async function mergeRosterFromRemote(remote, paths) {
5315
5642
  ) ?? lookupKey;
5316
5643
  const remoteIdentity = remote.identities[matchedKey];
5317
5644
  if (remoteIdentity) {
5318
- if (!existsSync14(identityDir)) mkdirSync8(identityDir, { recursive: true });
5319
- const idPath = path16.join(identityDir, `${remoteEmp.name}.md`);
5645
+ if (!existsSync16(identityDir)) mkdirSync8(identityDir, { recursive: true });
5646
+ const idPath = path17.join(identityDir, `${remoteEmp.name}.md`);
5320
5647
  let localIdentity = null;
5321
5648
  try {
5322
- localIdentity = existsSync14(idPath) ? readFileSync11(idPath, "utf-8") : null;
5649
+ localIdentity = existsSync16(idPath) ? readFileSync12(idPath, "utf-8") : null;
5323
5650
  } catch {
5324
5651
  }
5325
5652
  if (localIdentity !== remoteIdentity) {
5326
- writeFileSync8(idPath, remoteIdentity, "utf-8");
5653
+ writeFileSync9(idPath, remoteIdentity, "utf-8");
5327
5654
  identitiesUpdated++;
5328
5655
  }
5329
5656
  }
@@ -5349,16 +5676,18 @@ async function mergeRosterFromRemote(remote, paths) {
5349
5676
  }
5350
5677
  if (remote.agentConfig && Object.keys(remote.agentConfig).length > 0) {
5351
5678
  try {
5352
- const agentConfigPath = path16.join(EXE_AI_DIR, "agent-config.json");
5679
+ const agentConfigPath = path17.join(EXE_AI_DIR, "agent-config.json");
5353
5680
  let local = {};
5354
- if (existsSync14(agentConfigPath)) {
5681
+ if (existsSync16(agentConfigPath)) {
5355
5682
  try {
5356
- local = JSON.parse(readFileSync11(agentConfigPath, "utf-8"));
5683
+ local = JSON.parse(readFileSync12(agentConfigPath, "utf-8"));
5357
5684
  } catch {
5358
5685
  }
5359
5686
  }
5360
5687
  const merged = { ...remote.agentConfig, ...local };
5361
- writeFileSync8(agentConfigPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
5688
+ ensurePrivateDirSync(path17.dirname(agentConfigPath));
5689
+ writeFileSync9(agentConfigPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
5690
+ enforcePrivateFileSync(agentConfigPath);
5362
5691
  } catch {
5363
5692
  }
5364
5693
  }
@@ -5782,7 +6111,7 @@ async function cloudPullDocuments(config) {
5782
6111
  }
5783
6112
  return { pulled };
5784
6113
  }
5785
- var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS, PUSH_BATCH_SIZE, ROSTER_LOCK_PATH, LOCK_STALE_MS, ROSTER_DELETIONS_PATH;
6114
+ var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS, PUSH_BATCH_SIZE, ROSTER_LOCK_PATH, LOCK_STALE_MS, _pgPromise, _pgFailed, ROSTER_DELETIONS_PATH;
5786
6115
  var init_cloud_sync = __esm({
5787
6116
  "src/lib/cloud-sync.ts"() {
5788
6117
  "use strict";
@@ -5793,12 +6122,15 @@ var init_cloud_sync = __esm({
5793
6122
  init_config();
5794
6123
  init_crdt_sync();
5795
6124
  init_employees();
6125
+ init_secure_files();
5796
6126
  LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
5797
6127
  FETCH_TIMEOUT_MS = 3e4;
5798
6128
  PUSH_BATCH_SIZE = 5e3;
5799
- ROSTER_LOCK_PATH = path16.join(EXE_AI_DIR, "roster-merge.lock");
6129
+ ROSTER_LOCK_PATH = path17.join(EXE_AI_DIR, "roster-merge.lock");
5800
6130
  LOCK_STALE_MS = 3e4;
5801
- ROSTER_DELETIONS_PATH = path16.join(EXE_AI_DIR, "roster-deletions.json");
6131
+ _pgPromise = null;
6132
+ _pgFailed = false;
6133
+ ROSTER_DELETIONS_PATH = path17.join(EXE_AI_DIR, "roster-deletions.json");
5802
6134
  }
5803
6135
  });
5804
6136
 
@@ -6165,10 +6497,10 @@ init_database();
6165
6497
  init_notifications();
6166
6498
  init_task_scope();
6167
6499
  init_employees();
6168
- import crypto4 from "crypto";
6500
+ import crypto5 from "crypto";
6169
6501
  import { execSync as execSync4 } from "child_process";
6170
- import { existsSync as existsSync15, mkdirSync as mkdirSync9, openSync as openSync3, closeSync as closeSync3 } from "fs";
6171
- import path17 from "path";
6502
+ import { existsSync as existsSync17, mkdirSync as mkdirSync9, openSync as openSync3, closeSync as closeSync3 } from "fs";
6503
+ import path18 from "path";
6172
6504
  async function main() {
6173
6505
  const agentId = process.env.AGENT_ID ?? "default";
6174
6506
  const agentRole = process.env.AGENT_ROLE ?? "employee";
@@ -6248,7 +6580,7 @@ async function main() {
6248
6580
  if (limitReached) {
6249
6581
  } else {
6250
6582
  await writeMemory({
6251
- id: crypto4.randomUUID(),
6583
+ id: crypto5.randomUUID(),
6252
6584
  agent_id: agentId,
6253
6585
  agent_role: agentRole,
6254
6586
  session_id: `auto-summary-${Date.now()}`,
@@ -6303,8 +6635,8 @@ async function main() {
6303
6635
  }
6304
6636
  try {
6305
6637
  const { EXE_AI_DIR: EXE_AI_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
6306
- const flagPath = path17.join(EXE_AI_DIR2, "session-cache", "needs-backfill");
6307
- if (existsSync15(flagPath)) {
6638
+ const flagPath = path18.join(EXE_AI_DIR2, "session-cache", "needs-backfill");
6639
+ if (existsSync17(flagPath)) {
6308
6640
  const { tryAcquireWorkerSlot: tryAcquireWorkerSlot2, registerWorkerPid: registerWorkerPid2 } = await Promise.resolve().then(() => (init_worker_gate(), worker_gate_exports));
6309
6641
  if (!tryAcquireWorkerSlot2()) {
6310
6642
  process.stderr.write("[summary-worker] Backfill needed but worker gate full \u2014 skipping\n");
@@ -6312,11 +6644,11 @@ async function main() {
6312
6644
  const { spawn: spawn2 } = await import("child_process");
6313
6645
  const { fileURLToPath: fileURLToPath3 } = await import("url");
6314
6646
  const thisFile = fileURLToPath3(import.meta.url);
6315
- const backfillPath = path17.resolve(path17.dirname(thisFile), "backfill-vectors.js");
6316
- if (existsSync15(backfillPath)) {
6647
+ const backfillPath = path18.resolve(path18.dirname(thisFile), "backfill-vectors.js");
6648
+ if (existsSync17(backfillPath)) {
6317
6649
  const { EXE_AI_DIR: exeDir2 } = await Promise.resolve().then(() => (init_config(), config_exports));
6318
- const bLogPath = path17.join(exeDir2, "workers.log");
6319
- mkdirSync9(path17.dirname(bLogPath), { recursive: true });
6650
+ const bLogPath = path18.join(exeDir2, "workers.log");
6651
+ mkdirSync9(path18.dirname(bLogPath), { recursive: true });
6320
6652
  const bLogFd = openSync3(bLogPath, "a");
6321
6653
  const child = spawn2(process.execPath, [backfillPath], {
6322
6654
  detached: true,