@askexenow/exe-os 0.9.8 → 0.9.10

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 +1411 -953
  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 +913 -543
  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 +418 -262
  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 +793 -485
  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 +566 -357
  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 +530 -319
  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 +547 -336
  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 +649 -417
  44. package/dist/hooks/bug-report-worker.js +486 -316
  45. package/dist/hooks/codex-stop-task-finalizer.js +4678 -0
  46. package/dist/hooks/commit-complete.js +528 -317
  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 +3442 -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 +534 -323
  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 +614 -382
  58. package/dist/hooks/response-ingest-worker.js +372 -122
  59. package/dist/hooks/session-end.js +569 -347
  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 +664 -431
  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 +1049 -680
  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 +422 -357
  88. package/dist/lib/tmux-routing.js +314 -248
  89. package/dist/lib/token-spend.js +26 -8
  90. package/dist/mcp/server.js +1408 -672
  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 +448 -371
  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 +1983 -315
  99. package/dist/runtime/index.js +567 -355
  100. package/dist/tui/App.js +887 -531
  101. package/package.json +4 -4
@@ -26,6 +26,44 @@ var __copyProps = (to, from, except, desc) => {
26
26
  };
27
27
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
28
 
29
+ // src/lib/secure-files.ts
30
+ import { chmodSync, existsSync, mkdirSync } from "fs";
31
+ import { chmod, mkdir } from "fs/promises";
32
+ async function ensurePrivateDir(dirPath) {
33
+ await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
34
+ try {
35
+ await chmod(dirPath, PRIVATE_DIR_MODE);
36
+ } catch {
37
+ }
38
+ }
39
+ function ensurePrivateDirSync(dirPath) {
40
+ mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
41
+ try {
42
+ chmodSync(dirPath, PRIVATE_DIR_MODE);
43
+ } catch {
44
+ }
45
+ }
46
+ async function enforcePrivateFile(filePath) {
47
+ try {
48
+ await chmod(filePath, PRIVATE_FILE_MODE);
49
+ } catch {
50
+ }
51
+ }
52
+ function enforcePrivateFileSync(filePath) {
53
+ try {
54
+ if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
55
+ } catch {
56
+ }
57
+ }
58
+ var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
59
+ var init_secure_files = __esm({
60
+ "src/lib/secure-files.ts"() {
61
+ "use strict";
62
+ PRIVATE_DIR_MODE = 448;
63
+ PRIVATE_FILE_MODE = 384;
64
+ }
65
+ });
66
+
29
67
  // src/lib/config.ts
30
68
  var config_exports = {};
31
69
  __export(config_exports, {
@@ -42,8 +80,8 @@ __export(config_exports, {
42
80
  migrateConfig: () => migrateConfig,
43
81
  saveConfig: () => saveConfig
44
82
  });
45
- import { readFile, writeFile, mkdir, chmod } from "fs/promises";
46
- import { readFileSync, existsSync, renameSync } from "fs";
83
+ import { readFile, writeFile } from "fs/promises";
84
+ import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
47
85
  import path from "path";
48
86
  import os from "os";
49
87
  function resolveDataDir() {
@@ -51,7 +89,7 @@ function resolveDataDir() {
51
89
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
52
90
  const newDir = path.join(os.homedir(), ".exe-os");
53
91
  const legacyDir = path.join(os.homedir(), ".exe-mem");
54
- if (!existsSync(newDir) && existsSync(legacyDir)) {
92
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
55
93
  try {
56
94
  renameSync(legacyDir, newDir);
57
95
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -114,9 +152,9 @@ function normalizeAutoUpdate(raw) {
114
152
  }
115
153
  async function loadConfig() {
116
154
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
117
- await mkdir(dir, { recursive: true });
155
+ await ensurePrivateDir(dir);
118
156
  const configPath = path.join(dir, "config.json");
119
- if (!existsSync(configPath)) {
157
+ if (!existsSync2(configPath)) {
120
158
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
121
159
  }
122
160
  const raw = await readFile(configPath, "utf-8");
@@ -129,6 +167,7 @@ async function loadConfig() {
129
167
  `);
130
168
  try {
131
169
  await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
170
+ await enforcePrivateFile(configPath);
132
171
  } catch {
133
172
  }
134
173
  }
@@ -147,7 +186,7 @@ async function loadConfig() {
147
186
  function loadConfigSync() {
148
187
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
149
188
  const configPath = path.join(dir, "config.json");
150
- if (!existsSync(configPath)) {
189
+ if (!existsSync2(configPath)) {
151
190
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
152
191
  }
153
192
  try {
@@ -165,12 +204,10 @@ function loadConfigSync() {
165
204
  }
166
205
  async function saveConfig(config) {
167
206
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
168
- await mkdir(dir, { recursive: true });
207
+ await ensurePrivateDir(dir);
169
208
  const configPath = path.join(dir, "config.json");
170
209
  await writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
171
- if (config.cloud?.apiKey) {
172
- await chmod(configPath, 384);
173
- }
210
+ await enforcePrivateFile(configPath);
174
211
  }
175
212
  async function loadConfigFrom(configPath) {
176
213
  const raw = await readFile(configPath, "utf-8");
@@ -190,6 +227,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
190
227
  var init_config = __esm({
191
228
  "src/lib/config.ts"() {
192
229
  "use strict";
230
+ init_secure_files();
193
231
  EXE_AI_DIR = resolveDataDir();
194
232
  DB_PATH = path.join(EXE_AI_DIR, "memories.db");
195
233
  MODELS_DIR = path.join(EXE_AI_DIR, "models");
@@ -268,7 +306,7 @@ var init_config = __esm({
268
306
 
269
307
  // src/lib/employees.ts
270
308
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
271
- import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
309
+ import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
272
310
  import { execSync } from "child_process";
273
311
  import path2 from "path";
274
312
  import os2 from "os";
@@ -289,7 +327,7 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
289
327
  return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
290
328
  }
291
329
  async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
292
- if (!existsSync2(employeesPath)) {
330
+ if (!existsSync3(employeesPath)) {
293
331
  return [];
294
332
  }
295
333
  const raw = await readFile2(employeesPath, "utf-8");
@@ -304,7 +342,7 @@ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
304
342
  await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
305
343
  }
306
344
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
307
- if (!existsSync2(employeesPath)) return [];
345
+ if (!existsSync3(employeesPath)) return [];
308
346
  try {
309
347
  return JSON.parse(readFileSync2(employeesPath, "utf-8"));
310
348
  } catch {
@@ -355,7 +393,7 @@ function registerBinSymlinks(name) {
355
393
  for (const suffix of ["", "-opencode"]) {
356
394
  const linkName = `${name}${suffix}`;
357
395
  const linkPath = path2.join(binDir, linkName);
358
- if (existsSync2(linkPath)) {
396
+ if (existsSync3(linkPath)) {
359
397
  skipped.push(linkName);
360
398
  continue;
361
399
  }
@@ -1020,13 +1058,50 @@ var init_database_adapter = __esm({
1020
1058
  }
1021
1059
  });
1022
1060
 
1061
+ // src/lib/daemon-auth.ts
1062
+ import crypto from "crypto";
1063
+ import path4 from "path";
1064
+ import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
1065
+ function normalizeToken(token) {
1066
+ if (!token) return null;
1067
+ const trimmed = token.trim();
1068
+ return trimmed.length > 0 ? trimmed : null;
1069
+ }
1070
+ function readDaemonToken() {
1071
+ try {
1072
+ if (!existsSync4(DAEMON_TOKEN_PATH)) return null;
1073
+ return normalizeToken(readFileSync3(DAEMON_TOKEN_PATH, "utf8"));
1074
+ } catch {
1075
+ return null;
1076
+ }
1077
+ }
1078
+ function ensureDaemonToken(seed) {
1079
+ const existing = readDaemonToken();
1080
+ if (existing) return existing;
1081
+ const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
1082
+ ensurePrivateDirSync(EXE_AI_DIR);
1083
+ writeFileSync2(DAEMON_TOKEN_PATH, `${token}
1084
+ `, "utf8");
1085
+ enforcePrivateFileSync(DAEMON_TOKEN_PATH);
1086
+ return token;
1087
+ }
1088
+ var DAEMON_TOKEN_PATH;
1089
+ var init_daemon_auth = __esm({
1090
+ "src/lib/daemon-auth.ts"() {
1091
+ "use strict";
1092
+ init_config();
1093
+ init_secure_files();
1094
+ DAEMON_TOKEN_PATH = path4.join(EXE_AI_DIR, "exed.token");
1095
+ }
1096
+ });
1097
+
1023
1098
  // src/lib/exe-daemon-client.ts
1024
1099
  import net from "net";
1025
1100
  import os4 from "os";
1026
1101
  import { spawn } from "child_process";
1027
1102
  import { randomUUID } from "crypto";
1028
- import { existsSync as existsSync3, unlinkSync as unlinkSync2, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
1029
- import path4 from "path";
1103
+ import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
1104
+ import path5 from "path";
1030
1105
  import { fileURLToPath } from "url";
1031
1106
  function handleData(chunk) {
1032
1107
  _buffer += chunk.toString();
@@ -1054,9 +1129,9 @@ function handleData(chunk) {
1054
1129
  }
1055
1130
  }
1056
1131
  function cleanupStaleFiles() {
1057
- if (existsSync3(PID_PATH)) {
1132
+ if (existsSync5(PID_PATH)) {
1058
1133
  try {
1059
- const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
1134
+ const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
1060
1135
  if (pid > 0) {
1061
1136
  try {
1062
1137
  process.kill(pid, 0);
@@ -1077,11 +1152,11 @@ function cleanupStaleFiles() {
1077
1152
  }
1078
1153
  }
1079
1154
  function findPackageRoot() {
1080
- let dir = path4.dirname(fileURLToPath(import.meta.url));
1081
- const { root } = path4.parse(dir);
1155
+ let dir = path5.dirname(fileURLToPath(import.meta.url));
1156
+ const { root } = path5.parse(dir);
1082
1157
  while (dir !== root) {
1083
- if (existsSync3(path4.join(dir, "package.json"))) return dir;
1084
- dir = path4.dirname(dir);
1158
+ if (existsSync5(path5.join(dir, "package.json"))) return dir;
1159
+ dir = path5.dirname(dir);
1085
1160
  }
1086
1161
  return null;
1087
1162
  }
@@ -1107,16 +1182,17 @@ function spawnDaemon() {
1107
1182
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
1108
1183
  return;
1109
1184
  }
1110
- const daemonPath = path4.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1111
- if (!existsSync3(daemonPath)) {
1185
+ const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1186
+ if (!existsSync5(daemonPath)) {
1112
1187
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
1113
1188
  `);
1114
1189
  return;
1115
1190
  }
1116
1191
  const resolvedPath = daemonPath;
1192
+ const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
1117
1193
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
1118
1194
  `);
1119
- const logPath = path4.join(path4.dirname(SOCKET_PATH), "exed.log");
1195
+ const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
1120
1196
  let stderrFd = "ignore";
1121
1197
  try {
1122
1198
  stderrFd = openSync(logPath, "a");
@@ -1134,7 +1210,8 @@ function spawnDaemon() {
1134
1210
  TMUX_PANE: void 0,
1135
1211
  // Prevents resolveExeSession() from scoping to one session
1136
1212
  EXE_DAEMON_SOCK: SOCKET_PATH,
1137
- EXE_DAEMON_PID: PID_PATH
1213
+ EXE_DAEMON_PID: PID_PATH,
1214
+ [DAEMON_TOKEN_ENV]: daemonToken
1138
1215
  }
1139
1216
  });
1140
1217
  child.unref();
@@ -1241,13 +1318,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
1241
1318
  return;
1242
1319
  }
1243
1320
  const id = randomUUID();
1321
+ const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
1244
1322
  const timer = setTimeout(() => {
1245
1323
  _pending.delete(id);
1246
1324
  resolve({ error: "Request timeout" });
1247
1325
  }, timeoutMs);
1248
1326
  _pending.set(id, { resolve, timer });
1249
1327
  try {
1250
- _socket.write(JSON.stringify({ id, ...payload }) + "\n");
1328
+ _socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
1251
1329
  } catch {
1252
1330
  clearTimeout(timer);
1253
1331
  _pending.delete(id);
@@ -1258,17 +1336,19 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
1258
1336
  function isClientConnected() {
1259
1337
  return _connected;
1260
1338
  }
1261
- var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
1339
+ 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;
1262
1340
  var init_exe_daemon_client = __esm({
1263
1341
  "src/lib/exe-daemon-client.ts"() {
1264
1342
  "use strict";
1265
1343
  init_config();
1266
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path4.join(EXE_AI_DIR, "exed.sock");
1267
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path4.join(EXE_AI_DIR, "exed.pid");
1268
- SPAWN_LOCK_PATH = path4.join(EXE_AI_DIR, "exed-spawn.lock");
1344
+ init_daemon_auth();
1345
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path5.join(EXE_AI_DIR, "exed.sock");
1346
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path5.join(EXE_AI_DIR, "exed.pid");
1347
+ SPAWN_LOCK_PATH = path5.join(EXE_AI_DIR, "exed-spawn.lock");
1269
1348
  SPAWN_LOCK_STALE_MS = 3e4;
1270
1349
  CONNECT_TIMEOUT_MS = 15e3;
1271
1350
  REQUEST_TIMEOUT_MS = 3e4;
1351
+ DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
1272
1352
  _socket = null;
1273
1353
  _connected = false;
1274
1354
  _buffer = "";
@@ -1847,6 +1927,7 @@ async function ensureSchema() {
1847
1927
  project TEXT NOT NULL,
1848
1928
  summary TEXT NOT NULL,
1849
1929
  task_file TEXT,
1930
+ session_scope TEXT,
1850
1931
  read INTEGER NOT NULL DEFAULT 0,
1851
1932
  created_at TEXT NOT NULL
1852
1933
  );
@@ -1855,7 +1936,7 @@ async function ensureSchema() {
1855
1936
  ON notifications(read);
1856
1937
 
1857
1938
  CREATE INDEX IF NOT EXISTS idx_notifications_agent
1858
- ON notifications(agent_id);
1939
+ ON notifications(agent_id, session_scope);
1859
1940
 
1860
1941
  CREATE INDEX IF NOT EXISTS idx_notifications_task_file
1861
1942
  ON notifications(task_file);
@@ -1893,6 +1974,7 @@ async function ensureSchema() {
1893
1974
  target_agent TEXT NOT NULL,
1894
1975
  target_project TEXT,
1895
1976
  target_device TEXT NOT NULL DEFAULT 'local',
1977
+ session_scope TEXT,
1896
1978
  content TEXT NOT NULL,
1897
1979
  priority TEXT DEFAULT 'normal',
1898
1980
  status TEXT DEFAULT 'pending',
@@ -1906,10 +1988,31 @@ async function ensureSchema() {
1906
1988
  );
1907
1989
 
1908
1990
  CREATE INDEX IF NOT EXISTS idx_messages_target
1909
- ON messages(target_agent, status);
1991
+ ON messages(target_agent, session_scope, status);
1910
1992
 
1911
1993
  CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
1912
- ON messages(target_agent, from_agent, server_seq);
1994
+ ON messages(target_agent, session_scope, from_agent, server_seq);
1995
+ `);
1996
+ try {
1997
+ await client.execute({
1998
+ sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
1999
+ args: []
2000
+ });
2001
+ } catch {
2002
+ }
2003
+ try {
2004
+ await client.execute({
2005
+ sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
2006
+ args: []
2007
+ });
2008
+ } catch {
2009
+ }
2010
+ await client.executeMultiple(`
2011
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
2012
+ ON notifications(agent_id, session_scope, read, created_at);
2013
+
2014
+ CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
2015
+ ON messages(target_agent, session_scope, status, created_at);
1913
2016
  `);
1914
2017
  try {
1915
2018
  await client.execute({
@@ -2493,6 +2596,13 @@ async function ensureSchema() {
2493
2596
  } catch {
2494
2597
  }
2495
2598
  }
2599
+ try {
2600
+ await client.execute({
2601
+ sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
2602
+ args: []
2603
+ });
2604
+ } catch {
2605
+ }
2496
2606
  }
2497
2607
  async function disposeDatabase() {
2498
2608
  if (_walCheckpointTimer) {
@@ -2734,14 +2844,14 @@ __export(keychain_exports, {
2734
2844
  setMasterKey: () => setMasterKey
2735
2845
  });
2736
2846
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
2737
- import { existsSync as existsSync4 } from "fs";
2738
- import path5 from "path";
2847
+ import { existsSync as existsSync6 } from "fs";
2848
+ import path6 from "path";
2739
2849
  import os5 from "os";
2740
2850
  function getKeyDir() {
2741
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path5.join(os5.homedir(), ".exe-os");
2851
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path6.join(os5.homedir(), ".exe-os");
2742
2852
  }
2743
2853
  function getKeyPath() {
2744
- return path5.join(getKeyDir(), "master.key");
2854
+ return path6.join(getKeyDir(), "master.key");
2745
2855
  }
2746
2856
  async function tryKeytar() {
2747
2857
  try {
@@ -2762,7 +2872,7 @@ async function getMasterKey() {
2762
2872
  }
2763
2873
  }
2764
2874
  const keyPath = getKeyPath();
2765
- if (!existsSync4(keyPath)) {
2875
+ if (!existsSync6(keyPath)) {
2766
2876
  process.stderr.write(
2767
2877
  `[keychain] Key not found at ${keyPath} (HOME=${os5.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
2768
2878
  `
@@ -2805,7 +2915,7 @@ async function deleteMasterKey() {
2805
2915
  }
2806
2916
  }
2807
2917
  const keyPath = getKeyPath();
2808
- if (existsSync4(keyPath)) {
2918
+ if (existsSync6(keyPath)) {
2809
2919
  await unlink(keyPath);
2810
2920
  }
2811
2921
  }
@@ -2907,6 +3017,7 @@ var shard_manager_exports = {};
2907
3017
  __export(shard_manager_exports, {
2908
3018
  disposeShards: () => disposeShards,
2909
3019
  ensureShardSchema: () => ensureShardSchema,
3020
+ getOpenShardCount: () => getOpenShardCount,
2910
3021
  getReadyShardClient: () => getReadyShardClient,
2911
3022
  getShardClient: () => getShardClient,
2912
3023
  getShardsDir: () => getShardsDir,
@@ -2915,15 +3026,18 @@ __export(shard_manager_exports, {
2915
3026
  listShards: () => listShards,
2916
3027
  shardExists: () => shardExists
2917
3028
  });
2918
- import path6 from "path";
2919
- import { existsSync as existsSync5, mkdirSync, readdirSync } from "fs";
3029
+ import path7 from "path";
3030
+ import { existsSync as existsSync7, mkdirSync as mkdirSync2, readdirSync } from "fs";
2920
3031
  import { createClient as createClient2 } from "@libsql/client";
2921
3032
  function initShardManager(encryptionKey) {
2922
3033
  _encryptionKey = encryptionKey;
2923
- if (!existsSync5(SHARDS_DIR)) {
2924
- mkdirSync(SHARDS_DIR, { recursive: true });
3034
+ if (!existsSync7(SHARDS_DIR)) {
3035
+ mkdirSync2(SHARDS_DIR, { recursive: true });
2925
3036
  }
2926
3037
  _shardingEnabled = true;
3038
+ if (_evictionTimer) clearInterval(_evictionTimer);
3039
+ _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
3040
+ _evictionTimer.unref();
2927
3041
  }
2928
3042
  function isShardingEnabled() {
2929
3043
  return _shardingEnabled;
@@ -2940,21 +3054,28 @@ function getShardClient(projectName) {
2940
3054
  throw new Error(`Invalid project name for shard: "${projectName}"`);
2941
3055
  }
2942
3056
  const cached = _shards.get(safeName);
2943
- if (cached) return cached;
2944
- const dbPath = path6.join(SHARDS_DIR, `${safeName}.db`);
3057
+ if (cached) {
3058
+ _shardLastAccess.set(safeName, Date.now());
3059
+ return cached;
3060
+ }
3061
+ while (_shards.size >= MAX_OPEN_SHARDS) {
3062
+ evictLRU();
3063
+ }
3064
+ const dbPath = path7.join(SHARDS_DIR, `${safeName}.db`);
2945
3065
  const client = createClient2({
2946
3066
  url: `file:${dbPath}`,
2947
3067
  encryptionKey: _encryptionKey
2948
3068
  });
2949
3069
  _shards.set(safeName, client);
3070
+ _shardLastAccess.set(safeName, Date.now());
2950
3071
  return client;
2951
3072
  }
2952
3073
  function shardExists(projectName) {
2953
3074
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
2954
- return existsSync5(path6.join(SHARDS_DIR, `${safeName}.db`));
3075
+ return existsSync7(path7.join(SHARDS_DIR, `${safeName}.db`));
2955
3076
  }
2956
3077
  function listShards() {
2957
- if (!existsSync5(SHARDS_DIR)) return [];
3078
+ if (!existsSync7(SHARDS_DIR)) return [];
2958
3079
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
2959
3080
  }
2960
3081
  async function ensureShardSchema(client) {
@@ -3006,6 +3127,8 @@ async function ensureShardSchema(client) {
3006
3127
  for (const col of [
3007
3128
  "ALTER TABLE memories ADD COLUMN task_id TEXT",
3008
3129
  "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
3130
+ "ALTER TABLE memories ADD COLUMN author_device_id TEXT",
3131
+ "ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
3009
3132
  "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
3010
3133
  "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
3011
3134
  "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
@@ -3143,21 +3266,69 @@ async function getReadyShardClient(projectName) {
3143
3266
  await ensureShardSchema(client);
3144
3267
  return client;
3145
3268
  }
3269
+ function evictLRU() {
3270
+ let oldest = null;
3271
+ let oldestTime = Infinity;
3272
+ for (const [name, time] of _shardLastAccess) {
3273
+ if (time < oldestTime) {
3274
+ oldestTime = time;
3275
+ oldest = name;
3276
+ }
3277
+ }
3278
+ if (oldest) {
3279
+ const client = _shards.get(oldest);
3280
+ if (client) {
3281
+ client.close();
3282
+ }
3283
+ _shards.delete(oldest);
3284
+ _shardLastAccess.delete(oldest);
3285
+ }
3286
+ }
3287
+ function evictIdleShards() {
3288
+ const now = Date.now();
3289
+ const toEvict = [];
3290
+ for (const [name, lastAccess] of _shardLastAccess) {
3291
+ if (now - lastAccess > SHARD_IDLE_MS) {
3292
+ toEvict.push(name);
3293
+ }
3294
+ }
3295
+ for (const name of toEvict) {
3296
+ const client = _shards.get(name);
3297
+ if (client) {
3298
+ client.close();
3299
+ }
3300
+ _shards.delete(name);
3301
+ _shardLastAccess.delete(name);
3302
+ }
3303
+ }
3304
+ function getOpenShardCount() {
3305
+ return _shards.size;
3306
+ }
3146
3307
  function disposeShards() {
3308
+ if (_evictionTimer) {
3309
+ clearInterval(_evictionTimer);
3310
+ _evictionTimer = null;
3311
+ }
3147
3312
  for (const [, client] of _shards) {
3148
3313
  client.close();
3149
3314
  }
3150
3315
  _shards.clear();
3316
+ _shardLastAccess.clear();
3151
3317
  _shardingEnabled = false;
3152
3318
  _encryptionKey = null;
3153
3319
  }
3154
- var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
3320
+ var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
3155
3321
  var init_shard_manager = __esm({
3156
3322
  "src/lib/shard-manager.ts"() {
3157
3323
  "use strict";
3158
3324
  init_config();
3159
- SHARDS_DIR = path6.join(EXE_AI_DIR, "shards");
3325
+ SHARDS_DIR = path7.join(EXE_AI_DIR, "shards");
3326
+ SHARD_IDLE_MS = 5 * 60 * 1e3;
3327
+ MAX_OPEN_SHARDS = 10;
3328
+ EVICTION_INTERVAL_MS = 60 * 1e3;
3160
3329
  _shards = /* @__PURE__ */ new Map();
3330
+ _shardLastAccess = /* @__PURE__ */ new Map();
3331
+ _evictionTimer = null;
3161
3332
  _encryptionKey = null;
3162
3333
  _shardingEnabled = false;
3163
3334
  }
@@ -3262,14 +3433,14 @@ __export(session_registry_exports, {
3262
3433
  pruneStaleSessions: () => pruneStaleSessions,
3263
3434
  registerSession: () => registerSession
3264
3435
  });
3265
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync6 } from "fs";
3436
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync8 } from "fs";
3266
3437
  import { execSync as execSync2 } from "child_process";
3267
- import path7 from "path";
3438
+ import path8 from "path";
3268
3439
  import os6 from "os";
3269
3440
  function registerSession(entry) {
3270
- const dir = path7.dirname(REGISTRY_PATH);
3271
- if (!existsSync6(dir)) {
3272
- mkdirSync2(dir, { recursive: true });
3441
+ const dir = path8.dirname(REGISTRY_PATH);
3442
+ if (!existsSync8(dir)) {
3443
+ mkdirSync3(dir, { recursive: true });
3273
3444
  }
3274
3445
  const sessions = listSessions();
3275
3446
  const idx = sessions.findIndex((s) => s.windowName === entry.windowName);
@@ -3278,11 +3449,11 @@ function registerSession(entry) {
3278
3449
  } else {
3279
3450
  sessions.push(entry);
3280
3451
  }
3281
- writeFileSync2(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
3452
+ writeFileSync3(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
3282
3453
  }
3283
3454
  function listSessions() {
3284
3455
  try {
3285
- const raw = readFileSync4(REGISTRY_PATH, "utf8");
3456
+ const raw = readFileSync5(REGISTRY_PATH, "utf8");
3286
3457
  return JSON.parse(raw);
3287
3458
  } catch {
3288
3459
  return [];
@@ -3303,7 +3474,7 @@ function pruneStaleSessions() {
3303
3474
  const alive = sessions.filter((s) => liveSet.has(s.windowName));
3304
3475
  const pruned = sessions.length - alive.length;
3305
3476
  if (pruned > 0) {
3306
- writeFileSync2(REGISTRY_PATH, JSON.stringify(alive, null, 2));
3477
+ writeFileSync3(REGISTRY_PATH, JSON.stringify(alive, null, 2));
3307
3478
  }
3308
3479
  return pruned;
3309
3480
  }
@@ -3311,7 +3482,7 @@ var REGISTRY_PATH;
3311
3482
  var init_session_registry = __esm({
3312
3483
  "src/lib/session-registry.ts"() {
3313
3484
  "use strict";
3314
- REGISTRY_PATH = path7.join(os6.homedir(), ".exe-os", "session-registry.json");
3485
+ REGISTRY_PATH = path8.join(os6.homedir(), ".exe-os", "session-registry.json");
3315
3486
  }
3316
3487
  });
3317
3488
 
@@ -3591,12 +3762,12 @@ var init_runtime_table = __esm({
3591
3762
  });
3592
3763
 
3593
3764
  // src/lib/agent-config.ts
3594
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync7, mkdirSync as mkdirSync3 } from "fs";
3595
- import path8 from "path";
3765
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync9 } from "fs";
3766
+ import path9 from "path";
3596
3767
  function loadAgentConfig() {
3597
- if (!existsSync7(AGENT_CONFIG_PATH)) return {};
3768
+ if (!existsSync9(AGENT_CONFIG_PATH)) return {};
3598
3769
  try {
3599
- return JSON.parse(readFileSync5(AGENT_CONFIG_PATH, "utf-8"));
3770
+ return JSON.parse(readFileSync6(AGENT_CONFIG_PATH, "utf-8"));
3600
3771
  } catch {
3601
3772
  return {};
3602
3773
  }
@@ -3615,7 +3786,8 @@ var init_agent_config = __esm({
3615
3786
  "use strict";
3616
3787
  init_config();
3617
3788
  init_runtime_table();
3618
- AGENT_CONFIG_PATH = path8.join(EXE_AI_DIR, "agent-config.json");
3789
+ init_secure_files();
3790
+ AGENT_CONFIG_PATH = path9.join(EXE_AI_DIR, "agent-config.json");
3619
3791
  DEFAULT_MODELS = {
3620
3792
  claude: "claude-opus-4",
3621
3793
  codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
@@ -3633,17 +3805,17 @@ __export(intercom_queue_exports, {
3633
3805
  queueIntercom: () => queueIntercom,
3634
3806
  readQueue: () => readQueue
3635
3807
  });
3636
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync8, mkdirSync as mkdirSync4 } from "fs";
3637
- import path9 from "path";
3808
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, renameSync as renameSync3, existsSync as existsSync10, mkdirSync as mkdirSync4 } from "fs";
3809
+ import path10 from "path";
3638
3810
  import os7 from "os";
3639
3811
  function ensureDir() {
3640
- const dir = path9.dirname(QUEUE_PATH);
3641
- if (!existsSync8(dir)) mkdirSync4(dir, { recursive: true });
3812
+ const dir = path10.dirname(QUEUE_PATH);
3813
+ if (!existsSync10(dir)) mkdirSync4(dir, { recursive: true });
3642
3814
  }
3643
3815
  function readQueue() {
3644
3816
  try {
3645
- if (!existsSync8(QUEUE_PATH)) return [];
3646
- return JSON.parse(readFileSync6(QUEUE_PATH, "utf8"));
3817
+ if (!existsSync10(QUEUE_PATH)) return [];
3818
+ return JSON.parse(readFileSync7(QUEUE_PATH, "utf8"));
3647
3819
  } catch {
3648
3820
  return [];
3649
3821
  }
@@ -3651,7 +3823,7 @@ function readQueue() {
3651
3823
  function writeQueue(queue) {
3652
3824
  ensureDir();
3653
3825
  const tmp = `${QUEUE_PATH}.tmp`;
3654
- writeFileSync4(tmp, JSON.stringify(queue, null, 2));
3826
+ writeFileSync5(tmp, JSON.stringify(queue, null, 2));
3655
3827
  renameSync3(tmp, QUEUE_PATH);
3656
3828
  }
3657
3829
  function queueIntercom(targetSession, reason) {
@@ -3743,10 +3915,10 @@ var QUEUE_PATH, MAX_RETRIES2, TTL_MS, INTERCOM_LOG;
3743
3915
  var init_intercom_queue = __esm({
3744
3916
  "src/lib/intercom-queue.ts"() {
3745
3917
  "use strict";
3746
- QUEUE_PATH = path9.join(os7.homedir(), ".exe-os", "intercom-queue.json");
3918
+ QUEUE_PATH = path10.join(os7.homedir(), ".exe-os", "intercom-queue.json");
3747
3919
  MAX_RETRIES2 = 5;
3748
3920
  TTL_MS = 60 * 60 * 1e3;
3749
- INTERCOM_LOG = path9.join(os7.homedir(), ".exe-os", "intercom.log");
3921
+ INTERCOM_LOG = path10.join(os7.homedir(), ".exe-os", "intercom.log");
3750
3922
  }
3751
3923
  });
3752
3924
 
@@ -3767,9 +3939,12 @@ __export(license_exports, {
3767
3939
  stopLicenseRevalidation: () => stopLicenseRevalidation,
3768
3940
  validateLicense: () => validateLicense
3769
3941
  });
3770
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync9, mkdirSync as mkdirSync5 } from "fs";
3942
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync11, mkdirSync as mkdirSync5 } from "fs";
3771
3943
  import { randomUUID as randomUUID3 } from "crypto";
3772
- import path10 from "path";
3944
+ import { createRequire as createRequire2 } from "module";
3945
+ import { pathToFileURL as pathToFileURL2 } from "url";
3946
+ import os8 from "os";
3947
+ import path11 from "path";
3773
3948
  import { jwtVerify, importSPKI } from "jose";
3774
3949
  async function fetchRetry(url, init) {
3775
3950
  try {
@@ -3780,37 +3955,37 @@ async function fetchRetry(url, init) {
3780
3955
  }
3781
3956
  }
3782
3957
  function loadDeviceId() {
3783
- const deviceJsonPath = path10.join(EXE_AI_DIR, "device.json");
3958
+ const deviceJsonPath = path11.join(EXE_AI_DIR, "device.json");
3784
3959
  try {
3785
- if (existsSync9(deviceJsonPath)) {
3786
- const data = JSON.parse(readFileSync7(deviceJsonPath, "utf8"));
3960
+ if (existsSync11(deviceJsonPath)) {
3961
+ const data = JSON.parse(readFileSync8(deviceJsonPath, "utf8"));
3787
3962
  if (data.deviceId) return data.deviceId;
3788
3963
  }
3789
3964
  } catch {
3790
3965
  }
3791
3966
  try {
3792
- if (existsSync9(DEVICE_ID_PATH)) {
3793
- const id2 = readFileSync7(DEVICE_ID_PATH, "utf8").trim();
3967
+ if (existsSync11(DEVICE_ID_PATH)) {
3968
+ const id2 = readFileSync8(DEVICE_ID_PATH, "utf8").trim();
3794
3969
  if (id2) return id2;
3795
3970
  }
3796
3971
  } catch {
3797
3972
  }
3798
3973
  const id = randomUUID3();
3799
3974
  mkdirSync5(EXE_AI_DIR, { recursive: true });
3800
- writeFileSync5(DEVICE_ID_PATH, id, "utf8");
3975
+ writeFileSync6(DEVICE_ID_PATH, id, "utf8");
3801
3976
  return id;
3802
3977
  }
3803
3978
  function loadLicense() {
3804
3979
  try {
3805
- if (!existsSync9(LICENSE_PATH)) return null;
3806
- return readFileSync7(LICENSE_PATH, "utf8").trim();
3980
+ if (!existsSync11(LICENSE_PATH)) return null;
3981
+ return readFileSync8(LICENSE_PATH, "utf8").trim();
3807
3982
  } catch {
3808
3983
  return null;
3809
3984
  }
3810
3985
  }
3811
3986
  function saveLicense(apiKey) {
3812
3987
  mkdirSync5(EXE_AI_DIR, { recursive: true });
3813
- writeFileSync5(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
3988
+ writeFileSync6(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
3814
3989
  }
3815
3990
  async function verifyLicenseJwt(token) {
3816
3991
  try {
@@ -3836,8 +4011,8 @@ async function verifyLicenseJwt(token) {
3836
4011
  }
3837
4012
  async function getCachedLicense() {
3838
4013
  try {
3839
- if (!existsSync9(CACHE_PATH)) return null;
3840
- const raw = JSON.parse(readFileSync7(CACHE_PATH, "utf8"));
4014
+ if (!existsSync11(CACHE_PATH)) return null;
4015
+ const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
3841
4016
  if (!raw.token || typeof raw.token !== "string") return null;
3842
4017
  return await verifyLicenseJwt(raw.token);
3843
4018
  } catch {
@@ -3846,8 +4021,8 @@ async function getCachedLicense() {
3846
4021
  }
3847
4022
  function readCachedToken() {
3848
4023
  try {
3849
- if (!existsSync9(CACHE_PATH)) return null;
3850
- const raw = JSON.parse(readFileSync7(CACHE_PATH, "utf8"));
4024
+ if (!existsSync11(CACHE_PATH)) return null;
4025
+ const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
3851
4026
  return typeof raw.token === "string" ? raw.token : null;
3852
4027
  } catch {
3853
4028
  return null;
@@ -3881,56 +4056,130 @@ function getRawCachedPlan() {
3881
4056
  }
3882
4057
  function cacheResponse(token) {
3883
4058
  try {
3884
- writeFileSync5(CACHE_PATH, JSON.stringify({ token }), "utf8");
4059
+ writeFileSync6(CACHE_PATH, JSON.stringify({ token }), "utf8");
3885
4060
  } catch {
3886
4061
  }
3887
4062
  }
3888
- async function validateLicense(apiKey, deviceId) {
3889
- const did = deviceId ?? loadDeviceId();
4063
+ function loadPrismaForLicense() {
4064
+ if (_prismaFailed) return null;
4065
+ const dbUrl = process.env.DATABASE_URL;
4066
+ if (!dbUrl) {
4067
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path11.join(os8.homedir(), "exe-db");
4068
+ if (!existsSync11(path11.join(exeDbRoot, "package.json"))) {
4069
+ _prismaFailed = true;
4070
+ return null;
4071
+ }
4072
+ }
4073
+ if (!_prismaPromise) {
4074
+ _prismaPromise = (async () => {
4075
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
4076
+ if (explicitPath) {
4077
+ const mod2 = await import(pathToFileURL2(explicitPath).href);
4078
+ const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
4079
+ if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
4080
+ return new Ctor2();
4081
+ }
4082
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path11.join(os8.homedir(), "exe-db");
4083
+ const req = createRequire2(path11.join(exeDbRoot, "package.json"));
4084
+ const entry = req.resolve("@prisma/client");
4085
+ const mod = await import(pathToFileURL2(entry).href);
4086
+ const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
4087
+ if (!Ctor) throw new Error(`No PrismaClient in ${entry}`);
4088
+ return new Ctor();
4089
+ })().catch((err) => {
4090
+ _prismaFailed = true;
4091
+ _prismaPromise = null;
4092
+ throw err;
4093
+ });
4094
+ }
4095
+ return _prismaPromise;
4096
+ }
4097
+ async function validateViaPostgres(apiKey) {
4098
+ const loader = loadPrismaForLicense();
4099
+ if (!loader) return null;
4100
+ try {
4101
+ const prisma = await loader;
4102
+ const rows = await prisma.$queryRawUnsafe(
4103
+ `SELECT plan, email, status, device_limit, employee_limit, memory_limit, expires_at
4104
+ FROM billing.licenses WHERE key = $1 LIMIT 1`,
4105
+ apiKey
4106
+ );
4107
+ if (!rows || rows.length === 0) return null;
4108
+ const row = rows[0];
4109
+ if (row.status !== "active") return null;
4110
+ if (row.expires_at && new Date(row.expires_at) < /* @__PURE__ */ new Date()) return null;
4111
+ const plan = row.plan;
4112
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
4113
+ return {
4114
+ valid: true,
4115
+ plan,
4116
+ email: row.email,
4117
+ expiresAt: row.expires_at ? new Date(row.expires_at).toISOString() : null,
4118
+ deviceLimit: row.device_limit ?? limits.devices,
4119
+ employeeLimit: row.employee_limit ?? limits.employees,
4120
+ memoryLimit: row.memory_limit ?? limits.memories
4121
+ };
4122
+ } catch {
4123
+ return null;
4124
+ }
4125
+ }
4126
+ async function validateViaCFWorker(apiKey, deviceId) {
3890
4127
  try {
3891
4128
  const res = await fetchRetry(`${API_BASE}/auth/activate`, {
3892
4129
  method: "POST",
3893
4130
  headers: { "Content-Type": "application/json" },
3894
- body: JSON.stringify({ apiKey, deviceId: did }),
4131
+ body: JSON.stringify({ apiKey, deviceId }),
3895
4132
  signal: AbortSignal.timeout(1e4)
3896
4133
  });
3897
- if (res.ok) {
3898
- const data = await res.json();
3899
- if (data.error === "device_limit_exceeded") {
3900
- const cached2 = await getCachedLicense();
3901
- if (cached2) return cached2;
3902
- const raw2 = getRawCachedPlan();
3903
- if (raw2) return { ...raw2, valid: false };
3904
- return { ...FREE_LICENSE, valid: false, plan: "free" };
3905
- }
3906
- if (data.token) {
3907
- cacheResponse(data.token);
3908
- const verified = await verifyLicenseJwt(data.token);
3909
- if (verified) return verified;
3910
- }
3911
- const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
3912
- return {
3913
- valid: data.valid,
3914
- plan: data.plan,
3915
- email: data.email,
3916
- expiresAt: data.expiresAt,
3917
- deviceLimit: limits.devices,
3918
- employeeLimit: limits.employees,
3919
- memoryLimit: limits.memories
3920
- };
4134
+ if (!res.ok) return null;
4135
+ const data = await res.json();
4136
+ if (data.error === "device_limit_exceeded") return null;
4137
+ if (!data.valid) return null;
4138
+ if (data.token) {
4139
+ cacheResponse(data.token);
4140
+ const verified = await verifyLicenseJwt(data.token);
4141
+ if (verified) return verified;
4142
+ }
4143
+ const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
4144
+ return {
4145
+ valid: data.valid,
4146
+ plan: data.plan,
4147
+ email: data.email,
4148
+ expiresAt: data.expiresAt,
4149
+ deviceLimit: limits.devices,
4150
+ employeeLimit: limits.employees,
4151
+ memoryLimit: limits.memories
4152
+ };
4153
+ } catch {
4154
+ return null;
4155
+ }
4156
+ }
4157
+ async function validateLicense(apiKey, deviceId) {
4158
+ const did = deviceId ?? loadDeviceId();
4159
+ const pgResult = await validateViaPostgres(apiKey);
4160
+ if (pgResult) {
4161
+ try {
4162
+ writeFileSync6(CACHE_PATH, JSON.stringify({ pgLicense: pgResult, ts: Date.now() }), "utf8");
4163
+ } catch {
4164
+ }
4165
+ return pgResult;
4166
+ }
4167
+ const cfResult = await validateViaCFWorker(apiKey, did);
4168
+ if (cfResult) return cfResult;
4169
+ const cached = await getCachedLicense();
4170
+ if (cached) return cached;
4171
+ try {
4172
+ if (existsSync11(CACHE_PATH)) {
4173
+ const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
4174
+ if (raw.pgLicense && raw.ts && Date.now() - raw.ts < 7 * 24 * 60 * 60 * 1e3) {
4175
+ return raw.pgLicense;
4176
+ }
3921
4177
  }
3922
- const cached = await getCachedLicense();
3923
- if (cached) return cached;
3924
- const raw = getRawCachedPlan();
3925
- if (raw) return raw;
3926
- return { ...FREE_LICENSE, valid: false, plan: "free" };
3927
4178
  } catch {
3928
- const cached = await getCachedLicense();
3929
- if (cached) return cached;
3930
- const rawFallback = getRawCachedPlan();
3931
- if (rawFallback) return rawFallback;
3932
- return { ...FREE_LICENSE, valid: false, error: "offline" };
3933
4179
  }
4180
+ const rawFallback = getRawCachedPlan();
4181
+ if (rawFallback) return rawFallback;
4182
+ return { ...FREE_LICENSE, valid: false };
3934
4183
  }
3935
4184
  function getCacheAgeMs() {
3936
4185
  try {
@@ -3945,9 +4194,9 @@ async function checkLicense() {
3945
4194
  let key = loadLicense();
3946
4195
  if (!key) {
3947
4196
  try {
3948
- const configPath = path10.join(EXE_AI_DIR, "config.json");
3949
- if (existsSync9(configPath)) {
3950
- const raw = JSON.parse(readFileSync7(configPath, "utf8"));
4197
+ const configPath = path11.join(EXE_AI_DIR, "config.json");
4198
+ if (existsSync11(configPath)) {
4199
+ const raw = JSON.parse(readFileSync8(configPath, "utf8"));
3951
4200
  const cloud = raw.cloud;
3952
4201
  if (cloud?.apiKey) {
3953
4202
  key = cloud.apiKey;
@@ -4101,14 +4350,14 @@ function stopLicenseRevalidation() {
4101
4350
  _revalTimer = null;
4102
4351
  }
4103
4352
  }
4104
- 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;
4353
+ 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;
4105
4354
  var init_license = __esm({
4106
4355
  "src/lib/license.ts"() {
4107
4356
  "use strict";
4108
4357
  init_config();
4109
- LICENSE_PATH = path10.join(EXE_AI_DIR, "license.key");
4110
- CACHE_PATH = path10.join(EXE_AI_DIR, "license-cache.json");
4111
- DEVICE_ID_PATH = path10.join(EXE_AI_DIR, "device-id");
4358
+ LICENSE_PATH = path11.join(EXE_AI_DIR, "license.key");
4359
+ CACHE_PATH = path11.join(EXE_AI_DIR, "license-cache.json");
4360
+ DEVICE_ID_PATH = path11.join(EXE_AI_DIR, "device-id");
4112
4361
  API_BASE = "https://askexe.com/cloud";
4113
4362
  RETRY_DELAY_MS = 500;
4114
4363
  LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
@@ -4132,18 +4381,20 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
4132
4381
  employeeLimit: 1,
4133
4382
  memoryLimit: 5e3
4134
4383
  };
4384
+ _prismaPromise = null;
4385
+ _prismaFailed = false;
4135
4386
  CACHE_MAX_AGE_MS = 36e5;
4136
4387
  _revalTimer = null;
4137
4388
  }
4138
4389
  });
4139
4390
 
4140
4391
  // src/lib/plan-limits.ts
4141
- import { readFileSync as readFileSync8, existsSync as existsSync10 } from "fs";
4142
- import path11 from "path";
4392
+ import { readFileSync as readFileSync9, existsSync as existsSync12 } from "fs";
4393
+ import path12 from "path";
4143
4394
  function getLicenseSync() {
4144
4395
  try {
4145
- if (!existsSync10(CACHE_PATH2)) return freeLicense();
4146
- const raw = JSON.parse(readFileSync8(CACHE_PATH2, "utf8"));
4396
+ if (!existsSync12(CACHE_PATH2)) return freeLicense();
4397
+ const raw = JSON.parse(readFileSync9(CACHE_PATH2, "utf8"));
4147
4398
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
4148
4399
  const parts = raw.token.split(".");
4149
4400
  if (parts.length !== 3) return freeLicense();
@@ -4181,8 +4432,8 @@ function assertEmployeeLimitSync(rosterPath) {
4181
4432
  const filePath = rosterPath ?? EMPLOYEES_PATH;
4182
4433
  let count = 0;
4183
4434
  try {
4184
- if (existsSync10(filePath)) {
4185
- const raw = readFileSync8(filePath, "utf8");
4435
+ if (existsSync12(filePath)) {
4436
+ const raw = readFileSync9(filePath, "utf8");
4186
4437
  const employees = JSON.parse(raw);
4187
4438
  count = Array.isArray(employees) ? employees.length : 0;
4188
4439
  }
@@ -4211,29 +4462,30 @@ var init_plan_limits = __esm({
4211
4462
  this.name = "PlanLimitError";
4212
4463
  }
4213
4464
  };
4214
- CACHE_PATH2 = path11.join(EXE_AI_DIR, "license-cache.json");
4465
+ CACHE_PATH2 = path12.join(EXE_AI_DIR, "license-cache.json");
4215
4466
  }
4216
4467
  });
4217
4468
 
4218
4469
  // src/lib/notifications.ts
4219
- import crypto from "crypto";
4220
- import path12 from "path";
4221
- import os8 from "os";
4470
+ import crypto2 from "crypto";
4471
+ import path13 from "path";
4472
+ import os9 from "os";
4222
4473
  import {
4223
- readFileSync as readFileSync9,
4474
+ readFileSync as readFileSync10,
4224
4475
  readdirSync as readdirSync2,
4225
4476
  unlinkSync as unlinkSync3,
4226
- existsSync as existsSync11,
4477
+ existsSync as existsSync13,
4227
4478
  rmdirSync
4228
4479
  } from "fs";
4229
4480
  async function writeNotification(notification) {
4230
4481
  try {
4231
4482
  const client = getClient();
4232
- const id = crypto.randomUUID();
4483
+ const id = crypto2.randomUUID();
4233
4484
  const now = (/* @__PURE__ */ new Date()).toISOString();
4485
+ const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
4234
4486
  await client.execute({
4235
- sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
4236
- VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
4487
+ sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
4488
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
4237
4489
  args: [
4238
4490
  id,
4239
4491
  notification.agentId,
@@ -4242,6 +4494,7 @@ async function writeNotification(notification) {
4242
4494
  notification.project,
4243
4495
  notification.summary,
4244
4496
  notification.taskFile ?? null,
4497
+ sessionScope,
4245
4498
  now
4246
4499
  ]
4247
4500
  });
@@ -4250,21 +4503,22 @@ async function writeNotification(notification) {
4250
4503
  `);
4251
4504
  }
4252
4505
  }
4253
- async function readUnreadNotifications(agentFilter) {
4506
+ async function readUnreadNotifications(agentFilter, sessionScope) {
4254
4507
  try {
4255
4508
  const client = getClient();
4256
4509
  const conditions = ["read = 0"];
4257
4510
  const args = [];
4511
+ const scope = strictSessionScopeFilter(sessionScope);
4258
4512
  if (agentFilter) {
4259
4513
  conditions.push("agent_id = ?");
4260
4514
  args.push(agentFilter);
4261
4515
  }
4262
4516
  const result = await client.execute({
4263
- sql: `SELECT id, agent_id, agent_role, event, project, summary, task_file, created_at
4517
+ sql: `SELECT id, agent_id, agent_role, event, project, summary, task_file, session_scope, created_at
4264
4518
  FROM notifications
4265
- WHERE ${conditions.join(" AND ")}
4519
+ WHERE ${conditions.join(" AND ")}${scope.sql}
4266
4520
  ORDER BY created_at ASC`,
4267
- args
4521
+ args: [...args, ...scope.args]
4268
4522
  });
4269
4523
  return result.rows.map((r) => ({
4270
4524
  id: String(r.id),
@@ -4274,6 +4528,7 @@ async function readUnreadNotifications(agentFilter) {
4274
4528
  project: String(r.project),
4275
4529
  summary: String(r.summary),
4276
4530
  taskFile: r.task_file ? String(r.task_file) : void 0,
4531
+ sessionScope: r.session_scope == null ? null : String(r.session_scope),
4277
4532
  timestamp: String(r.created_at),
4278
4533
  read: false
4279
4534
  }));
@@ -4281,54 +4536,60 @@ async function readUnreadNotifications(agentFilter) {
4281
4536
  return [];
4282
4537
  }
4283
4538
  }
4284
- async function markAsRead(ids) {
4539
+ async function markAsRead(ids, sessionScope) {
4285
4540
  if (ids.length === 0) return;
4286
4541
  try {
4287
4542
  const client = getClient();
4288
4543
  const placeholders = ids.map(() => "?").join(", ");
4544
+ const scope = strictSessionScopeFilter(sessionScope);
4289
4545
  await client.execute({
4290
- sql: `UPDATE notifications SET read = 1 WHERE id IN (${placeholders})`,
4291
- args: ids
4546
+ sql: `UPDATE notifications SET read = 1 WHERE id IN (${placeholders})${scope.sql}`,
4547
+ args: [...ids, ...scope.args]
4292
4548
  });
4293
4549
  } catch {
4294
4550
  }
4295
4551
  }
4296
- async function markAsReadByTaskFile(taskFile) {
4552
+ async function markAsReadByTaskFile(taskFile, sessionScope) {
4297
4553
  try {
4298
4554
  const client = getClient();
4555
+ const scope = strictSessionScopeFilter(sessionScope);
4299
4556
  await client.execute({
4300
- sql: "UPDATE notifications SET read = 1 WHERE task_file = ? AND read = 0",
4301
- args: [taskFile]
4557
+ sql: `UPDATE notifications SET read = 1
4558
+ WHERE task_file = ? AND read = 0${scope.sql}`,
4559
+ args: [taskFile, ...scope.args]
4302
4560
  });
4303
4561
  } catch {
4304
4562
  }
4305
4563
  }
4306
- async function cleanupOldNotifications(daysOld = CLEANUP_DAYS) {
4564
+ async function cleanupOldNotifications(daysOld = CLEANUP_DAYS, sessionScope) {
4307
4565
  try {
4308
4566
  const client = getClient();
4309
4567
  const cutoff = new Date(
4310
4568
  Date.now() - daysOld * 24 * 60 * 60 * 1e3
4311
4569
  ).toISOString();
4570
+ const scope = strictSessionScopeFilter(sessionScope);
4312
4571
  const result = await client.execute({
4313
- sql: "DELETE FROM notifications WHERE created_at < ?",
4314
- args: [cutoff]
4572
+ sql: `DELETE FROM notifications WHERE created_at < ?${scope.sql}`,
4573
+ args: [cutoff, ...scope.args]
4315
4574
  });
4316
4575
  return result.rowsAffected;
4317
4576
  } catch {
4318
4577
  return 0;
4319
4578
  }
4320
4579
  }
4321
- async function markDoneTaskNotificationsAsRead() {
4580
+ async function markDoneTaskNotificationsAsRead(sessionScope) {
4322
4581
  try {
4323
4582
  const client = getClient();
4583
+ const scope = strictSessionScopeFilter(sessionScope);
4324
4584
  const result = await client.execute({
4325
4585
  sql: `UPDATE notifications SET read = 1
4326
4586
  WHERE read = 0
4327
4587
  AND task_file IS NOT NULL
4588
+ ${scope.sql}
4328
4589
  AND task_file IN (
4329
- SELECT task_file FROM tasks WHERE status = 'done'
4590
+ SELECT task_file FROM tasks WHERE status = 'done'${scope.sql}
4330
4591
  )`,
4331
- args: []
4592
+ args: [...scope.args, ...scope.args]
4332
4593
  });
4333
4594
  return result.rowsAffected;
4334
4595
  } catch {
@@ -4336,9 +4597,9 @@ async function markDoneTaskNotificationsAsRead() {
4336
4597
  }
4337
4598
  }
4338
4599
  async function migrateJsonNotifications() {
4339
- const base = process.env.EXE_OS_DIR || process.env.EXE_MEM_DIR || path12.join(os8.homedir(), ".exe-os");
4340
- const notifDir = path12.join(base, "notifications");
4341
- if (!existsSync11(notifDir)) return 0;
4600
+ const base = process.env.EXE_OS_DIR || process.env.EXE_MEM_DIR || path13.join(os9.homedir(), ".exe-os");
4601
+ const notifDir = path13.join(base, "notifications");
4602
+ if (!existsSync13(notifDir)) return 0;
4342
4603
  let migrated = 0;
4343
4604
  try {
4344
4605
  const files = readdirSync2(notifDir).filter((f) => f.endsWith(".json"));
@@ -4346,19 +4607,20 @@ async function migrateJsonNotifications() {
4346
4607
  const client = getClient();
4347
4608
  for (const file of files) {
4348
4609
  try {
4349
- const filePath = path12.join(notifDir, file);
4350
- const data = JSON.parse(readFileSync9(filePath, "utf8"));
4610
+ const filePath = path13.join(notifDir, file);
4611
+ const data = JSON.parse(readFileSync10(filePath, "utf8"));
4351
4612
  await client.execute({
4352
- sql: `INSERT OR IGNORE INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
4353
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
4613
+ sql: `INSERT OR IGNORE INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
4614
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
4354
4615
  args: [
4355
- crypto.randomUUID(),
4616
+ crypto2.randomUUID(),
4356
4617
  data.agentId ?? "unknown",
4357
4618
  data.agentRole ?? "unknown",
4358
4619
  data.event ?? "session_summary",
4359
4620
  data.project ?? "unknown",
4360
4621
  data.summary ?? "",
4361
4622
  data.taskFile ?? null,
4623
+ null,
4362
4624
  data.read ? 1 : 0,
4363
4625
  data.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
4364
4626
  ]
@@ -4384,6 +4646,7 @@ var init_notifications = __esm({
4384
4646
  "src/lib/notifications.ts"() {
4385
4647
  "use strict";
4386
4648
  init_database();
4649
+ init_task_scope();
4387
4650
  CLEANUP_DAYS = 7;
4388
4651
  }
4389
4652
  });
@@ -4401,7 +4664,7 @@ __export(session_kill_telemetry_exports, {
4401
4664
  recordSessionKill: () => recordSessionKill,
4402
4665
  sumTokensSavedSince: () => sumTokensSavedSince
4403
4666
  });
4404
- import crypto2 from "crypto";
4667
+ import crypto3 from "crypto";
4405
4668
  async function recordSessionKill(input) {
4406
4669
  try {
4407
4670
  const client = getClient();
@@ -4411,7 +4674,7 @@ async function recordSessionKill(input) {
4411
4674
  ticks_idle, estimated_tokens_saved)
4412
4675
  VALUES (?, ?, ?, ?, ?, ?, ?)`,
4413
4676
  args: [
4414
- crypto2.randomUUID(),
4677
+ crypto3.randomUUID(),
4415
4678
  input.sessionName,
4416
4679
  input.agentId,
4417
4680
  (/* @__PURE__ */ new Date()).toISOString(),
@@ -4492,13 +4755,126 @@ var init_session_kill_telemetry = __esm({
4492
4755
  }
4493
4756
  });
4494
4757
 
4495
- // src/lib/tasks-crud.ts
4496
- import crypto3 from "crypto";
4497
- import path13 from "path";
4498
- import os9 from "os";
4758
+ // src/lib/project-name.ts
4759
+ var project_name_exports = {};
4760
+ __export(project_name_exports, {
4761
+ _resetCache: () => _resetCache,
4762
+ getProjectName: () => getProjectName
4763
+ });
4499
4764
  import { execSync as execSync5 } from "child_process";
4765
+ import path14 from "path";
4766
+ function getProjectName(cwd) {
4767
+ const dir = cwd ?? process.cwd();
4768
+ if (_cached2 && _cachedCwd === dir) return _cached2;
4769
+ try {
4770
+ let repoRoot;
4771
+ try {
4772
+ const gitCommonDir = execSync5("git rev-parse --path-format=absolute --git-common-dir", {
4773
+ cwd: dir,
4774
+ encoding: "utf8",
4775
+ timeout: 2e3,
4776
+ stdio: ["pipe", "pipe", "pipe"]
4777
+ }).trim();
4778
+ repoRoot = path14.dirname(gitCommonDir);
4779
+ } catch {
4780
+ repoRoot = execSync5("git rev-parse --show-toplevel", {
4781
+ cwd: dir,
4782
+ encoding: "utf8",
4783
+ timeout: 2e3,
4784
+ stdio: ["pipe", "pipe", "pipe"]
4785
+ }).trim();
4786
+ }
4787
+ _cached2 = path14.basename(repoRoot);
4788
+ _cachedCwd = dir;
4789
+ return _cached2;
4790
+ } catch {
4791
+ _cached2 = path14.basename(dir);
4792
+ _cachedCwd = dir;
4793
+ return _cached2;
4794
+ }
4795
+ }
4796
+ function _resetCache() {
4797
+ _cached2 = null;
4798
+ _cachedCwd = null;
4799
+ }
4800
+ var _cached2, _cachedCwd;
4801
+ var init_project_name = __esm({
4802
+ "src/lib/project-name.ts"() {
4803
+ "use strict";
4804
+ _cached2 = null;
4805
+ _cachedCwd = null;
4806
+ }
4807
+ });
4808
+
4809
+ // src/lib/session-scope.ts
4810
+ var session_scope_exports = {};
4811
+ __export(session_scope_exports, {
4812
+ assertSessionScope: () => assertSessionScope,
4813
+ findSessionForProject: () => findSessionForProject,
4814
+ getSessionProject: () => getSessionProject
4815
+ });
4816
+ function getSessionProject(sessionName) {
4817
+ const sessions = listSessions();
4818
+ const entry = sessions.find((s) => s.windowName === sessionName);
4819
+ if (!entry) return null;
4820
+ const parts = entry.projectDir.split("/").filter(Boolean);
4821
+ return parts[parts.length - 1] ?? null;
4822
+ }
4823
+ function findSessionForProject(projectName) {
4824
+ const sessions = listSessions();
4825
+ for (const s of sessions) {
4826
+ const proj = s.projectDir.split("/").filter(Boolean).pop();
4827
+ if (proj === projectName && isCoordinatorName(s.agentId)) return s;
4828
+ }
4829
+ return null;
4830
+ }
4831
+ function assertSessionScope(actionType, targetProject) {
4832
+ try {
4833
+ const currentProject = getProjectName();
4834
+ const exeSession = resolveExeSession();
4835
+ if (!exeSession) {
4836
+ return { allowed: true, reason: "no_session" };
4837
+ }
4838
+ if (currentProject === targetProject) {
4839
+ return {
4840
+ allowed: true,
4841
+ reason: "same_session",
4842
+ currentProject,
4843
+ targetProject
4844
+ };
4845
+ }
4846
+ process.stderr.write(
4847
+ `[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
4848
+ `
4849
+ );
4850
+ return {
4851
+ allowed: false,
4852
+ reason: "cross_session_denied",
4853
+ currentProject,
4854
+ targetProject,
4855
+ targetSession: findSessionForProject(targetProject)?.windowName
4856
+ };
4857
+ } catch {
4858
+ return { allowed: true, reason: "no_session" };
4859
+ }
4860
+ }
4861
+ var init_session_scope = __esm({
4862
+ "src/lib/session-scope.ts"() {
4863
+ "use strict";
4864
+ init_session_registry();
4865
+ init_project_name();
4866
+ init_tmux_routing();
4867
+ init_employees();
4868
+ }
4869
+ });
4870
+
4871
+ // src/lib/tasks-crud.ts
4872
+ import crypto4 from "crypto";
4873
+ import path15 from "path";
4874
+ import os10 from "os";
4875
+ import { execSync as execSync6 } from "child_process";
4500
4876
  import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
4501
- import { existsSync as existsSync12, readFileSync as readFileSync10 } from "fs";
4877
+ import { existsSync as existsSync14, readFileSync as readFileSync11 } from "fs";
4502
4878
  async function writeCheckpoint(input) {
4503
4879
  const client = getClient();
4504
4880
  const row = await resolveTask(client, input.taskId);
@@ -4614,13 +4990,28 @@ async function resolveTask(client, identifier, scopeSession) {
4614
4990
  }
4615
4991
  async function createTaskCore(input) {
4616
4992
  const client = getClient();
4617
- const id = crypto3.randomUUID();
4993
+ const id = crypto4.randomUUID();
4618
4994
  const now = (/* @__PURE__ */ new Date()).toISOString();
4619
4995
  const slug = slugify(input.title);
4620
4996
  let earlySessionScope = null;
4997
+ let scopeMismatchWarning;
4621
4998
  try {
4622
4999
  const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
4623
- earlySessionScope = resolveExeSession2();
5000
+ const resolved = resolveExeSession2();
5001
+ if (resolved && input.projectName) {
5002
+ const { getSessionProject: getSessionProject2 } = await Promise.resolve().then(() => (init_session_scope(), session_scope_exports));
5003
+ const sessionProject = getSessionProject2(resolved);
5004
+ if (sessionProject && sessionProject !== input.projectName) {
5005
+ scopeMismatchWarning = `session/project mismatch: session "${resolved}" owns "${sessionProject}" but task targets "${input.projectName}". Routed to default scope.`;
5006
+ process.stderr.write(`[create_task] ${scopeMismatchWarning}
5007
+ `);
5008
+ earlySessionScope = null;
5009
+ } else {
5010
+ earlySessionScope = resolved;
5011
+ }
5012
+ } else {
5013
+ earlySessionScope = resolved;
5014
+ }
4624
5015
  } catch {
4625
5016
  }
4626
5017
  const scope = earlySessionScope ?? "default";
@@ -4671,10 +5062,14 @@ async function createTaskCore(input) {
4671
5062
  ${laneWarning}` : laneWarning;
4672
5063
  }
4673
5064
  }
5065
+ if (scopeMismatchWarning) {
5066
+ warning = warning ? `${warning}
5067
+ ${scopeMismatchWarning}` : scopeMismatchWarning;
5068
+ }
4674
5069
  if (input.baseDir) {
4675
5070
  try {
4676
- await mkdir4(path13.join(input.baseDir, "exe", "output"), { recursive: true });
4677
- await mkdir4(path13.join(input.baseDir, "exe", "research"), { recursive: true });
5071
+ await mkdir4(path15.join(input.baseDir, "exe", "output"), { recursive: true });
5072
+ await mkdir4(path15.join(input.baseDir, "exe", "research"), { recursive: true });
4678
5073
  await ensureArchitectureDoc(input.baseDir, input.projectName);
4679
5074
  await ensureGitignoreExe(input.baseDir);
4680
5075
  } catch {
@@ -4710,13 +5105,19 @@ ${laneWarning}` : laneWarning;
4710
5105
  });
4711
5106
  if (input.baseDir) {
4712
5107
  try {
4713
- const EXE_OS_DIR = path13.join(os9.homedir(), ".exe-os");
4714
- const mdPath = path13.join(EXE_OS_DIR, taskFile);
4715
- const mdDir = path13.dirname(mdPath);
4716
- if (!existsSync12(mdDir)) await mkdir4(mdDir, { recursive: true });
5108
+ const EXE_OS_DIR = path15.join(os10.homedir(), ".exe-os");
5109
+ const mdPath = path15.join(EXE_OS_DIR, taskFile);
5110
+ const mdDir = path15.dirname(mdPath);
5111
+ if (!existsSync14(mdDir)) await mkdir4(mdDir, { recursive: true });
4717
5112
  const reviewer = input.reviewer ?? input.assignedBy;
4718
5113
  const mdContent = `# ${input.title}
4719
5114
 
5115
+ ## MANDATORY: When done
5116
+
5117
+ You MUST call update_task with status "done" and a result summary when finished.
5118
+ If you skip this, your reviewer will not know you're done and your work won't be reviewed.
5119
+ Do NOT let a failed commit or any error prevent you from calling update_task(done).
5120
+
4720
5121
  **ID:** ${id}
4721
5122
  **Status:** ${initialStatus}
4722
5123
  **Priority:** ${input.priority}
@@ -4730,12 +5131,6 @@ ${laneWarning}` : laneWarning;
4730
5131
  ## Context
4731
5132
 
4732
5133
  ${input.context}
4733
-
4734
- ## MANDATORY: When done
4735
-
4736
- You MUST call update_task with status "done" and a result summary when finished.
4737
- If you skip this, your reviewer will not know you're done and your work won't be reviewed.
4738
- Do NOT let a failed commit or any error prevent you from calling update_task(done).
4739
5134
  `;
4740
5135
  await writeFile4(mdPath, mdContent, "utf-8");
4741
5136
  } catch (err) {
@@ -4817,14 +5212,14 @@ function isTmuxSessionAlive(identifier) {
4817
5212
  if (!identifier || identifier === "unknown") return true;
4818
5213
  try {
4819
5214
  if (identifier.startsWith("%")) {
4820
- const output = execSync5("tmux list-panes -a -F '#{pane_id}'", {
5215
+ const output = execSync6("tmux list-panes -a -F '#{pane_id}'", {
4821
5216
  timeout: 2e3,
4822
5217
  encoding: "utf8",
4823
5218
  stdio: ["pipe", "pipe", "pipe"]
4824
5219
  });
4825
5220
  return output.split("\n").some((l) => l.trim() === identifier);
4826
5221
  } else {
4827
- execSync5(`tmux has-session -t ${JSON.stringify(identifier)}`, {
5222
+ execSync6(`tmux has-session -t ${JSON.stringify(identifier)}`, {
4828
5223
  timeout: 2e3,
4829
5224
  stdio: ["pipe", "pipe", "pipe"]
4830
5225
  });
@@ -4833,7 +5228,7 @@ function isTmuxSessionAlive(identifier) {
4833
5228
  } catch {
4834
5229
  if (identifier.startsWith("%")) return true;
4835
5230
  try {
4836
- execSync5("tmux list-sessions", {
5231
+ execSync6("tmux list-sessions", {
4837
5232
  timeout: 2e3,
4838
5233
  stdio: ["pipe", "pipe", "pipe"]
4839
5234
  });
@@ -4848,12 +5243,12 @@ function checkStaleCompletion(taskContext, taskCreatedAt) {
4848
5243
  if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
4849
5244
  try {
4850
5245
  const since = new Date(taskCreatedAt).toISOString();
4851
- const branch = execSync5(
5246
+ const branch = execSync6(
4852
5247
  "git rev-parse --abbrev-ref HEAD 2>/dev/null",
4853
5248
  { encoding: "utf8", timeout: 3e3 }
4854
5249
  ).trim();
4855
5250
  const branchArg = branch && branch !== "HEAD" ? branch : "";
4856
- const commitCount = execSync5(
5251
+ const commitCount = execSync6(
4857
5252
  `git log --oneline --since="${since}" ${branchArg} 2>/dev/null | wc -l`,
4858
5253
  { encoding: "utf8", timeout: 5e3 }
4859
5254
  ).trim();
@@ -4984,7 +5379,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
4984
5379
  await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
4985
5380
  } catch {
4986
5381
  }
4987
- if (input.status === "done" || input.status === "cancelled") {
5382
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
4988
5383
  try {
4989
5384
  const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
4990
5385
  clearQueueForAgent2(String(row.assigned_to));
@@ -5013,9 +5408,9 @@ async function deleteTaskCore(taskId, _baseDir) {
5013
5408
  return { taskFile, assignedTo, assignedBy, taskSlug };
5014
5409
  }
5015
5410
  async function ensureArchitectureDoc(baseDir, projectName) {
5016
- const archPath = path13.join(baseDir, "exe", "ARCHITECTURE.md");
5411
+ const archPath = path15.join(baseDir, "exe", "ARCHITECTURE.md");
5017
5412
  try {
5018
- if (existsSync12(archPath)) return;
5413
+ if (existsSync14(archPath)) return;
5019
5414
  const template = [
5020
5415
  `# ${projectName} \u2014 System Architecture`,
5021
5416
  "",
@@ -5048,10 +5443,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
5048
5443
  }
5049
5444
  }
5050
5445
  async function ensureGitignoreExe(baseDir) {
5051
- const gitignorePath = path13.join(baseDir, ".gitignore");
5446
+ const gitignorePath = path15.join(baseDir, ".gitignore");
5052
5447
  try {
5053
- if (existsSync12(gitignorePath)) {
5054
- const content = readFileSync10(gitignorePath, "utf-8");
5448
+ if (existsSync14(gitignorePath)) {
5449
+ const content = readFileSync11(gitignorePath, "utf-8");
5055
5450
  if (/^\/?exe\/?$/m.test(content)) return;
5056
5451
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
5057
5452
  } else {
@@ -5082,58 +5477,42 @@ var init_tasks_crud = __esm({
5082
5477
  });
5083
5478
 
5084
5479
  // src/lib/tasks-review.ts
5085
- import path14 from "path";
5086
- import { existsSync as existsSync13, readdirSync as readdirSync3, unlinkSync as unlinkSync4 } from "fs";
5480
+ import path16 from "path";
5481
+ import { existsSync as existsSync15, readdirSync as readdirSync3, unlinkSync as unlinkSync4 } from "fs";
5087
5482
  async function countPendingReviews(sessionScope) {
5088
5483
  const client = getClient();
5089
- if (sessionScope) {
5090
- const result2 = await client.execute({
5091
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review' AND session_scope = ?",
5092
- args: [sessionScope]
5093
- });
5094
- return Number(result2.rows[0]?.cnt) || 0;
5095
- }
5484
+ const scope = strictSessionScopeFilter(
5485
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
5486
+ );
5096
5487
  const result = await client.execute({
5097
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review'",
5098
- args: []
5488
+ sql: `SELECT COUNT(*) as cnt FROM tasks
5489
+ WHERE status = 'needs_review'${scope.sql}`,
5490
+ args: [...scope.args]
5099
5491
  });
5100
5492
  return Number(result.rows[0]?.cnt) || 0;
5101
5493
  }
5102
5494
  async function countNewPendingReviewsSince(sinceIso, sessionScope) {
5103
5495
  const client = getClient();
5104
- if (sessionScope) {
5105
- const result2 = await client.execute({
5106
- sql: `SELECT COUNT(*) as cnt FROM tasks
5107
- WHERE status = 'needs_review' AND updated_at > ?
5108
- AND session_scope = ?`,
5109
- args: [sinceIso, sessionScope]
5110
- });
5111
- return Number(result2.rows[0]?.cnt) || 0;
5112
- }
5496
+ const scope = strictSessionScopeFilter(
5497
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
5498
+ );
5113
5499
  const result = await client.execute({
5114
5500
  sql: `SELECT COUNT(*) as cnt FROM tasks
5115
- WHERE status = 'needs_review' AND updated_at > ?`,
5116
- args: [sinceIso]
5501
+ WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
5502
+ args: [sinceIso, ...scope.args]
5117
5503
  });
5118
5504
  return Number(result.rows[0]?.cnt) || 0;
5119
5505
  }
5120
5506
  async function listPendingReviews(limit, sessionScope) {
5121
5507
  const client = getClient();
5122
- if (sessionScope) {
5123
- const result2 = await client.execute({
5124
- sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
5125
- WHERE status = 'needs_review'
5126
- AND session_scope = ?
5127
- ORDER BY updated_at ASC LIMIT ?`,
5128
- args: [sessionScope, limit]
5129
- });
5130
- return result2.rows;
5131
- }
5508
+ const scope = strictSessionScopeFilter(
5509
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
5510
+ );
5132
5511
  const result = await client.execute({
5133
5512
  sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
5134
- WHERE status = 'needs_review'
5513
+ WHERE status = 'needs_review'${scope.sql}
5135
5514
  ORDER BY updated_at ASC LIMIT ?`,
5136
- args: [limit]
5515
+ args: [...scope.args, limit]
5137
5516
  });
5138
5517
  return result.rows;
5139
5518
  }
@@ -5145,7 +5524,7 @@ async function cleanupOrphanedReviews() {
5145
5524
  WHERE status IN ('open', 'needs_review', 'in_progress')
5146
5525
  AND assigned_by = 'system'
5147
5526
  AND title LIKE 'Review:%'
5148
- AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
5527
+ AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
5149
5528
  args: [now]
5150
5529
  });
5151
5530
  const r1b = await client.execute({
@@ -5264,11 +5643,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
5264
5643
  );
5265
5644
  }
5266
5645
  try {
5267
- const cacheDir = path14.join(EXE_AI_DIR, "session-cache");
5268
- if (existsSync13(cacheDir)) {
5646
+ const cacheDir = path16.join(EXE_AI_DIR, "session-cache");
5647
+ if (existsSync15(cacheDir)) {
5269
5648
  for (const f of readdirSync3(cacheDir)) {
5270
5649
  if (f.startsWith("review-notified-")) {
5271
- unlinkSync4(path14.join(cacheDir, f));
5650
+ unlinkSync4(path16.join(cacheDir, f));
5272
5651
  }
5273
5652
  }
5274
5653
  }
@@ -5285,11 +5664,12 @@ var init_tasks_review = __esm({
5285
5664
  init_tmux_routing();
5286
5665
  init_session_key();
5287
5666
  init_state_bus();
5667
+ init_task_scope();
5288
5668
  }
5289
5669
  });
5290
5670
 
5291
5671
  // src/lib/tasks-chain.ts
5292
- import path15 from "path";
5672
+ import path17 from "path";
5293
5673
  import { readFile as readFile4, writeFile as writeFile5 } from "fs/promises";
5294
5674
  async function cascadeUnblock(taskId, baseDir, now) {
5295
5675
  const client = getClient();
@@ -5306,7 +5686,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
5306
5686
  });
5307
5687
  for (const ur of unblockedRows.rows) {
5308
5688
  try {
5309
- const ubFile = path15.join(baseDir, String(ur.task_file));
5689
+ const ubFile = path17.join(baseDir, String(ur.task_file));
5310
5690
  let ubContent = await readFile4(ubFile, "utf-8");
5311
5691
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
5312
5692
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -5341,7 +5721,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
5341
5721
  const scScope = sessionScopeFilter();
5342
5722
  const remaining = await client.execute({
5343
5723
  sql: `SELECT COUNT(*) as cnt FROM tasks
5344
- WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
5724
+ WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
5345
5725
  args: [parentTaskId, ...scScope.args]
5346
5726
  });
5347
5727
  const cnt = Number(remaining.rows[0]?.cnt ?? 1);
@@ -5373,119 +5753,6 @@ var init_tasks_chain = __esm({
5373
5753
  }
5374
5754
  });
5375
5755
 
5376
- // src/lib/project-name.ts
5377
- var project_name_exports = {};
5378
- __export(project_name_exports, {
5379
- _resetCache: () => _resetCache,
5380
- getProjectName: () => getProjectName
5381
- });
5382
- import { execSync as execSync6 } from "child_process";
5383
- import path16 from "path";
5384
- function getProjectName(cwd) {
5385
- const dir = cwd ?? process.cwd();
5386
- if (_cached2 && _cachedCwd === dir) return _cached2;
5387
- try {
5388
- let repoRoot;
5389
- try {
5390
- const gitCommonDir = execSync6("git rev-parse --path-format=absolute --git-common-dir", {
5391
- cwd: dir,
5392
- encoding: "utf8",
5393
- timeout: 2e3,
5394
- stdio: ["pipe", "pipe", "pipe"]
5395
- }).trim();
5396
- repoRoot = path16.dirname(gitCommonDir);
5397
- } catch {
5398
- repoRoot = execSync6("git rev-parse --show-toplevel", {
5399
- cwd: dir,
5400
- encoding: "utf8",
5401
- timeout: 2e3,
5402
- stdio: ["pipe", "pipe", "pipe"]
5403
- }).trim();
5404
- }
5405
- _cached2 = path16.basename(repoRoot);
5406
- _cachedCwd = dir;
5407
- return _cached2;
5408
- } catch {
5409
- _cached2 = path16.basename(dir);
5410
- _cachedCwd = dir;
5411
- return _cached2;
5412
- }
5413
- }
5414
- function _resetCache() {
5415
- _cached2 = null;
5416
- _cachedCwd = null;
5417
- }
5418
- var _cached2, _cachedCwd;
5419
- var init_project_name = __esm({
5420
- "src/lib/project-name.ts"() {
5421
- "use strict";
5422
- _cached2 = null;
5423
- _cachedCwd = null;
5424
- }
5425
- });
5426
-
5427
- // src/lib/session-scope.ts
5428
- var session_scope_exports = {};
5429
- __export(session_scope_exports, {
5430
- assertSessionScope: () => assertSessionScope,
5431
- findSessionForProject: () => findSessionForProject,
5432
- getSessionProject: () => getSessionProject
5433
- });
5434
- function getSessionProject(sessionName) {
5435
- const sessions = listSessions();
5436
- const entry = sessions.find((s) => s.windowName === sessionName);
5437
- if (!entry) return null;
5438
- const parts = entry.projectDir.split("/").filter(Boolean);
5439
- return parts[parts.length - 1] ?? null;
5440
- }
5441
- function findSessionForProject(projectName) {
5442
- const sessions = listSessions();
5443
- for (const s of sessions) {
5444
- const proj = s.projectDir.split("/").filter(Boolean).pop();
5445
- if (proj === projectName && isCoordinatorName(s.agentId)) return s;
5446
- }
5447
- return null;
5448
- }
5449
- function assertSessionScope(actionType, targetProject) {
5450
- try {
5451
- const currentProject = getProjectName();
5452
- const exeSession = resolveExeSession();
5453
- if (!exeSession) {
5454
- return { allowed: true, reason: "no_session" };
5455
- }
5456
- if (currentProject === targetProject) {
5457
- return {
5458
- allowed: true,
5459
- reason: "same_session",
5460
- currentProject,
5461
- targetProject
5462
- };
5463
- }
5464
- process.stderr.write(
5465
- `[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
5466
- `
5467
- );
5468
- return {
5469
- allowed: false,
5470
- reason: "cross_session_denied",
5471
- currentProject,
5472
- targetProject,
5473
- targetSession: findSessionForProject(targetProject)?.windowName
5474
- };
5475
- } catch {
5476
- return { allowed: true, reason: "no_session" };
5477
- }
5478
- }
5479
- var init_session_scope = __esm({
5480
- "src/lib/session-scope.ts"() {
5481
- "use strict";
5482
- init_session_registry();
5483
- init_project_name();
5484
- init_tmux_routing();
5485
- init_employees();
5486
- }
5487
- });
5488
-
5489
5756
  // src/lib/tasks-notify.ts
5490
5757
  async function dispatchTaskToEmployee(input) {
5491
5758
  if (isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
@@ -5553,10 +5820,10 @@ var init_tasks_notify = __esm({
5553
5820
  });
5554
5821
 
5555
5822
  // src/lib/behaviors.ts
5556
- import crypto4 from "crypto";
5823
+ import crypto5 from "crypto";
5557
5824
  async function storeBehavior(opts) {
5558
5825
  const client = getClient();
5559
- const id = crypto4.randomUUID();
5826
+ const id = crypto5.randomUUID();
5560
5827
  const now = (/* @__PURE__ */ new Date()).toISOString();
5561
5828
  await client.execute({
5562
5829
  sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
@@ -5585,7 +5852,7 @@ __export(skill_learning_exports, {
5585
5852
  storeTrajectory: () => storeTrajectory,
5586
5853
  sweepTrajectories: () => sweepTrajectories
5587
5854
  });
5588
- import crypto5 from "crypto";
5855
+ import crypto6 from "crypto";
5589
5856
  async function extractTrajectory(taskId, agentId) {
5590
5857
  const client = getClient();
5591
5858
  const result = await client.execute({
@@ -5614,11 +5881,11 @@ async function extractTrajectory(taskId, agentId) {
5614
5881
  return signature;
5615
5882
  }
5616
5883
  function hashSignature(signature) {
5617
- return crypto5.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
5884
+ return crypto6.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
5618
5885
  }
5619
5886
  async function storeTrajectory(opts) {
5620
5887
  const client = getClient();
5621
- const id = crypto5.randomUUID();
5888
+ const id = crypto6.randomUUID();
5622
5889
  const now = (/* @__PURE__ */ new Date()).toISOString();
5623
5890
  const signatureHash = hashSignature(opts.signature);
5624
5891
  await client.execute({
@@ -5883,8 +6150,8 @@ __export(tasks_exports, {
5883
6150
  updateTaskStatus: () => updateTaskStatus,
5884
6151
  writeCheckpoint: () => writeCheckpoint
5885
6152
  });
5886
- import path17 from "path";
5887
- import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync6, unlinkSync as unlinkSync5 } from "fs";
6153
+ import path18 from "path";
6154
+ import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, unlinkSync as unlinkSync5 } from "fs";
5888
6155
  async function createTask(input) {
5889
6156
  const result = await createTaskCore(input);
5890
6157
  if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -5903,12 +6170,12 @@ async function updateTask(input) {
5903
6170
  const { row, taskFile, now, taskId } = await updateTaskStatus(input);
5904
6171
  try {
5905
6172
  const agent = String(row.assigned_to);
5906
- const cacheDir = path17.join(EXE_AI_DIR, "session-cache");
5907
- const cachePath = path17.join(cacheDir, `current-task-${agent}.json`);
6173
+ const cacheDir = path18.join(EXE_AI_DIR, "session-cache");
6174
+ const cachePath = path18.join(cacheDir, `current-task-${agent}.json`);
5908
6175
  if (input.status === "in_progress") {
5909
6176
  mkdirSync6(cacheDir, { recursive: true });
5910
- writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
5911
- } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
6177
+ writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
6178
+ } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
5912
6179
  try {
5913
6180
  unlinkSync5(cachePath);
5914
6181
  } catch {
@@ -5916,10 +6183,10 @@ async function updateTask(input) {
5916
6183
  }
5917
6184
  } catch {
5918
6185
  }
5919
- if (input.status === "done") {
6186
+ if (input.status === "done" || input.status === "closed") {
5920
6187
  await cleanupReviewFile(row, taskFile, input.baseDir);
5921
6188
  }
5922
- if (input.status === "done" || input.status === "cancelled") {
6189
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
5923
6190
  try {
5924
6191
  const client = getClient();
5925
6192
  const taskTitle = String(row.title);
@@ -5935,7 +6202,7 @@ async function updateTask(input) {
5935
6202
  if (!isCoordinatorName(assignedAgent)) {
5936
6203
  try {
5937
6204
  const draftClient = getClient();
5938
- if (input.status === "done") {
6205
+ if (input.status === "done" || input.status === "closed") {
5939
6206
  await draftClient.execute({
5940
6207
  sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
5941
6208
  args: [assignedAgent]
@@ -5952,7 +6219,7 @@ async function updateTask(input) {
5952
6219
  try {
5953
6220
  const client = getClient();
5954
6221
  const cascaded = await client.execute({
5955
- sql: `UPDATE tasks SET status = 'done', updated_at = ?
6222
+ sql: `UPDATE tasks SET status = 'closed', updated_at = ?
5956
6223
  WHERE parent_task_id = ? AND status = 'needs_review'`,
5957
6224
  args: [now, taskId]
5958
6225
  });
@@ -5965,14 +6232,14 @@ async function updateTask(input) {
5965
6232
  } catch {
5966
6233
  }
5967
6234
  }
5968
- const isTerminal = input.status === "done" || input.status === "needs_review";
6235
+ const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
5969
6236
  if (isTerminal) {
5970
6237
  const isCoordinator = isCoordinatorName(String(row.assigned_to));
5971
6238
  if (!isCoordinator) {
5972
6239
  notifyTaskDone();
5973
6240
  }
5974
6241
  await markTaskNotificationsRead(taskFile);
5975
- if (input.status === "done") {
6242
+ if (input.status === "done" || input.status === "closed") {
5976
6243
  try {
5977
6244
  await cascadeUnblock(taskId, input.baseDir, now);
5978
6245
  } catch {
@@ -5992,7 +6259,7 @@ async function updateTask(input) {
5992
6259
  }
5993
6260
  }
5994
6261
  }
5995
- if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
6262
+ if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
5996
6263
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
5997
6264
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
5998
6265
  taskId,
@@ -6364,6 +6631,7 @@ __export(tmux_routing_exports, {
6364
6631
  isEmployeeAlive: () => isEmployeeAlive,
6365
6632
  isExeSession: () => isExeSession,
6366
6633
  isSessionBusy: () => isSessionBusy,
6634
+ notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
6367
6635
  notifyParentExe: () => notifyParentExe,
6368
6636
  parseParentExe: () => parseParentExe,
6369
6637
  registerParentExe: () => registerParentExe,
@@ -6374,13 +6642,13 @@ __export(tmux_routing_exports, {
6374
6642
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
6375
6643
  });
6376
6644
  import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
6377
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync7, mkdirSync as mkdirSync7, existsSync as existsSync14, appendFileSync, readdirSync as readdirSync4 } from "fs";
6378
- import path18 from "path";
6379
- import os10 from "os";
6645
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, mkdirSync as mkdirSync7, existsSync as existsSync16, appendFileSync, readdirSync as readdirSync4 } from "fs";
6646
+ import path19 from "path";
6647
+ import os11 from "os";
6380
6648
  import { fileURLToPath as fileURLToPath2 } from "url";
6381
6649
  import { unlinkSync as unlinkSync6 } from "fs";
6382
6650
  function spawnLockPath(sessionName) {
6383
- return path18.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
6651
+ return path19.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
6384
6652
  }
6385
6653
  function isProcessAlive(pid) {
6386
6654
  try {
@@ -6391,13 +6659,13 @@ function isProcessAlive(pid) {
6391
6659
  }
6392
6660
  }
6393
6661
  function acquireSpawnLock2(sessionName) {
6394
- if (!existsSync14(SPAWN_LOCK_DIR)) {
6662
+ if (!existsSync16(SPAWN_LOCK_DIR)) {
6395
6663
  mkdirSync7(SPAWN_LOCK_DIR, { recursive: true });
6396
6664
  }
6397
6665
  const lockFile = spawnLockPath(sessionName);
6398
- if (existsSync14(lockFile)) {
6666
+ if (existsSync16(lockFile)) {
6399
6667
  try {
6400
- const lock = JSON.parse(readFileSync11(lockFile, "utf8"));
6668
+ const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
6401
6669
  const age = Date.now() - lock.timestamp;
6402
6670
  if (isProcessAlive(lock.pid) && age < 6e4) {
6403
6671
  return false;
@@ -6405,7 +6673,7 @@ function acquireSpawnLock2(sessionName) {
6405
6673
  } catch {
6406
6674
  }
6407
6675
  }
6408
- writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
6676
+ writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
6409
6677
  return true;
6410
6678
  }
6411
6679
  function releaseSpawnLock2(sessionName) {
@@ -6417,13 +6685,13 @@ function releaseSpawnLock2(sessionName) {
6417
6685
  function resolveBehaviorsExporterScript() {
6418
6686
  try {
6419
6687
  const thisFile = fileURLToPath2(import.meta.url);
6420
- const scriptPath = path18.join(
6421
- path18.dirname(thisFile),
6688
+ const scriptPath = path19.join(
6689
+ path19.dirname(thisFile),
6422
6690
  "..",
6423
6691
  "bin",
6424
6692
  "exe-export-behaviors.js"
6425
6693
  );
6426
- return existsSync14(scriptPath) ? scriptPath : null;
6694
+ return existsSync16(scriptPath) ? scriptPath : null;
6427
6695
  } catch {
6428
6696
  return null;
6429
6697
  }
@@ -6489,12 +6757,12 @@ function extractRootExe(name) {
6489
6757
  return parts.length > 0 ? parts[parts.length - 1] : null;
6490
6758
  }
6491
6759
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
6492
- if (!existsSync14(SESSION_CACHE)) {
6760
+ if (!existsSync16(SESSION_CACHE)) {
6493
6761
  mkdirSync7(SESSION_CACHE, { recursive: true });
6494
6762
  }
6495
6763
  const rootExe = extractRootExe(parentExe) ?? parentExe;
6496
- const filePath = path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
6497
- writeFileSync7(filePath, JSON.stringify({
6764
+ const filePath = path19.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
6765
+ writeFileSync8(filePath, JSON.stringify({
6498
6766
  parentExe: rootExe,
6499
6767
  dispatchedBy: dispatchedBy || rootExe,
6500
6768
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -6502,7 +6770,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
6502
6770
  }
6503
6771
  function getParentExe(sessionKey) {
6504
6772
  try {
6505
- const data = JSON.parse(readFileSync11(path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
6773
+ const data = JSON.parse(readFileSync12(path19.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
6506
6774
  return data.parentExe || null;
6507
6775
  } catch {
6508
6776
  return null;
@@ -6510,8 +6778,8 @@ function getParentExe(sessionKey) {
6510
6778
  }
6511
6779
  function getDispatchedBy(sessionKey) {
6512
6780
  try {
6513
- const data = JSON.parse(readFileSync11(
6514
- path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
6781
+ const data = JSON.parse(readFileSync12(
6782
+ path19.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
6515
6783
  "utf8"
6516
6784
  ));
6517
6785
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -6581,8 +6849,8 @@ async function verifyPaneAtCapacity(sessionName) {
6581
6849
  }
6582
6850
  function readDebounceState() {
6583
6851
  try {
6584
- if (!existsSync14(DEBOUNCE_FILE)) return {};
6585
- const raw = JSON.parse(readFileSync11(DEBOUNCE_FILE, "utf8"));
6852
+ if (!existsSync16(DEBOUNCE_FILE)) return {};
6853
+ const raw = JSON.parse(readFileSync12(DEBOUNCE_FILE, "utf8"));
6586
6854
  const state = {};
6587
6855
  for (const [key, val] of Object.entries(raw)) {
6588
6856
  if (typeof val === "number") {
@@ -6598,8 +6866,8 @@ function readDebounceState() {
6598
6866
  }
6599
6867
  function writeDebounceState(state) {
6600
6868
  try {
6601
- if (!existsSync14(SESSION_CACHE)) mkdirSync7(SESSION_CACHE, { recursive: true });
6602
- writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
6869
+ if (!existsSync16(SESSION_CACHE)) mkdirSync7(SESSION_CACHE, { recursive: true });
6870
+ writeFileSync8(DEBOUNCE_FILE, JSON.stringify(state));
6603
6871
  } catch {
6604
6872
  }
6605
6873
  }
@@ -6697,8 +6965,8 @@ function sendIntercom(targetSession) {
6697
6965
  try {
6698
6966
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
6699
6967
  const agent = baseAgentName(rawAgent);
6700
- const markerPath = path18.join(SESSION_CACHE, `current-task-${agent}.json`);
6701
- if (existsSync14(markerPath)) {
6968
+ const markerPath = path19.join(SESSION_CACHE, `current-task-${agent}.json`);
6969
+ if (existsSync16(markerPath)) {
6702
6970
  logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
6703
6971
  return "debounced";
6704
6972
  }
@@ -6707,8 +6975,8 @@ function sendIntercom(targetSession) {
6707
6975
  try {
6708
6976
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
6709
6977
  const agent = baseAgentName(rawAgent);
6710
- const taskDir = path18.join(process.cwd(), "exe", agent);
6711
- if (existsSync14(taskDir)) {
6978
+ const taskDir = path19.join(process.cwd(), "exe", agent);
6979
+ if (existsSync16(taskDir)) {
6712
6980
  const files = readdirSync4(taskDir).filter(
6713
6981
  (f) => f.endsWith(".md") && f !== "DONE.txt"
6714
6982
  );
@@ -6768,6 +7036,21 @@ function notifyParentExe(sessionKey) {
6768
7036
  }
6769
7037
  return true;
6770
7038
  }
7039
+ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
7040
+ const transport = getTransport();
7041
+ try {
7042
+ const sessions = transport.listSessions();
7043
+ if (!sessions.includes(coordinatorSession)) return false;
7044
+ execSync7(
7045
+ `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
7046
+ { timeout: 3e3 }
7047
+ );
7048
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
7049
+ return true;
7050
+ } catch {
7051
+ return false;
7052
+ }
7053
+ }
6771
7054
  function ensureEmployee(employeeName, exeSession, projectDir, opts) {
6772
7055
  if (isCoordinatorName(employeeName)) {
6773
7056
  return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
@@ -6841,26 +7124,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6841
7124
  const transport = getTransport();
6842
7125
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
6843
7126
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
6844
- const logDir = path18.join(os10.homedir(), ".exe-os", "session-logs");
6845
- const logFile = path18.join(logDir, `${instanceLabel}-${Date.now()}.log`);
6846
- if (!existsSync14(logDir)) {
7127
+ const logDir = path19.join(os11.homedir(), ".exe-os", "session-logs");
7128
+ const logFile = path19.join(logDir, `${instanceLabel}-${Date.now()}.log`);
7129
+ if (!existsSync16(logDir)) {
6847
7130
  mkdirSync7(logDir, { recursive: true });
6848
7131
  }
6849
7132
  transport.kill(sessionName);
6850
7133
  let cleanupSuffix = "";
6851
7134
  try {
6852
7135
  const thisFile = fileURLToPath2(import.meta.url);
6853
- const cleanupScript = path18.join(path18.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
6854
- if (existsSync14(cleanupScript)) {
7136
+ const cleanupScript = path19.join(path19.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
7137
+ if (existsSync16(cleanupScript)) {
6855
7138
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
6856
7139
  }
6857
7140
  } catch {
6858
7141
  }
6859
7142
  try {
6860
- const claudeJsonPath = path18.join(os10.homedir(), ".claude.json");
7143
+ const claudeJsonPath = path19.join(os11.homedir(), ".claude.json");
6861
7144
  let claudeJson = {};
6862
7145
  try {
6863
- claudeJson = JSON.parse(readFileSync11(claudeJsonPath, "utf8"));
7146
+ claudeJson = JSON.parse(readFileSync12(claudeJsonPath, "utf8"));
6864
7147
  } catch {
6865
7148
  }
6866
7149
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -6868,17 +7151,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6868
7151
  const trustDir = opts?.cwd ?? projectDir;
6869
7152
  if (!projects[trustDir]) projects[trustDir] = {};
6870
7153
  projects[trustDir].hasTrustDialogAccepted = true;
6871
- writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
7154
+ writeFileSync8(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
6872
7155
  } catch {
6873
7156
  }
6874
7157
  try {
6875
- const settingsDir = path18.join(os10.homedir(), ".claude", "projects");
7158
+ const settingsDir = path19.join(os11.homedir(), ".claude", "projects");
6876
7159
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
6877
- const projSettingsDir = path18.join(settingsDir, normalizedKey);
6878
- const settingsPath = path18.join(projSettingsDir, "settings.json");
7160
+ const projSettingsDir = path19.join(settingsDir, normalizedKey);
7161
+ const settingsPath = path19.join(projSettingsDir, "settings.json");
6879
7162
  let settings = {};
6880
7163
  try {
6881
- settings = JSON.parse(readFileSync11(settingsPath, "utf8"));
7164
+ settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
6882
7165
  } catch {
6883
7166
  }
6884
7167
  const perms = settings.permissions ?? {};
@@ -6907,7 +7190,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6907
7190
  perms.allow = allow;
6908
7191
  settings.permissions = perms;
6909
7192
  mkdirSync7(projSettingsDir, { recursive: true });
6910
- writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + "\n");
7193
+ writeFileSync8(settingsPath, JSON.stringify(settings, null, 2) + "\n");
6911
7194
  }
6912
7195
  } catch {
6913
7196
  }
@@ -6922,8 +7205,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6922
7205
  let behaviorsFlag = "";
6923
7206
  let legacyFallbackWarned = false;
6924
7207
  if (!useExeAgent && !useBinSymlink) {
6925
- const identityPath = path18.join(
6926
- os10.homedir(),
7208
+ const identityPath = path19.join(
7209
+ os11.homedir(),
6927
7210
  ".exe-os",
6928
7211
  "identity",
6929
7212
  `${employeeName}.md`
@@ -6932,13 +7215,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6932
7215
  const hasAgentFlag = claudeSupportsAgentFlag();
6933
7216
  if (hasAgentFlag) {
6934
7217
  identityFlag = ` --agent ${employeeName}`;
6935
- } else if (existsSync14(identityPath)) {
7218
+ } else if (existsSync16(identityPath)) {
6936
7219
  identityFlag = ` --append-system-prompt-file ${identityPath}`;
6937
7220
  legacyFallbackWarned = true;
6938
7221
  }
6939
7222
  const behaviorsFile = exportBehaviorsSync(
6940
7223
  employeeName,
6941
- path18.basename(spawnCwd),
7224
+ path19.basename(spawnCwd),
6942
7225
  sessionName
6943
7226
  );
6944
7227
  if (behaviorsFile) {
@@ -6953,16 +7236,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6953
7236
  }
6954
7237
  let sessionContextFlag = "";
6955
7238
  try {
6956
- const ctxDir = path18.join(os10.homedir(), ".exe-os", "session-cache");
7239
+ const ctxDir = path19.join(os11.homedir(), ".exe-os", "session-cache");
6957
7240
  mkdirSync7(ctxDir, { recursive: true });
6958
- const ctxFile = path18.join(ctxDir, `session-context-${sessionName}.md`);
7241
+ const ctxFile = path19.join(ctxDir, `session-context-${sessionName}.md`);
6959
7242
  const ctxContent = [
6960
7243
  `## Session Context`,
6961
7244
  `You are running in tmux session: ${sessionName}.`,
6962
7245
  `Your parent coordinator session is ${exeSession}.`,
6963
7246
  `Your employees (if any) use the -${exeSession} suffix.`
6964
7247
  ].join("\n");
6965
- writeFileSync7(ctxFile, ctxContent);
7248
+ writeFileSync8(ctxFile, ctxContent);
6966
7249
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
6967
7250
  } catch {
6968
7251
  }
@@ -7039,8 +7322,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
7039
7322
  transport.pipeLog(sessionName, logFile);
7040
7323
  try {
7041
7324
  const mySession = getMySession();
7042
- const dispatchInfo = path18.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
7043
- writeFileSync7(dispatchInfo, JSON.stringify({
7325
+ const dispatchInfo = path19.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
7326
+ writeFileSync8(dispatchInfo, JSON.stringify({
7044
7327
  dispatchedBy: mySession,
7045
7328
  rootExe: exeSession,
7046
7329
  provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
@@ -7114,15 +7397,15 @@ var init_tmux_routing = __esm({
7114
7397
  init_intercom_queue();
7115
7398
  init_plan_limits();
7116
7399
  init_employees();
7117
- SPAWN_LOCK_DIR = path18.join(os10.homedir(), ".exe-os", "spawn-locks");
7118
- SESSION_CACHE = path18.join(os10.homedir(), ".exe-os", "session-cache");
7400
+ SPAWN_LOCK_DIR = path19.join(os11.homedir(), ".exe-os", "spawn-locks");
7401
+ SESSION_CACHE = path19.join(os11.homedir(), ".exe-os", "session-cache");
7119
7402
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
7120
7403
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
7121
7404
  VERIFY_PANE_LINES = 200;
7122
7405
  INTERCOM_DEBOUNCE_MS = 3e4;
7123
7406
  CODEX_DEBOUNCE_MS = 12e4;
7124
- INTERCOM_LOG2 = path18.join(os10.homedir(), ".exe-os", "intercom.log");
7125
- DEBOUNCE_FILE = path18.join(SESSION_CACHE, "intercom-debounce.json");
7407
+ INTERCOM_LOG2 = path19.join(os11.homedir(), ".exe-os", "intercom.log");
7408
+ DEBOUNCE_FILE = path19.join(SESSION_CACHE, "intercom-debounce.json");
7126
7409
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
7127
7410
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
7128
7411
  }
@@ -7145,6 +7428,15 @@ function sessionScopeFilter(sessionScope, tableAlias) {
7145
7428
  args: [scope]
7146
7429
  };
7147
7430
  }
7431
+ function strictSessionScopeFilter(sessionScope, tableAlias) {
7432
+ const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
7433
+ if (!scope) return { sql: "", args: [] };
7434
+ const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
7435
+ return {
7436
+ sql: ` AND ${col} = ?`,
7437
+ args: [scope]
7438
+ };
7439
+ }
7148
7440
  var init_task_scope = __esm({
7149
7441
  "src/lib/task-scope.ts"() {
7150
7442
  "use strict";
@@ -7179,14 +7471,14 @@ __export(worker_gate_exports, {
7179
7471
  tryAcquireBackfillLock: () => tryAcquireBackfillLock,
7180
7472
  tryAcquireWorkerSlot: () => tryAcquireWorkerSlot
7181
7473
  });
7182
- import { readdirSync as readdirSync6, writeFileSync as writeFileSync9, unlinkSync as unlinkSync8, mkdirSync as mkdirSync9, existsSync as existsSync15 } from "fs";
7183
- import path20 from "path";
7474
+ import { readdirSync as readdirSync6, writeFileSync as writeFileSync10, unlinkSync as unlinkSync8, mkdirSync as mkdirSync9, existsSync as existsSync17 } from "fs";
7475
+ import path21 from "path";
7184
7476
  function tryAcquireWorkerSlot() {
7185
7477
  try {
7186
7478
  mkdirSync9(WORKER_PID_DIR, { recursive: true });
7187
7479
  const reservationId = `res-${process.pid}-${Date.now()}`;
7188
- const reservationPath = path20.join(WORKER_PID_DIR, `${reservationId}.pid`);
7189
- writeFileSync9(reservationPath, String(process.pid));
7480
+ const reservationPath = path21.join(WORKER_PID_DIR, `${reservationId}.pid`);
7481
+ writeFileSync10(reservationPath, String(process.pid));
7190
7482
  const files = readdirSync6(WORKER_PID_DIR);
7191
7483
  let alive = 0;
7192
7484
  for (const f of files) {
@@ -7203,7 +7495,7 @@ function tryAcquireWorkerSlot() {
7203
7495
  alive++;
7204
7496
  } catch {
7205
7497
  try {
7206
- unlinkSync8(path20.join(WORKER_PID_DIR, f));
7498
+ unlinkSync8(path21.join(WORKER_PID_DIR, f));
7207
7499
  } catch {
7208
7500
  }
7209
7501
  }
@@ -7227,20 +7519,20 @@ function tryAcquireWorkerSlot() {
7227
7519
  function registerWorkerPid(pid) {
7228
7520
  try {
7229
7521
  mkdirSync9(WORKER_PID_DIR, { recursive: true });
7230
- writeFileSync9(path20.join(WORKER_PID_DIR, `worker-${pid}.pid`), String(pid));
7522
+ writeFileSync10(path21.join(WORKER_PID_DIR, `worker-${pid}.pid`), String(pid));
7231
7523
  } catch {
7232
7524
  }
7233
7525
  }
7234
7526
  function cleanupWorkerPid() {
7235
7527
  try {
7236
- unlinkSync8(path20.join(WORKER_PID_DIR, `worker-${process.pid}.pid`));
7528
+ unlinkSync8(path21.join(WORKER_PID_DIR, `worker-${process.pid}.pid`));
7237
7529
  } catch {
7238
7530
  }
7239
7531
  }
7240
7532
  function tryAcquireBackfillLock() {
7241
7533
  try {
7242
7534
  mkdirSync9(WORKER_PID_DIR, { recursive: true });
7243
- if (existsSync15(BACKFILL_LOCK)) {
7535
+ if (existsSync17(BACKFILL_LOCK)) {
7244
7536
  try {
7245
7537
  const pid = parseInt(
7246
7538
  __require("fs").readFileSync(BACKFILL_LOCK, "utf8").trim(),
@@ -7256,7 +7548,7 @@ function tryAcquireBackfillLock() {
7256
7548
  } catch {
7257
7549
  }
7258
7550
  }
7259
- writeFileSync9(BACKFILL_LOCK, String(process.pid));
7551
+ writeFileSync10(BACKFILL_LOCK, String(process.pid));
7260
7552
  return true;
7261
7553
  } catch {
7262
7554
  return true;
@@ -7273,9 +7565,9 @@ var init_worker_gate = __esm({
7273
7565
  "src/lib/worker-gate.ts"() {
7274
7566
  "use strict";
7275
7567
  init_config();
7276
- WORKER_PID_DIR = path20.join(EXE_AI_DIR, "worker-pids");
7568
+ WORKER_PID_DIR = path21.join(EXE_AI_DIR, "worker-pids");
7277
7569
  MAX_CONCURRENT_WORKERS = 3;
7278
- BACKFILL_LOCK = path20.join(WORKER_PID_DIR, "backfill.lock");
7570
+ BACKFILL_LOCK = path21.join(WORKER_PID_DIR, "backfill.lock");
7279
7571
  }
7280
7572
  });
7281
7573
 
@@ -7287,13 +7579,13 @@ __export(crypto_exports, {
7287
7579
  initSyncCrypto: () => initSyncCrypto,
7288
7580
  isSyncCryptoInitialized: () => isSyncCryptoInitialized
7289
7581
  });
7290
- import crypto6 from "crypto";
7582
+ import crypto7 from "crypto";
7291
7583
  function initSyncCrypto(masterKey) {
7292
7584
  if (masterKey.length !== 32) {
7293
7585
  throw new Error(`Master key must be 32 bytes, got ${masterKey.length}`);
7294
7586
  }
7295
7587
  _syncKey = Buffer.from(
7296
- crypto6.hkdfSync("sha256", masterKey, "", SYNC_HKDF_INFO, 32)
7588
+ crypto7.hkdfSync("sha256", masterKey, "", SYNC_HKDF_INFO, 32)
7297
7589
  );
7298
7590
  }
7299
7591
  function isSyncCryptoInitialized() {
@@ -7307,8 +7599,8 @@ function requireSyncKey() {
7307
7599
  }
7308
7600
  function encryptSyncBlob(data) {
7309
7601
  const key = requireSyncKey();
7310
- const iv = crypto6.randomBytes(IV_LENGTH);
7311
- const cipher = crypto6.createCipheriv(ALGORITHM, key, iv);
7602
+ const iv = crypto7.randomBytes(IV_LENGTH);
7603
+ const cipher = crypto7.createCipheriv(ALGORITHM, key, iv);
7312
7604
  const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
7313
7605
  const tag = cipher.getAuthTag();
7314
7606
  return Buffer.concat([iv, encrypted, tag]).toString("base64");
@@ -7322,7 +7614,7 @@ function decryptSyncBlob(ciphertext) {
7322
7614
  const iv = combined.subarray(0, IV_LENGTH);
7323
7615
  const tag = combined.subarray(combined.length - TAG_LENGTH);
7324
7616
  const encrypted = combined.subarray(IV_LENGTH, combined.length - TAG_LENGTH);
7325
- const decipher = crypto6.createDecipheriv(ALGORITHM, key, iv);
7617
+ const decipher = crypto7.createDecipheriv(ALGORITHM, key, iv);
7326
7618
  decipher.setAuthTag(tag);
7327
7619
  return Buffer.concat([decipher.update(encrypted), decipher.final()]);
7328
7620
  }
@@ -7377,8 +7669,8 @@ __export(crdt_sync_exports, {
7377
7669
  rebuildFromDb: () => rebuildFromDb
7378
7670
  });
7379
7671
  import * as Y from "yjs";
7380
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync10, existsSync as existsSync16, mkdirSync as mkdirSync10, unlinkSync as unlinkSync9 } from "fs";
7381
- import path21 from "path";
7672
+ import { readFileSync as readFileSync14, writeFileSync as writeFileSync11, existsSync as existsSync18, mkdirSync as mkdirSync10, unlinkSync as unlinkSync9 } from "fs";
7673
+ import path22 from "path";
7382
7674
  import { homedir } from "os";
7383
7675
  function getStatePath() {
7384
7676
  return _statePathOverride ?? DEFAULT_STATE_PATH;
@@ -7390,9 +7682,9 @@ function initCrdtDoc() {
7390
7682
  if (doc) return doc;
7391
7683
  doc = new Y.Doc();
7392
7684
  const sp = getStatePath();
7393
- if (existsSync16(sp)) {
7685
+ if (existsSync18(sp)) {
7394
7686
  try {
7395
- const state = readFileSync13(sp);
7687
+ const state = readFileSync14(sp);
7396
7688
  Y.applyUpdate(doc, new Uint8Array(state));
7397
7689
  } catch {
7398
7690
  console.warn("[crdt-sync] WARN: corrupted state file, rebuilding from DB");
@@ -7534,10 +7826,10 @@ function persistState() {
7534
7826
  if (!doc) return;
7535
7827
  try {
7536
7828
  const sp = getStatePath();
7537
- const dir = path21.dirname(sp);
7538
- if (!existsSync16(dir)) mkdirSync10(dir, { recursive: true });
7829
+ const dir = path22.dirname(sp);
7830
+ if (!existsSync18(dir)) mkdirSync10(dir, { recursive: true });
7539
7831
  const state = Y.encodeStateAsUpdate(doc);
7540
- writeFileSync10(sp, Buffer.from(state));
7832
+ writeFileSync11(sp, Buffer.from(state));
7541
7833
  } catch {
7542
7834
  }
7543
7835
  }
@@ -7578,7 +7870,7 @@ var DEFAULT_STATE_PATH, _statePathOverride, doc;
7578
7870
  var init_crdt_sync = __esm({
7579
7871
  "src/lib/crdt-sync.ts"() {
7580
7872
  "use strict";
7581
- DEFAULT_STATE_PATH = path21.join(homedir(), ".exe-os", "crdt-state.bin");
7873
+ DEFAULT_STATE_PATH = path22.join(homedir(), ".exe-os", "crdt-state.bin");
7582
7874
  _statePathOverride = null;
7583
7875
  doc = null;
7584
7876
  }
@@ -7610,39 +7902,107 @@ __export(cloud_sync_exports, {
7610
7902
  cloudSync: () => cloudSync,
7611
7903
  mergeConfig: () => mergeConfig,
7612
7904
  mergeRosterFromRemote: () => mergeRosterFromRemote,
7905
+ pushToPostgres: () => pushToPostgres,
7613
7906
  recordRosterDeletion: () => recordRosterDeletion
7614
7907
  });
7615
- import { readFileSync as readFileSync14, writeFileSync as writeFileSync11, existsSync as existsSync17, readdirSync as readdirSync7, mkdirSync as mkdirSync11, appendFileSync as appendFileSync2, unlinkSync as unlinkSync10, openSync as openSync2, closeSync as closeSync2 } from "fs";
7616
- import crypto7 from "crypto";
7617
- import path22 from "path";
7908
+ import { readFileSync as readFileSync15, writeFileSync as writeFileSync12, existsSync as existsSync19, readdirSync as readdirSync7, mkdirSync as mkdirSync11, appendFileSync as appendFileSync2, unlinkSync as unlinkSync10, openSync as openSync2, closeSync as closeSync2 } from "fs";
7909
+ import crypto8 from "crypto";
7910
+ import path23 from "path";
7618
7911
  import { homedir as homedir2 } from "os";
7619
7912
  function sqlSafe(v) {
7620
7913
  return v === void 0 ? null : v;
7621
7914
  }
7622
7915
  function logError(msg) {
7623
7916
  try {
7624
- const logPath = path22.join(homedir2(), ".exe-os", "workers.log");
7917
+ const logPath = path23.join(homedir2(), ".exe-os", "workers.log");
7625
7918
  appendFileSync2(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
7626
7919
  `);
7627
7920
  } catch {
7628
7921
  }
7629
7922
  }
7923
+ function loadPgClient() {
7924
+ if (_pgFailed) return null;
7925
+ const postgresUrl = process.env.DATABASE_URL;
7926
+ const configPath = path23.join(EXE_AI_DIR, "config.json");
7927
+ let cloudPostgresUrl;
7928
+ try {
7929
+ if (existsSync19(configPath)) {
7930
+ const cfg = JSON.parse(readFileSync15(configPath, "utf8"));
7931
+ cloudPostgresUrl = cfg.cloud?.postgresUrl;
7932
+ if (cfg.cloud?.syncToPostgres === false) {
7933
+ _pgFailed = true;
7934
+ return null;
7935
+ }
7936
+ }
7937
+ } catch {
7938
+ }
7939
+ const url = postgresUrl || cloudPostgresUrl;
7940
+ if (!url) {
7941
+ _pgFailed = true;
7942
+ return null;
7943
+ }
7944
+ if (!_pgPromise) {
7945
+ _pgPromise = (async () => {
7946
+ const { createRequire: createRequire3 } = await import("module");
7947
+ const { pathToFileURL: pathToFileURL3 } = await import("url");
7948
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path23.join(homedir2(), "exe-db");
7949
+ const req = createRequire3(path23.join(exeDbRoot, "package.json"));
7950
+ const entry = req.resolve("@prisma/client");
7951
+ const mod = await import(pathToFileURL3(entry).href);
7952
+ const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
7953
+ if (!Ctor) throw new Error("No PrismaClient");
7954
+ return new Ctor();
7955
+ })().catch(() => {
7956
+ _pgFailed = true;
7957
+ _pgPromise = null;
7958
+ throw new Error("pg_unavailable");
7959
+ });
7960
+ }
7961
+ return _pgPromise;
7962
+ }
7963
+ async function pushToPostgres(records) {
7964
+ const loader = loadPgClient();
7965
+ if (!loader) return 0;
7966
+ let prisma;
7967
+ try {
7968
+ prisma = await loader;
7969
+ } catch {
7970
+ return 0;
7971
+ }
7972
+ let inserted = 0;
7973
+ for (const rec of records) {
7974
+ try {
7975
+ await prisma.$executeRawUnsafe(
7976
+ `INSERT INTO raw.raw_events (id, source, source_id, event_type, payload, metadata, timestamp)
7977
+ VALUES (gen_random_uuid(), 'cloud_sync', $1, 'memory', $2::jsonb, $3::jsonb, $4)
7978
+ ON CONFLICT (source, source_id, event_type) DO NOTHING`,
7979
+ String(rec.id ?? ""),
7980
+ JSON.stringify(rec),
7981
+ JSON.stringify({ agent_id: rec.agent_id, project_name: rec.project_name, tool_name: rec.tool_name }),
7982
+ rec.timestamp ? new Date(String(rec.timestamp)) : /* @__PURE__ */ new Date()
7983
+ );
7984
+ inserted++;
7985
+ } catch {
7986
+ }
7987
+ }
7988
+ return inserted;
7989
+ }
7630
7990
  async function withRosterLock(fn) {
7631
7991
  try {
7632
7992
  const fd = openSync2(ROSTER_LOCK_PATH, "wx");
7633
7993
  closeSync2(fd);
7634
- writeFileSync11(ROSTER_LOCK_PATH, String(Date.now()));
7994
+ writeFileSync12(ROSTER_LOCK_PATH, String(Date.now()));
7635
7995
  } catch (err) {
7636
7996
  if (err.code === "EEXIST") {
7637
7997
  try {
7638
- const ts = parseInt(readFileSync14(ROSTER_LOCK_PATH, "utf-8"), 10);
7998
+ const ts = parseInt(readFileSync15(ROSTER_LOCK_PATH, "utf-8"), 10);
7639
7999
  if (Date.now() - ts < LOCK_STALE_MS) {
7640
8000
  throw new Error("Roster merge already in progress \u2014 another sync is running");
7641
8001
  }
7642
8002
  unlinkSync10(ROSTER_LOCK_PATH);
7643
8003
  const fd = openSync2(ROSTER_LOCK_PATH, "wx");
7644
8004
  closeSync2(fd);
7645
- writeFileSync11(ROSTER_LOCK_PATH, String(Date.now()));
8005
+ writeFileSync12(ROSTER_LOCK_PATH, String(Date.now()));
7646
8006
  } catch (retryErr) {
7647
8007
  if (retryErr instanceof Error && retryErr.message.includes("already in progress")) throw retryErr;
7648
8008
  throw new Error("Roster merge already in progress \u2014 another sync is running");
@@ -7912,6 +8272,10 @@ async function cloudSync(config) {
7912
8272
  const maxVersion = Number(records[records.length - 1].version);
7913
8273
  const pushOk = await cloudPush(records, maxVersion, config);
7914
8274
  if (!pushOk) break;
8275
+ try {
8276
+ await pushToPostgres(records);
8277
+ } catch {
8278
+ }
7915
8279
  await client.execute({
7916
8280
  sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_cloud_push_version', ?)",
7917
8281
  args: [String(maxVersion)]
@@ -8016,8 +8380,8 @@ async function cloudSync(config) {
8016
8380
  try {
8017
8381
  const employees = await loadEmployees();
8018
8382
  rosterResult.employees = employees.length;
8019
- const idDir = path22.join(EXE_AI_DIR, "identity");
8020
- if (existsSync17(idDir)) {
8383
+ const idDir = path23.join(EXE_AI_DIR, "identity");
8384
+ if (existsSync19(idDir)) {
8021
8385
  rosterResult.identities = readdirSync7(idDir).filter((f) => f.endsWith(".md")).length;
8022
8386
  }
8023
8387
  } catch {
@@ -8038,62 +8402,62 @@ async function cloudSync(config) {
8038
8402
  function recordRosterDeletion(name) {
8039
8403
  let deletions = [];
8040
8404
  try {
8041
- if (existsSync17(ROSTER_DELETIONS_PATH)) {
8042
- deletions = JSON.parse(readFileSync14(ROSTER_DELETIONS_PATH, "utf-8"));
8405
+ if (existsSync19(ROSTER_DELETIONS_PATH)) {
8406
+ deletions = JSON.parse(readFileSync15(ROSTER_DELETIONS_PATH, "utf-8"));
8043
8407
  }
8044
8408
  } catch {
8045
8409
  }
8046
8410
  if (!deletions.includes(name)) deletions.push(name);
8047
- writeFileSync11(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
8411
+ writeFileSync12(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
8048
8412
  }
8049
8413
  function consumeRosterDeletions() {
8050
8414
  try {
8051
- if (!existsSync17(ROSTER_DELETIONS_PATH)) return [];
8052
- const deletions = JSON.parse(readFileSync14(ROSTER_DELETIONS_PATH, "utf-8"));
8053
- writeFileSync11(ROSTER_DELETIONS_PATH, "[]");
8415
+ if (!existsSync19(ROSTER_DELETIONS_PATH)) return [];
8416
+ const deletions = JSON.parse(readFileSync15(ROSTER_DELETIONS_PATH, "utf-8"));
8417
+ writeFileSync12(ROSTER_DELETIONS_PATH, "[]");
8054
8418
  return deletions;
8055
8419
  } catch {
8056
8420
  return [];
8057
8421
  }
8058
8422
  }
8059
8423
  function buildRosterBlob(paths) {
8060
- const rosterPath = paths?.rosterPath ?? path22.join(EXE_AI_DIR, "exe-employees.json");
8061
- const identityDir = paths?.identityDir ?? path22.join(EXE_AI_DIR, "identity");
8062
- const configPath = paths?.configPath ?? path22.join(EXE_AI_DIR, "config.json");
8424
+ const rosterPath = paths?.rosterPath ?? path23.join(EXE_AI_DIR, "exe-employees.json");
8425
+ const identityDir = paths?.identityDir ?? path23.join(EXE_AI_DIR, "identity");
8426
+ const configPath = paths?.configPath ?? path23.join(EXE_AI_DIR, "config.json");
8063
8427
  let roster = [];
8064
- if (existsSync17(rosterPath)) {
8428
+ if (existsSync19(rosterPath)) {
8065
8429
  try {
8066
- roster = JSON.parse(readFileSync14(rosterPath, "utf-8"));
8430
+ roster = JSON.parse(readFileSync15(rosterPath, "utf-8"));
8067
8431
  } catch {
8068
8432
  }
8069
8433
  }
8070
8434
  const identities = {};
8071
- if (existsSync17(identityDir)) {
8435
+ if (existsSync19(identityDir)) {
8072
8436
  for (const file of readdirSync7(identityDir).filter((f) => f.endsWith(".md"))) {
8073
8437
  try {
8074
- identities[file] = readFileSync14(path22.join(identityDir, file), "utf-8");
8438
+ identities[file] = readFileSync15(path23.join(identityDir, file), "utf-8");
8075
8439
  } catch {
8076
8440
  }
8077
8441
  }
8078
8442
  }
8079
8443
  let config;
8080
- if (existsSync17(configPath)) {
8444
+ if (existsSync19(configPath)) {
8081
8445
  try {
8082
- config = JSON.parse(readFileSync14(configPath, "utf-8"));
8446
+ config = JSON.parse(readFileSync15(configPath, "utf-8"));
8083
8447
  } catch {
8084
8448
  }
8085
8449
  }
8086
8450
  let agentConfig;
8087
- const agentConfigPath = path22.join(EXE_AI_DIR, "agent-config.json");
8088
- if (existsSync17(agentConfigPath)) {
8451
+ const agentConfigPath = path23.join(EXE_AI_DIR, "agent-config.json");
8452
+ if (existsSync19(agentConfigPath)) {
8089
8453
  try {
8090
- agentConfig = JSON.parse(readFileSync14(agentConfigPath, "utf-8"));
8454
+ agentConfig = JSON.parse(readFileSync15(agentConfigPath, "utf-8"));
8091
8455
  } catch {
8092
8456
  }
8093
8457
  }
8094
8458
  const deletedNames = consumeRosterDeletions();
8095
8459
  const content = JSON.stringify({ roster, identities, config, agentConfig, deletedNames });
8096
- const hash = crypto7.createHash("sha256").update(content).digest("hex").slice(0, 16);
8460
+ const hash = crypto8.createHash("sha256").update(content).digest("hex").slice(0, 16);
8097
8461
  return { roster, identities, config, agentConfig, deletedNames, version: hash };
8098
8462
  }
8099
8463
  async function cloudPushRoster(config) {
@@ -8163,23 +8527,24 @@ async function cloudPullRoster(config) {
8163
8527
  }
8164
8528
  }
8165
8529
  function mergeConfig(remoteConfig, configPath) {
8166
- const cfgPath = configPath ?? path22.join(EXE_AI_DIR, "config.json");
8530
+ const cfgPath = configPath ?? path23.join(EXE_AI_DIR, "config.json");
8167
8531
  let local = {};
8168
- if (existsSync17(cfgPath)) {
8532
+ if (existsSync19(cfgPath)) {
8169
8533
  try {
8170
- local = JSON.parse(readFileSync14(cfgPath, "utf-8"));
8534
+ local = JSON.parse(readFileSync15(cfgPath, "utf-8"));
8171
8535
  } catch {
8172
8536
  }
8173
8537
  }
8174
8538
  const merged = { ...remoteConfig, ...local };
8175
- const dir = path22.dirname(cfgPath);
8176
- if (!existsSync17(dir)) mkdirSync11(dir, { recursive: true });
8177
- writeFileSync11(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
8539
+ const dir = path23.dirname(cfgPath);
8540
+ ensurePrivateDirSync(dir);
8541
+ writeFileSync12(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
8542
+ enforcePrivateFileSync(cfgPath);
8178
8543
  }
8179
8544
  async function mergeRosterFromRemote(remote, paths) {
8180
8545
  return withRosterLock(async () => {
8181
8546
  const rosterPath = paths?.rosterPath ?? void 0;
8182
- const identityDir = paths?.identityDir ?? path22.join(EXE_AI_DIR, "identity");
8547
+ const identityDir = paths?.identityDir ?? path23.join(EXE_AI_DIR, "identity");
8183
8548
  const localEmployees = await loadEmployees(rosterPath);
8184
8549
  const localNames = new Set(localEmployees.map((e) => e.name));
8185
8550
  let added = 0;
@@ -8200,15 +8565,15 @@ async function mergeRosterFromRemote(remote, paths) {
8200
8565
  ) ?? lookupKey;
8201
8566
  const remoteIdentity = remote.identities[matchedKey];
8202
8567
  if (remoteIdentity) {
8203
- if (!existsSync17(identityDir)) mkdirSync11(identityDir, { recursive: true });
8204
- const idPath = path22.join(identityDir, `${remoteEmp.name}.md`);
8568
+ if (!existsSync19(identityDir)) mkdirSync11(identityDir, { recursive: true });
8569
+ const idPath = path23.join(identityDir, `${remoteEmp.name}.md`);
8205
8570
  let localIdentity = null;
8206
8571
  try {
8207
- localIdentity = existsSync17(idPath) ? readFileSync14(idPath, "utf-8") : null;
8572
+ localIdentity = existsSync19(idPath) ? readFileSync15(idPath, "utf-8") : null;
8208
8573
  } catch {
8209
8574
  }
8210
8575
  if (localIdentity !== remoteIdentity) {
8211
- writeFileSync11(idPath, remoteIdentity, "utf-8");
8576
+ writeFileSync12(idPath, remoteIdentity, "utf-8");
8212
8577
  identitiesUpdated++;
8213
8578
  }
8214
8579
  }
@@ -8234,16 +8599,18 @@ async function mergeRosterFromRemote(remote, paths) {
8234
8599
  }
8235
8600
  if (remote.agentConfig && Object.keys(remote.agentConfig).length > 0) {
8236
8601
  try {
8237
- const agentConfigPath = path22.join(EXE_AI_DIR, "agent-config.json");
8602
+ const agentConfigPath = path23.join(EXE_AI_DIR, "agent-config.json");
8238
8603
  let local = {};
8239
- if (existsSync17(agentConfigPath)) {
8604
+ if (existsSync19(agentConfigPath)) {
8240
8605
  try {
8241
- local = JSON.parse(readFileSync14(agentConfigPath, "utf-8"));
8606
+ local = JSON.parse(readFileSync15(agentConfigPath, "utf-8"));
8242
8607
  } catch {
8243
8608
  }
8244
8609
  }
8245
8610
  const merged = { ...remote.agentConfig, ...local };
8246
- writeFileSync11(agentConfigPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
8611
+ ensurePrivateDirSync(path23.dirname(agentConfigPath));
8612
+ writeFileSync12(agentConfigPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
8613
+ enforcePrivateFileSync(agentConfigPath);
8247
8614
  } catch {
8248
8615
  }
8249
8616
  }
@@ -8667,7 +9034,7 @@ async function cloudPullDocuments(config) {
8667
9034
  }
8668
9035
  return { pulled };
8669
9036
  }
8670
- var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS, PUSH_BATCH_SIZE, ROSTER_LOCK_PATH, LOCK_STALE_MS, ROSTER_DELETIONS_PATH;
9037
+ var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS, PUSH_BATCH_SIZE, ROSTER_LOCK_PATH, LOCK_STALE_MS, _pgPromise, _pgFailed, ROSTER_DELETIONS_PATH;
8671
9038
  var init_cloud_sync = __esm({
8672
9039
  "src/lib/cloud-sync.ts"() {
8673
9040
  "use strict";
@@ -8678,12 +9045,15 @@ var init_cloud_sync = __esm({
8678
9045
  init_config();
8679
9046
  init_crdt_sync();
8680
9047
  init_employees();
9048
+ init_secure_files();
8681
9049
  LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
8682
9050
  FETCH_TIMEOUT_MS = 3e4;
8683
9051
  PUSH_BATCH_SIZE = 5e3;
8684
- ROSTER_LOCK_PATH = path22.join(EXE_AI_DIR, "roster-merge.lock");
9052
+ ROSTER_LOCK_PATH = path23.join(EXE_AI_DIR, "roster-merge.lock");
8685
9053
  LOCK_STALE_MS = 3e4;
8686
- ROSTER_DELETIONS_PATH = path22.join(EXE_AI_DIR, "roster-deletions.json");
9054
+ _pgPromise = null;
9055
+ _pgFailed = false;
9056
+ ROSTER_DELETIONS_PATH = path23.join(EXE_AI_DIR, "roster-deletions.json");
8687
9057
  }
8688
9058
  });
8689
9059
 
@@ -8695,7 +9065,7 @@ __export(schedules_exports, {
8695
9065
  listSchedules: () => listSchedules,
8696
9066
  parseHumanCron: () => parseHumanCron
8697
9067
  });
8698
- import crypto8 from "crypto";
9068
+ import crypto9 from "crypto";
8699
9069
  import { execSync as execSync9 } from "child_process";
8700
9070
  async function ensureDb() {
8701
9071
  if (!isInitialized()) {
@@ -8764,7 +9134,7 @@ function parseHumanCron(input) {
8764
9134
  async function createSchedule(input) {
8765
9135
  await ensureDb();
8766
9136
  const client = getClient();
8767
- const id = crypto8.randomUUID().slice(0, 8);
9137
+ const id = crypto9.randomUUID().slice(0, 8);
8768
9138
  const now = (/* @__PURE__ */ new Date()).toISOString();
8769
9139
  const prompt = input.prompt ?? input.description;
8770
9140
  await client.execute({
@@ -8865,10 +9235,10 @@ var init_schedules = __esm({
8865
9235
 
8866
9236
  // src/bin/exe-boot.ts
8867
9237
  init_employees();
8868
- import path23 from "path";
9238
+ import path24 from "path";
8869
9239
  import { mkdir as mkdir5, writeFile as writeFile6 } from "fs/promises";
8870
- import { existsSync as existsSync18, readFileSync as readFileSync15, readdirSync as readdirSync8, unlinkSync as unlinkSync11 } from "fs";
8871
- import os11 from "os";
9240
+ import { existsSync as existsSync20, readFileSync as readFileSync16, readdirSync as readdirSync8, unlinkSync as unlinkSync11 } from "fs";
9241
+ import os12 from "os";
8872
9242
 
8873
9243
  // src/lib/employee-templates.ts
8874
9244
  init_global_procedures();
@@ -9370,18 +9740,18 @@ init_notifications();
9370
9740
  init_config();
9371
9741
  init_session_key();
9372
9742
  init_employees();
9373
- import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, mkdirSync as mkdirSync8, unlinkSync as unlinkSync7, readdirSync as readdirSync5 } from "fs";
9743
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync9, mkdirSync as mkdirSync8, unlinkSync as unlinkSync7, readdirSync as readdirSync5 } from "fs";
9374
9744
  import { execSync as execSync8 } from "child_process";
9375
- import path19 from "path";
9376
- var CACHE_DIR = path19.join(EXE_AI_DIR, "session-cache");
9745
+ import path20 from "path";
9746
+ var CACHE_DIR = path20.join(EXE_AI_DIR, "session-cache");
9377
9747
  var STALE_MS = 24 * 60 * 60 * 1e3;
9378
9748
  function getMarkerPath() {
9379
- return path19.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
9749
+ return path20.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
9380
9750
  }
9381
9751
  function writeActiveAgent(agentId, agentRole) {
9382
9752
  try {
9383
9753
  mkdirSync8(CACHE_DIR, { recursive: true });
9384
- writeFileSync8(
9754
+ writeFileSync9(
9385
9755
  getMarkerPath(),
9386
9756
  JSON.stringify({ agentId, agentRole, startedAt: (/* @__PURE__ */ new Date()).toISOString() })
9387
9757
  );
@@ -9391,11 +9761,11 @@ function writeActiveAgent(agentId, agentRole) {
9391
9761
  function cleanupSessionMarkers() {
9392
9762
  const key = getSessionKey();
9393
9763
  try {
9394
- unlinkSync7(path19.join(CACHE_DIR, `active-agent-${key}.json`));
9764
+ unlinkSync7(path20.join(CACHE_DIR, `active-agent-${key}.json`));
9395
9765
  } catch {
9396
9766
  }
9397
9767
  try {
9398
- unlinkSync7(path19.join(CACHE_DIR, "active-agent-undefined.json"));
9768
+ unlinkSync7(path20.join(CACHE_DIR, "active-agent-undefined.json"));
9399
9769
  } catch {
9400
9770
  }
9401
9771
  }
@@ -9485,7 +9855,7 @@ async function boot(options) {
9485
9855
  const employeeDirs = entries.filter((e) => e.isDirectory() && !["output", "research"].includes(e.name));
9486
9856
  for (const dir of employeeDirs) {
9487
9857
  const employee = dir.name;
9488
- const taskDir = path23.join(exeDir, employee);
9858
+ const taskDir = path24.join(exeDir, employee);
9489
9859
  let files;
9490
9860
  try {
9491
9861
  files = readdirSync9(taskDir).filter((f) => f.endsWith(".md"));
@@ -9496,7 +9866,7 @@ async function boot(options) {
9496
9866
  const taskFilePath = `exe/${employee}/${file}`;
9497
9867
  let content;
9498
9868
  try {
9499
- content = readFs(path23.join(taskDir, file), "utf8");
9869
+ content = readFs(path24.join(taskDir, file), "utf8");
9500
9870
  } catch {
9501
9871
  continue;
9502
9872
  }
@@ -9582,12 +9952,12 @@ async function boot(options) {
9582
9952
  }
9583
9953
  try {
9584
9954
  for (const reviewDirName of /* @__PURE__ */ new Set(["exe", coordinatorName])) {
9585
- const reviewDir = path23.join(process.cwd(), "exe", reviewDirName);
9586
- if (existsSync18(reviewDir)) {
9955
+ const reviewDir = path24.join(process.cwd(), "exe", reviewDirName);
9956
+ if (existsSync20(reviewDir)) {
9587
9957
  for (const f of readdirSync8(reviewDir)) {
9588
9958
  if (f.startsWith("review-") && f.endsWith(".md")) {
9589
9959
  try {
9590
- unlinkSync11(path23.join(reviewDir, f));
9960
+ unlinkSync11(path24.join(reviewDir, f));
9591
9961
  } catch {
9592
9962
  }
9593
9963
  }
@@ -9633,12 +10003,12 @@ async function boot(options) {
9633
10003
  });
9634
10004
  const taskFile = String(r.task_file);
9635
10005
  try {
9636
- const filePath = path23.join(process.cwd(), taskFile);
9637
- if (existsSync18(filePath)) {
9638
- let content = readFileSync15(filePath, "utf8");
10006
+ const filePath = path24.join(process.cwd(), taskFile);
10007
+ if (existsSync20(filePath)) {
10008
+ let content = readFileSync16(filePath, "utf8");
9639
10009
  content = content.replace(/\*\*Status:\*\* needs_review/, "**Status:** done");
9640
- const { writeFileSync: writeFileSync12 } = await import("fs");
9641
- writeFileSync12(filePath, content);
10010
+ const { writeFileSync: writeFileSync13 } = await import("fs");
10011
+ writeFileSync13(filePath, content);
9642
10012
  }
9643
10013
  } catch {
9644
10014
  }
@@ -10132,19 +10502,19 @@ async function boot(options) {
10132
10502
  })()
10133
10503
  ]);
10134
10504
  try {
10135
- const configPath = path23.join(
10136
- process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path23.join(os11.homedir(), ".exe-os"),
10505
+ const configPath = path24.join(
10506
+ process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path24.join(os12.homedir(), ".exe-os"),
10137
10507
  "config.json"
10138
10508
  );
10139
- if (existsSync18(configPath)) {
10140
- const raw = JSON.parse(readFileSync15(configPath, "utf8"));
10509
+ if (existsSync20(configPath)) {
10510
+ const raw = JSON.parse(readFileSync16(configPath, "utf8"));
10141
10511
  briefData.cloudConnected = !!(raw.cloud || raw.turso);
10142
10512
  }
10143
10513
  } catch {
10144
10514
  }
10145
10515
  try {
10146
- const backfillFlagPath = path23.join(EXE_AI_DIR, "session-cache", "needs-backfill");
10147
- const isBackfillNeeded = () => existsSync18(backfillFlagPath);
10516
+ const backfillFlagPath = path24.join(EXE_AI_DIR, "session-cache", "needs-backfill");
10517
+ const isBackfillNeeded = () => existsSync20(backfillFlagPath);
10148
10518
  const coverageResult = await client.execute({
10149
10519
  sql: `SELECT COUNT(*) as total,
10150
10520
  SUM(CASE WHEN vector IS NOT NULL THEN 1 ELSE 0 END) as with_vectors
@@ -10166,8 +10536,8 @@ async function boot(options) {
10166
10536
  let daemonRunning = false;
10167
10537
  let daemonUptime;
10168
10538
  let daemonRequestsServed;
10169
- const socketPath = path23.join(EXE_AI_DIR, "exed.sock");
10170
- if (existsSync18(socketPath)) {
10539
+ const socketPath = path24.join(EXE_AI_DIR, "exed.sock");
10540
+ if (existsSync20(socketPath)) {
10171
10541
  try {
10172
10542
  const net2 = await import("net");
10173
10543
  const health = await new Promise((resolve) => {
@@ -10209,10 +10579,10 @@ async function boot(options) {
10209
10579
  }
10210
10580
  }
10211
10581
  if (!daemonRunning) {
10212
- const pidPath = path23.join(EXE_AI_DIR, "exed.pid");
10213
- if (existsSync18(pidPath)) {
10582
+ const pidPath = path24.join(EXE_AI_DIR, "exed.pid");
10583
+ if (existsSync20(pidPath)) {
10214
10584
  try {
10215
- const pid = parseInt(readFileSync15(pidPath, "utf8").trim(), 10);
10585
+ const pid = parseInt(readFileSync16(pidPath, "utf8").trim(), 10);
10216
10586
  if (pid > 0) {
10217
10587
  process.kill(pid, 0);
10218
10588
  daemonRunning = true;
@@ -10223,8 +10593,8 @@ async function boot(options) {
10223
10593
  }
10224
10594
  if (nullCount === 0) {
10225
10595
  try {
10226
- const flagPath = path23.join(EXE_AI_DIR, "session-cache", "needs-backfill");
10227
- if (existsSync18(flagPath)) {
10596
+ const flagPath = path24.join(EXE_AI_DIR, "session-cache", "needs-backfill");
10597
+ if (existsSync20(flagPath)) {
10228
10598
  const { unlinkSync: unlinkSync12 } = await import("fs");
10229
10599
  unlinkSync12(flagPath);
10230
10600
  }
@@ -10250,10 +10620,10 @@ async function boot(options) {
10250
10620
  const { spawn: spawn2 } = await import("child_process");
10251
10621
  const { fileURLToPath: fileURLToPath4 } = await import("url");
10252
10622
  const thisFile = fileURLToPath4(import.meta.url);
10253
- const backfillPath = path23.resolve(path23.dirname(thisFile), "backfill-vectors.js");
10254
- if (existsSync18(backfillPath)) {
10623
+ const backfillPath = path24.resolve(path24.dirname(thisFile), "backfill-vectors.js");
10624
+ if (existsSync20(backfillPath)) {
10255
10625
  const { openSync: openSync3, closeSync: closeSync3 } = await import("fs");
10256
- const workerLogPath = path23.join(EXE_AI_DIR, "workers.log");
10626
+ const workerLogPath = path24.join(EXE_AI_DIR, "workers.log");
10257
10627
  let stderrFd = "ignore";
10258
10628
  try {
10259
10629
  stderrFd = openSync3(workerLogPath, "a");
@@ -10283,8 +10653,8 @@ async function boot(options) {
10283
10653
  const criticalBinaries = ["backfill-vectors.js", "scan-tasks.js"];
10284
10654
  const missing = [];
10285
10655
  for (const bin of criticalBinaries) {
10286
- const binPath = path23.resolve(path23.dirname(thisFile), bin);
10287
- if (!existsSync18(binPath)) {
10656
+ const binPath = path24.resolve(path24.dirname(thisFile), bin);
10657
+ if (!existsSync20(binPath)) {
10288
10658
  missing.push(`dist/bin/${bin}`);
10289
10659
  }
10290
10660
  }
@@ -10313,7 +10683,7 @@ async function boot(options) {
10313
10683
  console.log(brief);
10314
10684
  return;
10315
10685
  }
10316
- const sessionDir = path23.join(EXE_AI_DIR, "sessions", coordinatorName);
10686
+ const sessionDir = path24.join(EXE_AI_DIR, "sessions", coordinatorName);
10317
10687
  await mkdir5(sessionDir, { recursive: true });
10318
10688
  const claudeMdContent = `${getSessionPrompt(coordinatorEmployee.systemPrompt)}
10319
10689
 
@@ -10322,7 +10692,7 @@ async function boot(options) {
10322
10692
  # Status Brief
10323
10693
 
10324
10694
  ${brief}`;
10325
- await writeFile6(path23.join(sessionDir, "CLAUDE.md"), claudeMdContent, "utf-8");
10695
+ await writeFile6(path24.join(sessionDir, "CLAUDE.md"), claudeMdContent, "utf-8");
10326
10696
  const unread = await readUnreadNotifications();
10327
10697
  if (unread.length > 0) {
10328
10698
  console.log(`\u{1F4EC} ${unread.length} unread notification${unread.length === 1 ? "" : "s"}`);
@@ -10331,12 +10701,12 @@ ${brief}`;
10331
10701
  await cleanupOldNotifications();
10332
10702
  console.log(brief);
10333
10703
  try {
10334
- const configPath2 = path23.join(
10335
- process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path23.join(os11.homedir(), ".exe-os"),
10704
+ const configPath2 = path24.join(
10705
+ process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path24.join(os12.homedir(), ".exe-os"),
10336
10706
  "config.json"
10337
10707
  );
10338
- if (existsSync18(configPath2)) {
10339
- const rawCfg = JSON.parse(readFileSync15(configPath2, "utf8"));
10708
+ if (existsSync20(configPath2)) {
10709
+ const rawCfg = JSON.parse(readFileSync16(configPath2, "utf8"));
10340
10710
  const cloudCfg = rawCfg.cloud;
10341
10711
  if (cloudCfg?.apiKey) {
10342
10712
  const { initSyncCrypto: initSyncCrypto2, isSyncCryptoInitialized: isSyncCryptoInitialized2 } = await Promise.resolve().then(() => (init_crypto(), crypto_exports));