@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
@@ -90,6 +90,44 @@ var init_db_retry = __esm({
90
90
  }
91
91
  });
92
92
 
93
+ // src/lib/secure-files.ts
94
+ import { chmodSync, existsSync, mkdirSync } from "fs";
95
+ import { chmod, mkdir } from "fs/promises";
96
+ async function ensurePrivateDir(dirPath) {
97
+ await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
98
+ try {
99
+ await chmod(dirPath, PRIVATE_DIR_MODE);
100
+ } catch {
101
+ }
102
+ }
103
+ function ensurePrivateDirSync(dirPath) {
104
+ mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
105
+ try {
106
+ chmodSync(dirPath, PRIVATE_DIR_MODE);
107
+ } catch {
108
+ }
109
+ }
110
+ async function enforcePrivateFile(filePath) {
111
+ try {
112
+ await chmod(filePath, PRIVATE_FILE_MODE);
113
+ } catch {
114
+ }
115
+ }
116
+ function enforcePrivateFileSync(filePath) {
117
+ try {
118
+ if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
119
+ } catch {
120
+ }
121
+ }
122
+ var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
123
+ var init_secure_files = __esm({
124
+ "src/lib/secure-files.ts"() {
125
+ "use strict";
126
+ PRIVATE_DIR_MODE = 448;
127
+ PRIVATE_FILE_MODE = 384;
128
+ }
129
+ });
130
+
93
131
  // src/lib/config.ts
94
132
  var config_exports = {};
95
133
  __export(config_exports, {
@@ -106,8 +144,8 @@ __export(config_exports, {
106
144
  migrateConfig: () => migrateConfig,
107
145
  saveConfig: () => saveConfig
108
146
  });
109
- import { readFile, writeFile, mkdir, chmod } from "fs/promises";
110
- import { readFileSync, existsSync, renameSync } from "fs";
147
+ import { readFile, writeFile } from "fs/promises";
148
+ import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
111
149
  import path from "path";
112
150
  import os from "os";
113
151
  function resolveDataDir() {
@@ -115,7 +153,7 @@ function resolveDataDir() {
115
153
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
116
154
  const newDir = path.join(os.homedir(), ".exe-os");
117
155
  const legacyDir = path.join(os.homedir(), ".exe-mem");
118
- if (!existsSync(newDir) && existsSync(legacyDir)) {
156
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
119
157
  try {
120
158
  renameSync(legacyDir, newDir);
121
159
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -178,9 +216,9 @@ function normalizeAutoUpdate(raw) {
178
216
  }
179
217
  async function loadConfig() {
180
218
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
181
- await mkdir(dir, { recursive: true });
219
+ await ensurePrivateDir(dir);
182
220
  const configPath = path.join(dir, "config.json");
183
- if (!existsSync(configPath)) {
221
+ if (!existsSync2(configPath)) {
184
222
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
185
223
  }
186
224
  const raw = await readFile(configPath, "utf-8");
@@ -193,6 +231,7 @@ async function loadConfig() {
193
231
  `);
194
232
  try {
195
233
  await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
234
+ await enforcePrivateFile(configPath);
196
235
  } catch {
197
236
  }
198
237
  }
@@ -211,7 +250,7 @@ async function loadConfig() {
211
250
  function loadConfigSync() {
212
251
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
213
252
  const configPath = path.join(dir, "config.json");
214
- if (!existsSync(configPath)) {
253
+ if (!existsSync2(configPath)) {
215
254
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
216
255
  }
217
256
  try {
@@ -229,12 +268,10 @@ function loadConfigSync() {
229
268
  }
230
269
  async function saveConfig(config) {
231
270
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
232
- await mkdir(dir, { recursive: true });
271
+ await ensurePrivateDir(dir);
233
272
  const configPath = path.join(dir, "config.json");
234
273
  await writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
235
- if (config.cloud?.apiKey) {
236
- await chmod(configPath, 384);
237
- }
274
+ await enforcePrivateFile(configPath);
238
275
  }
239
276
  async function loadConfigFrom(configPath) {
240
277
  const raw = await readFile(configPath, "utf-8");
@@ -254,6 +291,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
254
291
  var init_config = __esm({
255
292
  "src/lib/config.ts"() {
256
293
  "use strict";
294
+ init_secure_files();
257
295
  EXE_AI_DIR = resolveDataDir();
258
296
  DB_PATH = path.join(EXE_AI_DIR, "memories.db");
259
297
  MODELS_DIR = path.join(EXE_AI_DIR, "models");
@@ -332,7 +370,7 @@ var init_config = __esm({
332
370
 
333
371
  // src/lib/employees.ts
334
372
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
335
- import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
373
+ import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
336
374
  import { execSync } from "child_process";
337
375
  import path2 from "path";
338
376
  import os2 from "os";
@@ -353,7 +391,7 @@ function isCoordinatorName(agentName2, employees = loadEmployeesSync()) {
353
391
  return agentName2.toLowerCase() === getCoordinatorName(employees).toLowerCase();
354
392
  }
355
393
  async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
356
- if (!existsSync2(employeesPath)) {
394
+ if (!existsSync3(employeesPath)) {
357
395
  return [];
358
396
  }
359
397
  const raw = await readFile2(employeesPath, "utf-8");
@@ -364,7 +402,7 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
364
402
  }
365
403
  }
366
404
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
367
- if (!existsSync2(employeesPath)) return [];
405
+ if (!existsSync3(employeesPath)) return [];
368
406
  try {
369
407
  return JSON.parse(readFileSync2(employeesPath, "utf-8"));
370
408
  } catch {
@@ -985,13 +1023,50 @@ var init_database_adapter = __esm({
985
1023
  }
986
1024
  });
987
1025
 
1026
+ // src/lib/daemon-auth.ts
1027
+ import crypto from "crypto";
1028
+ import path4 from "path";
1029
+ import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
1030
+ function normalizeToken(token) {
1031
+ if (!token) return null;
1032
+ const trimmed = token.trim();
1033
+ return trimmed.length > 0 ? trimmed : null;
1034
+ }
1035
+ function readDaemonToken() {
1036
+ try {
1037
+ if (!existsSync4(DAEMON_TOKEN_PATH)) return null;
1038
+ return normalizeToken(readFileSync3(DAEMON_TOKEN_PATH, "utf8"));
1039
+ } catch {
1040
+ return null;
1041
+ }
1042
+ }
1043
+ function ensureDaemonToken(seed) {
1044
+ const existing = readDaemonToken();
1045
+ if (existing) return existing;
1046
+ const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
1047
+ ensurePrivateDirSync(EXE_AI_DIR);
1048
+ writeFileSync2(DAEMON_TOKEN_PATH, `${token}
1049
+ `, "utf8");
1050
+ enforcePrivateFileSync(DAEMON_TOKEN_PATH);
1051
+ return token;
1052
+ }
1053
+ var DAEMON_TOKEN_PATH;
1054
+ var init_daemon_auth = __esm({
1055
+ "src/lib/daemon-auth.ts"() {
1056
+ "use strict";
1057
+ init_config();
1058
+ init_secure_files();
1059
+ DAEMON_TOKEN_PATH = path4.join(EXE_AI_DIR, "exed.token");
1060
+ }
1061
+ });
1062
+
988
1063
  // src/lib/exe-daemon-client.ts
989
1064
  import net from "net";
990
1065
  import os4 from "os";
991
1066
  import { spawn } from "child_process";
992
1067
  import { randomUUID } from "crypto";
993
- import { existsSync as existsSync3, unlinkSync as unlinkSync2, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
994
- import path4 from "path";
1068
+ import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
1069
+ import path5 from "path";
995
1070
  import { fileURLToPath } from "url";
996
1071
  function handleData(chunk) {
997
1072
  _buffer += chunk.toString();
@@ -1019,9 +1094,9 @@ function handleData(chunk) {
1019
1094
  }
1020
1095
  }
1021
1096
  function cleanupStaleFiles() {
1022
- if (existsSync3(PID_PATH)) {
1097
+ if (existsSync5(PID_PATH)) {
1023
1098
  try {
1024
- const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
1099
+ const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
1025
1100
  if (pid > 0) {
1026
1101
  try {
1027
1102
  process.kill(pid, 0);
@@ -1042,11 +1117,11 @@ function cleanupStaleFiles() {
1042
1117
  }
1043
1118
  }
1044
1119
  function findPackageRoot() {
1045
- let dir = path4.dirname(fileURLToPath(import.meta.url));
1046
- const { root } = path4.parse(dir);
1120
+ let dir = path5.dirname(fileURLToPath(import.meta.url));
1121
+ const { root } = path5.parse(dir);
1047
1122
  while (dir !== root) {
1048
- if (existsSync3(path4.join(dir, "package.json"))) return dir;
1049
- dir = path4.dirname(dir);
1123
+ if (existsSync5(path5.join(dir, "package.json"))) return dir;
1124
+ dir = path5.dirname(dir);
1050
1125
  }
1051
1126
  return null;
1052
1127
  }
@@ -1072,16 +1147,17 @@ function spawnDaemon() {
1072
1147
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
1073
1148
  return;
1074
1149
  }
1075
- const daemonPath = path4.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1076
- if (!existsSync3(daemonPath)) {
1150
+ const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1151
+ if (!existsSync5(daemonPath)) {
1077
1152
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
1078
1153
  `);
1079
1154
  return;
1080
1155
  }
1081
1156
  const resolvedPath = daemonPath;
1157
+ const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
1082
1158
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
1083
1159
  `);
1084
- const logPath = path4.join(path4.dirname(SOCKET_PATH), "exed.log");
1160
+ const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
1085
1161
  let stderrFd = "ignore";
1086
1162
  try {
1087
1163
  stderrFd = openSync(logPath, "a");
@@ -1099,7 +1175,8 @@ function spawnDaemon() {
1099
1175
  TMUX_PANE: void 0,
1100
1176
  // Prevents resolveExeSession() from scoping to one session
1101
1177
  EXE_DAEMON_SOCK: SOCKET_PATH,
1102
- EXE_DAEMON_PID: PID_PATH
1178
+ EXE_DAEMON_PID: PID_PATH,
1179
+ [DAEMON_TOKEN_ENV]: daemonToken
1103
1180
  }
1104
1181
  });
1105
1182
  child.unref();
@@ -1209,13 +1286,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
1209
1286
  return;
1210
1287
  }
1211
1288
  const id = randomUUID();
1289
+ const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
1212
1290
  const timer = setTimeout(() => {
1213
1291
  _pending.delete(id);
1214
1292
  resolve({ error: "Request timeout" });
1215
1293
  }, timeoutMs);
1216
1294
  _pending.set(id, { resolve, timer });
1217
1295
  try {
1218
- _socket.write(JSON.stringify({ id, ...payload }) + "\n");
1296
+ _socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
1219
1297
  } catch {
1220
1298
  clearTimeout(timer);
1221
1299
  _pending.delete(id);
@@ -1244,9 +1322,9 @@ function killAndRespawnDaemon() {
1244
1322
  }
1245
1323
  try {
1246
1324
  process.stderr.write("[exed-client] Killing daemon for restart...\n");
1247
- if (existsSync3(PID_PATH)) {
1325
+ if (existsSync5(PID_PATH)) {
1248
1326
  try {
1249
- const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
1327
+ const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
1250
1328
  if (pid > 0) {
1251
1329
  try {
1252
1330
  process.kill(pid, "SIGKILL");
@@ -1366,17 +1444,19 @@ function disconnectClient() {
1366
1444
  function isClientConnected() {
1367
1445
  return _connected;
1368
1446
  }
1369
- var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _requestCount, _consecutiveFailures, HEALTH_CHECK_INTERVAL, MAX_RETRIES_BEFORE_RESTART, RETRY_DELAYS_MS, MIN_DAEMON_AGE_MS, _pending, MAX_BUFFER;
1447
+ var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, DAEMON_TOKEN_ENV, _socket, _connected, _buffer, _requestCount, _consecutiveFailures, HEALTH_CHECK_INTERVAL, MAX_RETRIES_BEFORE_RESTART, RETRY_DELAYS_MS, MIN_DAEMON_AGE_MS, _pending, MAX_BUFFER;
1370
1448
  var init_exe_daemon_client = __esm({
1371
1449
  "src/lib/exe-daemon-client.ts"() {
1372
1450
  "use strict";
1373
1451
  init_config();
1374
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path4.join(EXE_AI_DIR, "exed.sock");
1375
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path4.join(EXE_AI_DIR, "exed.pid");
1376
- SPAWN_LOCK_PATH = path4.join(EXE_AI_DIR, "exed-spawn.lock");
1452
+ init_daemon_auth();
1453
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path5.join(EXE_AI_DIR, "exed.sock");
1454
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path5.join(EXE_AI_DIR, "exed.pid");
1455
+ SPAWN_LOCK_PATH = path5.join(EXE_AI_DIR, "exed-spawn.lock");
1377
1456
  SPAWN_LOCK_STALE_MS = 3e4;
1378
1457
  CONNECT_TIMEOUT_MS = 15e3;
1379
1458
  REQUEST_TIMEOUT_MS = 3e4;
1459
+ DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
1380
1460
  _socket = null;
1381
1461
  _connected = false;
1382
1462
  _buffer = "";
@@ -1961,6 +2041,7 @@ async function ensureSchema() {
1961
2041
  project TEXT NOT NULL,
1962
2042
  summary TEXT NOT NULL,
1963
2043
  task_file TEXT,
2044
+ session_scope TEXT,
1964
2045
  read INTEGER NOT NULL DEFAULT 0,
1965
2046
  created_at TEXT NOT NULL
1966
2047
  );
@@ -1969,7 +2050,7 @@ async function ensureSchema() {
1969
2050
  ON notifications(read);
1970
2051
 
1971
2052
  CREATE INDEX IF NOT EXISTS idx_notifications_agent
1972
- ON notifications(agent_id);
2053
+ ON notifications(agent_id, session_scope);
1973
2054
 
1974
2055
  CREATE INDEX IF NOT EXISTS idx_notifications_task_file
1975
2056
  ON notifications(task_file);
@@ -2007,6 +2088,7 @@ async function ensureSchema() {
2007
2088
  target_agent TEXT NOT NULL,
2008
2089
  target_project TEXT,
2009
2090
  target_device TEXT NOT NULL DEFAULT 'local',
2091
+ session_scope TEXT,
2010
2092
  content TEXT NOT NULL,
2011
2093
  priority TEXT DEFAULT 'normal',
2012
2094
  status TEXT DEFAULT 'pending',
@@ -2020,10 +2102,31 @@ async function ensureSchema() {
2020
2102
  );
2021
2103
 
2022
2104
  CREATE INDEX IF NOT EXISTS idx_messages_target
2023
- ON messages(target_agent, status);
2105
+ ON messages(target_agent, session_scope, status);
2024
2106
 
2025
2107
  CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
2026
- ON messages(target_agent, from_agent, server_seq);
2108
+ ON messages(target_agent, session_scope, from_agent, server_seq);
2109
+ `);
2110
+ try {
2111
+ await client.execute({
2112
+ sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
2113
+ args: []
2114
+ });
2115
+ } catch {
2116
+ }
2117
+ try {
2118
+ await client.execute({
2119
+ sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
2120
+ args: []
2121
+ });
2122
+ } catch {
2123
+ }
2124
+ await client.executeMultiple(`
2125
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
2126
+ ON notifications(agent_id, session_scope, read, created_at);
2127
+
2128
+ CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
2129
+ ON messages(target_agent, session_scope, status, created_at);
2027
2130
  `);
2028
2131
  try {
2029
2132
  await client.execute({
@@ -2607,6 +2710,13 @@ async function ensureSchema() {
2607
2710
  } catch {
2608
2711
  }
2609
2712
  }
2713
+ try {
2714
+ await client.execute({
2715
+ sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
2716
+ args: []
2717
+ });
2718
+ } catch {
2719
+ }
2610
2720
  }
2611
2721
  async function disposeDatabase() {
2612
2722
  if (_walCheckpointTimer) {
@@ -2646,14 +2756,14 @@ var init_database = __esm({
2646
2756
 
2647
2757
  // src/lib/keychain.ts
2648
2758
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
2649
- import { existsSync as existsSync4 } from "fs";
2650
- import path5 from "path";
2759
+ import { existsSync as existsSync6 } from "fs";
2760
+ import path6 from "path";
2651
2761
  import os5 from "os";
2652
2762
  function getKeyDir() {
2653
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path5.join(os5.homedir(), ".exe-os");
2763
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path6.join(os5.homedir(), ".exe-os");
2654
2764
  }
2655
2765
  function getKeyPath() {
2656
- return path5.join(getKeyDir(), "master.key");
2766
+ return path6.join(getKeyDir(), "master.key");
2657
2767
  }
2658
2768
  async function tryKeytar() {
2659
2769
  try {
@@ -2674,7 +2784,7 @@ async function getMasterKey() {
2674
2784
  }
2675
2785
  }
2676
2786
  const keyPath = getKeyPath();
2677
- if (!existsSync4(keyPath)) {
2787
+ if (!existsSync6(keyPath)) {
2678
2788
  process.stderr.write(
2679
2789
  `[keychain] Key not found at ${keyPath} (HOME=${os5.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
2680
2790
  `
@@ -2761,6 +2871,7 @@ var shard_manager_exports = {};
2761
2871
  __export(shard_manager_exports, {
2762
2872
  disposeShards: () => disposeShards,
2763
2873
  ensureShardSchema: () => ensureShardSchema,
2874
+ getOpenShardCount: () => getOpenShardCount,
2764
2875
  getReadyShardClient: () => getReadyShardClient,
2765
2876
  getShardClient: () => getShardClient,
2766
2877
  getShardsDir: () => getShardsDir,
@@ -2769,15 +2880,18 @@ __export(shard_manager_exports, {
2769
2880
  listShards: () => listShards,
2770
2881
  shardExists: () => shardExists
2771
2882
  });
2772
- import path6 from "path";
2773
- import { existsSync as existsSync5, mkdirSync, readdirSync } from "fs";
2883
+ import path7 from "path";
2884
+ import { existsSync as existsSync7, mkdirSync as mkdirSync2, readdirSync } from "fs";
2774
2885
  import { createClient as createClient2 } from "@libsql/client";
2775
2886
  function initShardManager(encryptionKey) {
2776
2887
  _encryptionKey = encryptionKey;
2777
- if (!existsSync5(SHARDS_DIR)) {
2778
- mkdirSync(SHARDS_DIR, { recursive: true });
2888
+ if (!existsSync7(SHARDS_DIR)) {
2889
+ mkdirSync2(SHARDS_DIR, { recursive: true });
2779
2890
  }
2780
2891
  _shardingEnabled = true;
2892
+ if (_evictionTimer) clearInterval(_evictionTimer);
2893
+ _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
2894
+ _evictionTimer.unref();
2781
2895
  }
2782
2896
  function isShardingEnabled() {
2783
2897
  return _shardingEnabled;
@@ -2794,21 +2908,28 @@ function getShardClient(projectName) {
2794
2908
  throw new Error(`Invalid project name for shard: "${projectName}"`);
2795
2909
  }
2796
2910
  const cached = _shards.get(safeName);
2797
- if (cached) return cached;
2798
- const dbPath = path6.join(SHARDS_DIR, `${safeName}.db`);
2911
+ if (cached) {
2912
+ _shardLastAccess.set(safeName, Date.now());
2913
+ return cached;
2914
+ }
2915
+ while (_shards.size >= MAX_OPEN_SHARDS) {
2916
+ evictLRU();
2917
+ }
2918
+ const dbPath = path7.join(SHARDS_DIR, `${safeName}.db`);
2799
2919
  const client = createClient2({
2800
2920
  url: `file:${dbPath}`,
2801
2921
  encryptionKey: _encryptionKey
2802
2922
  });
2803
2923
  _shards.set(safeName, client);
2924
+ _shardLastAccess.set(safeName, Date.now());
2804
2925
  return client;
2805
2926
  }
2806
2927
  function shardExists(projectName) {
2807
2928
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
2808
- return existsSync5(path6.join(SHARDS_DIR, `${safeName}.db`));
2929
+ return existsSync7(path7.join(SHARDS_DIR, `${safeName}.db`));
2809
2930
  }
2810
2931
  function listShards() {
2811
- if (!existsSync5(SHARDS_DIR)) return [];
2932
+ if (!existsSync7(SHARDS_DIR)) return [];
2812
2933
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
2813
2934
  }
2814
2935
  async function ensureShardSchema(client) {
@@ -2860,6 +2981,8 @@ async function ensureShardSchema(client) {
2860
2981
  for (const col of [
2861
2982
  "ALTER TABLE memories ADD COLUMN task_id TEXT",
2862
2983
  "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
2984
+ "ALTER TABLE memories ADD COLUMN author_device_id TEXT",
2985
+ "ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
2863
2986
  "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
2864
2987
  "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
2865
2988
  "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
@@ -2997,21 +3120,69 @@ async function getReadyShardClient(projectName) {
2997
3120
  await ensureShardSchema(client);
2998
3121
  return client;
2999
3122
  }
3123
+ function evictLRU() {
3124
+ let oldest = null;
3125
+ let oldestTime = Infinity;
3126
+ for (const [name, time] of _shardLastAccess) {
3127
+ if (time < oldestTime) {
3128
+ oldestTime = time;
3129
+ oldest = name;
3130
+ }
3131
+ }
3132
+ if (oldest) {
3133
+ const client = _shards.get(oldest);
3134
+ if (client) {
3135
+ client.close();
3136
+ }
3137
+ _shards.delete(oldest);
3138
+ _shardLastAccess.delete(oldest);
3139
+ }
3140
+ }
3141
+ function evictIdleShards() {
3142
+ const now = Date.now();
3143
+ const toEvict = [];
3144
+ for (const [name, lastAccess] of _shardLastAccess) {
3145
+ if (now - lastAccess > SHARD_IDLE_MS) {
3146
+ toEvict.push(name);
3147
+ }
3148
+ }
3149
+ for (const name of toEvict) {
3150
+ const client = _shards.get(name);
3151
+ if (client) {
3152
+ client.close();
3153
+ }
3154
+ _shards.delete(name);
3155
+ _shardLastAccess.delete(name);
3156
+ }
3157
+ }
3158
+ function getOpenShardCount() {
3159
+ return _shards.size;
3160
+ }
3000
3161
  function disposeShards() {
3162
+ if (_evictionTimer) {
3163
+ clearInterval(_evictionTimer);
3164
+ _evictionTimer = null;
3165
+ }
3001
3166
  for (const [, client] of _shards) {
3002
3167
  client.close();
3003
3168
  }
3004
3169
  _shards.clear();
3170
+ _shardLastAccess.clear();
3005
3171
  _shardingEnabled = false;
3006
3172
  _encryptionKey = null;
3007
3173
  }
3008
- var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
3174
+ var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
3009
3175
  var init_shard_manager = __esm({
3010
3176
  "src/lib/shard-manager.ts"() {
3011
3177
  "use strict";
3012
3178
  init_config();
3013
- SHARDS_DIR = path6.join(EXE_AI_DIR, "shards");
3179
+ SHARDS_DIR = path7.join(EXE_AI_DIR, "shards");
3180
+ SHARD_IDLE_MS = 5 * 60 * 1e3;
3181
+ MAX_OPEN_SHARDS = 10;
3182
+ EVICTION_INTERVAL_MS = 60 * 1e3;
3014
3183
  _shards = /* @__PURE__ */ new Map();
3184
+ _shardLastAccess = /* @__PURE__ */ new Map();
3185
+ _evictionTimer = null;
3015
3186
  _encryptionKey = null;
3016
3187
  _shardingEnabled = false;
3017
3188
  }
@@ -3775,13 +3946,13 @@ var init_store = __esm({
3775
3946
  });
3776
3947
 
3777
3948
  // src/lib/session-registry.ts
3778
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync6 } from "fs";
3779
- import path7 from "path";
3949
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync8 } from "fs";
3950
+ import path8 from "path";
3780
3951
  import os6 from "os";
3781
3952
  function registerSession(entry) {
3782
- const dir = path7.dirname(REGISTRY_PATH);
3783
- if (!existsSync6(dir)) {
3784
- mkdirSync2(dir, { recursive: true });
3953
+ const dir = path8.dirname(REGISTRY_PATH);
3954
+ if (!existsSync8(dir)) {
3955
+ mkdirSync3(dir, { recursive: true });
3785
3956
  }
3786
3957
  const sessions = listSessions();
3787
3958
  const idx = sessions.findIndex((s) => s.windowName === entry.windowName);
@@ -3790,11 +3961,11 @@ function registerSession(entry) {
3790
3961
  } else {
3791
3962
  sessions.push(entry);
3792
3963
  }
3793
- writeFileSync2(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
3964
+ writeFileSync3(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
3794
3965
  }
3795
3966
  function listSessions() {
3796
3967
  try {
3797
- const raw = readFileSync4(REGISTRY_PATH, "utf8");
3968
+ const raw = readFileSync5(REGISTRY_PATH, "utf8");
3798
3969
  return JSON.parse(raw);
3799
3970
  } catch {
3800
3971
  return [];
@@ -3804,7 +3975,7 @@ var REGISTRY_PATH;
3804
3975
  var init_session_registry = __esm({
3805
3976
  "src/lib/session-registry.ts"() {
3806
3977
  "use strict";
3807
- REGISTRY_PATH = path7.join(os6.homedir(), ".exe-os", "session-registry.json");
3978
+ REGISTRY_PATH = path8.join(os6.homedir(), ".exe-os", "session-registry.json");
3808
3979
  }
3809
3980
  });
3810
3981
 
@@ -4084,12 +4255,12 @@ var init_runtime_table = __esm({
4084
4255
  });
4085
4256
 
4086
4257
  // src/lib/agent-config.ts
4087
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync7, mkdirSync as mkdirSync3 } from "fs";
4088
- import path8 from "path";
4258
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync9 } from "fs";
4259
+ import path9 from "path";
4089
4260
  function loadAgentConfig() {
4090
- if (!existsSync7(AGENT_CONFIG_PATH)) return {};
4261
+ if (!existsSync9(AGENT_CONFIG_PATH)) return {};
4091
4262
  try {
4092
- return JSON.parse(readFileSync5(AGENT_CONFIG_PATH, "utf-8"));
4263
+ return JSON.parse(readFileSync6(AGENT_CONFIG_PATH, "utf-8"));
4093
4264
  } catch {
4094
4265
  return {};
4095
4266
  }
@@ -4108,7 +4279,8 @@ var init_agent_config = __esm({
4108
4279
  "use strict";
4109
4280
  init_config();
4110
4281
  init_runtime_table();
4111
- AGENT_CONFIG_PATH = path8.join(EXE_AI_DIR, "agent-config.json");
4282
+ init_secure_files();
4283
+ AGENT_CONFIG_PATH = path9.join(EXE_AI_DIR, "agent-config.json");
4112
4284
  DEFAULT_MODELS = {
4113
4285
  claude: "claude-opus-4",
4114
4286
  codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
@@ -4126,17 +4298,17 @@ __export(intercom_queue_exports, {
4126
4298
  queueIntercom: () => queueIntercom,
4127
4299
  readQueue: () => readQueue
4128
4300
  });
4129
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync8, mkdirSync as mkdirSync4 } from "fs";
4130
- import path9 from "path";
4301
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, renameSync as renameSync3, existsSync as existsSync10, mkdirSync as mkdirSync4 } from "fs";
4302
+ import path10 from "path";
4131
4303
  import os7 from "os";
4132
4304
  function ensureDir() {
4133
- const dir = path9.dirname(QUEUE_PATH);
4134
- if (!existsSync8(dir)) mkdirSync4(dir, { recursive: true });
4305
+ const dir = path10.dirname(QUEUE_PATH);
4306
+ if (!existsSync10(dir)) mkdirSync4(dir, { recursive: true });
4135
4307
  }
4136
4308
  function readQueue() {
4137
4309
  try {
4138
- if (!existsSync8(QUEUE_PATH)) return [];
4139
- return JSON.parse(readFileSync6(QUEUE_PATH, "utf8"));
4310
+ if (!existsSync10(QUEUE_PATH)) return [];
4311
+ return JSON.parse(readFileSync7(QUEUE_PATH, "utf8"));
4140
4312
  } catch {
4141
4313
  return [];
4142
4314
  }
@@ -4144,7 +4316,7 @@ function readQueue() {
4144
4316
  function writeQueue(queue) {
4145
4317
  ensureDir();
4146
4318
  const tmp = `${QUEUE_PATH}.tmp`;
4147
- writeFileSync4(tmp, JSON.stringify(queue, null, 2));
4319
+ writeFileSync5(tmp, JSON.stringify(queue, null, 2));
4148
4320
  renameSync3(tmp, QUEUE_PATH);
4149
4321
  }
4150
4322
  function queueIntercom(targetSession, reason) {
@@ -4236,26 +4408,29 @@ var QUEUE_PATH, MAX_RETRIES2, TTL_MS, INTERCOM_LOG;
4236
4408
  var init_intercom_queue = __esm({
4237
4409
  "src/lib/intercom-queue.ts"() {
4238
4410
  "use strict";
4239
- QUEUE_PATH = path9.join(os7.homedir(), ".exe-os", "intercom-queue.json");
4411
+ QUEUE_PATH = path10.join(os7.homedir(), ".exe-os", "intercom-queue.json");
4240
4412
  MAX_RETRIES2 = 5;
4241
4413
  TTL_MS = 60 * 60 * 1e3;
4242
- INTERCOM_LOG = path9.join(os7.homedir(), ".exe-os", "intercom.log");
4414
+ INTERCOM_LOG = path10.join(os7.homedir(), ".exe-os", "intercom.log");
4243
4415
  }
4244
4416
  });
4245
4417
 
4246
4418
  // src/lib/license.ts
4247
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync9, mkdirSync as mkdirSync5 } from "fs";
4419
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync11, mkdirSync as mkdirSync5 } from "fs";
4248
4420
  import { randomUUID as randomUUID3 } from "crypto";
4249
- import path10 from "path";
4421
+ import { createRequire as createRequire2 } from "module";
4422
+ import { pathToFileURL as pathToFileURL2 } from "url";
4423
+ import os8 from "os";
4424
+ import path11 from "path";
4250
4425
  import { jwtVerify, importSPKI } from "jose";
4251
4426
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
4252
4427
  var init_license = __esm({
4253
4428
  "src/lib/license.ts"() {
4254
4429
  "use strict";
4255
4430
  init_config();
4256
- LICENSE_PATH = path10.join(EXE_AI_DIR, "license.key");
4257
- CACHE_PATH = path10.join(EXE_AI_DIR, "license-cache.json");
4258
- DEVICE_ID_PATH = path10.join(EXE_AI_DIR, "device-id");
4431
+ LICENSE_PATH = path11.join(EXE_AI_DIR, "license.key");
4432
+ CACHE_PATH = path11.join(EXE_AI_DIR, "license-cache.json");
4433
+ DEVICE_ID_PATH = path11.join(EXE_AI_DIR, "device-id");
4259
4434
  PLAN_LIMITS = {
4260
4435
  free: { devices: 1, employees: 1, memories: 5e3 },
4261
4436
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -4267,12 +4442,12 @@ var init_license = __esm({
4267
4442
  });
4268
4443
 
4269
4444
  // src/lib/plan-limits.ts
4270
- import { readFileSync as readFileSync8, existsSync as existsSync10 } from "fs";
4271
- import path11 from "path";
4445
+ import { readFileSync as readFileSync9, existsSync as existsSync12 } from "fs";
4446
+ import path12 from "path";
4272
4447
  function getLicenseSync() {
4273
4448
  try {
4274
- if (!existsSync10(CACHE_PATH2)) return freeLicense();
4275
- const raw = JSON.parse(readFileSync8(CACHE_PATH2, "utf8"));
4449
+ if (!existsSync12(CACHE_PATH2)) return freeLicense();
4450
+ const raw = JSON.parse(readFileSync9(CACHE_PATH2, "utf8"));
4276
4451
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
4277
4452
  const parts = raw.token.split(".");
4278
4453
  if (parts.length !== 3) return freeLicense();
@@ -4310,8 +4485,8 @@ function assertEmployeeLimitSync(rosterPath) {
4310
4485
  const filePath = rosterPath ?? EMPLOYEES_PATH;
4311
4486
  let count = 0;
4312
4487
  try {
4313
- if (existsSync10(filePath)) {
4314
- const raw = readFileSync8(filePath, "utf8");
4488
+ if (existsSync12(filePath)) {
4489
+ const raw = readFileSync9(filePath, "utf8");
4315
4490
  const employees = JSON.parse(raw);
4316
4491
  count = Array.isArray(employees) ? employees.length : 0;
4317
4492
  }
@@ -4340,29 +4515,30 @@ var init_plan_limits = __esm({
4340
4515
  this.name = "PlanLimitError";
4341
4516
  }
4342
4517
  };
4343
- CACHE_PATH2 = path11.join(EXE_AI_DIR, "license-cache.json");
4518
+ CACHE_PATH2 = path12.join(EXE_AI_DIR, "license-cache.json");
4344
4519
  }
4345
4520
  });
4346
4521
 
4347
4522
  // src/lib/notifications.ts
4348
- import crypto from "crypto";
4349
- import path12 from "path";
4350
- import os8 from "os";
4523
+ import crypto2 from "crypto";
4524
+ import path13 from "path";
4525
+ import os9 from "os";
4351
4526
  import {
4352
- readFileSync as readFileSync9,
4527
+ readFileSync as readFileSync10,
4353
4528
  readdirSync as readdirSync2,
4354
4529
  unlinkSync as unlinkSync3,
4355
- existsSync as existsSync11,
4530
+ existsSync as existsSync13,
4356
4531
  rmdirSync
4357
4532
  } from "fs";
4358
4533
  async function writeNotification(notification) {
4359
4534
  try {
4360
4535
  const client = getClient();
4361
- const id = crypto.randomUUID();
4536
+ const id = crypto2.randomUUID();
4362
4537
  const now = (/* @__PURE__ */ new Date()).toISOString();
4538
+ const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
4363
4539
  await client.execute({
4364
- sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
4365
- VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
4540
+ sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
4541
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
4366
4542
  args: [
4367
4543
  id,
4368
4544
  notification.agentId,
@@ -4371,6 +4547,7 @@ async function writeNotification(notification) {
4371
4547
  notification.project,
4372
4548
  notification.summary,
4373
4549
  notification.taskFile ?? null,
4550
+ sessionScope,
4374
4551
  now
4375
4552
  ]
4376
4553
  });
@@ -4379,12 +4556,14 @@ async function writeNotification(notification) {
4379
4556
  `);
4380
4557
  }
4381
4558
  }
4382
- async function markAsReadByTaskFile(taskFile) {
4559
+ async function markAsReadByTaskFile(taskFile, sessionScope) {
4383
4560
  try {
4384
4561
  const client = getClient();
4562
+ const scope = strictSessionScopeFilter(sessionScope);
4385
4563
  await client.execute({
4386
- sql: "UPDATE notifications SET read = 1 WHERE task_file = ? AND read = 0",
4387
- args: [taskFile]
4564
+ sql: `UPDATE notifications SET read = 1
4565
+ WHERE task_file = ? AND read = 0${scope.sql}`,
4566
+ args: [taskFile, ...scope.args]
4388
4567
  });
4389
4568
  } catch {
4390
4569
  }
@@ -4393,11 +4572,12 @@ var init_notifications = __esm({
4393
4572
  "src/lib/notifications.ts"() {
4394
4573
  "use strict";
4395
4574
  init_database();
4575
+ init_task_scope();
4396
4576
  }
4397
4577
  });
4398
4578
 
4399
4579
  // src/lib/session-kill-telemetry.ts
4400
- import crypto2 from "crypto";
4580
+ import crypto3 from "crypto";
4401
4581
  async function recordSessionKill(input) {
4402
4582
  try {
4403
4583
  const client = getClient();
@@ -4407,7 +4587,7 @@ async function recordSessionKill(input) {
4407
4587
  ticks_idle, estimated_tokens_saved)
4408
4588
  VALUES (?, ?, ?, ?, ?, ?, ?)`,
4409
4589
  args: [
4410
- crypto2.randomUUID(),
4590
+ crypto3.randomUUID(),
4411
4591
  input.sessionName,
4412
4592
  input.agentId,
4413
4593
  (/* @__PURE__ */ new Date()).toISOString(),
@@ -4430,6 +4610,110 @@ var init_session_kill_telemetry = __esm({
4430
4610
  }
4431
4611
  });
4432
4612
 
4613
+ // src/lib/project-name.ts
4614
+ import { execSync as execSync4 } from "child_process";
4615
+ import path14 from "path";
4616
+ function getProjectName(cwd) {
4617
+ const dir = cwd ?? process.cwd();
4618
+ if (_cached2 && _cachedCwd === dir) return _cached2;
4619
+ try {
4620
+ let repoRoot;
4621
+ try {
4622
+ const gitCommonDir = execSync4("git rev-parse --path-format=absolute --git-common-dir", {
4623
+ cwd: dir,
4624
+ encoding: "utf8",
4625
+ timeout: 2e3,
4626
+ stdio: ["pipe", "pipe", "pipe"]
4627
+ }).trim();
4628
+ repoRoot = path14.dirname(gitCommonDir);
4629
+ } catch {
4630
+ repoRoot = execSync4("git rev-parse --show-toplevel", {
4631
+ cwd: dir,
4632
+ encoding: "utf8",
4633
+ timeout: 2e3,
4634
+ stdio: ["pipe", "pipe", "pipe"]
4635
+ }).trim();
4636
+ }
4637
+ _cached2 = path14.basename(repoRoot);
4638
+ _cachedCwd = dir;
4639
+ return _cached2;
4640
+ } catch {
4641
+ _cached2 = path14.basename(dir);
4642
+ _cachedCwd = dir;
4643
+ return _cached2;
4644
+ }
4645
+ }
4646
+ var _cached2, _cachedCwd;
4647
+ var init_project_name = __esm({
4648
+ "src/lib/project-name.ts"() {
4649
+ "use strict";
4650
+ _cached2 = null;
4651
+ _cachedCwd = null;
4652
+ }
4653
+ });
4654
+
4655
+ // src/lib/session-scope.ts
4656
+ var session_scope_exports = {};
4657
+ __export(session_scope_exports, {
4658
+ assertSessionScope: () => assertSessionScope,
4659
+ findSessionForProject: () => findSessionForProject,
4660
+ getSessionProject: () => getSessionProject
4661
+ });
4662
+ function getSessionProject(sessionName) {
4663
+ const sessions = listSessions();
4664
+ const entry = sessions.find((s) => s.windowName === sessionName);
4665
+ if (!entry) return null;
4666
+ const parts = entry.projectDir.split("/").filter(Boolean);
4667
+ return parts[parts.length - 1] ?? null;
4668
+ }
4669
+ function findSessionForProject(projectName) {
4670
+ const sessions = listSessions();
4671
+ for (const s of sessions) {
4672
+ const proj = s.projectDir.split("/").filter(Boolean).pop();
4673
+ if (proj === projectName && isCoordinatorName(s.agentId)) return s;
4674
+ }
4675
+ return null;
4676
+ }
4677
+ function assertSessionScope(actionType, targetProject) {
4678
+ try {
4679
+ const currentProject = getProjectName();
4680
+ const exeSession2 = resolveExeSession();
4681
+ if (!exeSession2) {
4682
+ return { allowed: true, reason: "no_session" };
4683
+ }
4684
+ if (currentProject === targetProject) {
4685
+ return {
4686
+ allowed: true,
4687
+ reason: "same_session",
4688
+ currentProject,
4689
+ targetProject
4690
+ };
4691
+ }
4692
+ process.stderr.write(
4693
+ `[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
4694
+ `
4695
+ );
4696
+ return {
4697
+ allowed: false,
4698
+ reason: "cross_session_denied",
4699
+ currentProject,
4700
+ targetProject,
4701
+ targetSession: findSessionForProject(targetProject)?.windowName
4702
+ };
4703
+ } catch {
4704
+ return { allowed: true, reason: "no_session" };
4705
+ }
4706
+ }
4707
+ var init_session_scope = __esm({
4708
+ "src/lib/session-scope.ts"() {
4709
+ "use strict";
4710
+ init_session_registry();
4711
+ init_project_name();
4712
+ init_tmux_routing();
4713
+ init_employees();
4714
+ }
4715
+ });
4716
+
4433
4717
  // src/lib/tasks-crud.ts
4434
4718
  var tasks_crud_exports = {};
4435
4719
  __export(tasks_crud_exports, {
@@ -4447,12 +4731,12 @@ __export(tasks_crud_exports, {
4447
4731
  updateTaskStatus: () => updateTaskStatus,
4448
4732
  writeCheckpoint: () => writeCheckpoint
4449
4733
  });
4450
- import crypto3 from "crypto";
4451
- import path13 from "path";
4452
- import os9 from "os";
4453
- import { execSync as execSync4 } from "child_process";
4734
+ import crypto4 from "crypto";
4735
+ import path15 from "path";
4736
+ import os10 from "os";
4737
+ import { execSync as execSync5 } from "child_process";
4454
4738
  import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
4455
- import { existsSync as existsSync12, readFileSync as readFileSync10 } from "fs";
4739
+ import { existsSync as existsSync14, readFileSync as readFileSync11 } from "fs";
4456
4740
  async function writeCheckpoint(input) {
4457
4741
  const client = getClient();
4458
4742
  const row = await resolveTask(client, input.taskId);
@@ -4568,13 +4852,28 @@ async function resolveTask(client, identifier, scopeSession) {
4568
4852
  }
4569
4853
  async function createTaskCore(input) {
4570
4854
  const client = getClient();
4571
- const id = crypto3.randomUUID();
4855
+ const id = crypto4.randomUUID();
4572
4856
  const now = (/* @__PURE__ */ new Date()).toISOString();
4573
4857
  const slug = slugify(input.title);
4574
4858
  let earlySessionScope = null;
4859
+ let scopeMismatchWarning;
4575
4860
  try {
4576
4861
  const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
4577
- earlySessionScope = resolveExeSession2();
4862
+ const resolved = resolveExeSession2();
4863
+ if (resolved && input.projectName) {
4864
+ const { getSessionProject: getSessionProject2 } = await Promise.resolve().then(() => (init_session_scope(), session_scope_exports));
4865
+ const sessionProject = getSessionProject2(resolved);
4866
+ if (sessionProject && sessionProject !== input.projectName) {
4867
+ scopeMismatchWarning = `session/project mismatch: session "${resolved}" owns "${sessionProject}" but task targets "${input.projectName}". Routed to default scope.`;
4868
+ process.stderr.write(`[create_task] ${scopeMismatchWarning}
4869
+ `);
4870
+ earlySessionScope = null;
4871
+ } else {
4872
+ earlySessionScope = resolved;
4873
+ }
4874
+ } else {
4875
+ earlySessionScope = resolved;
4876
+ }
4578
4877
  } catch {
4579
4878
  }
4580
4879
  const scope = earlySessionScope ?? "default";
@@ -4625,10 +4924,14 @@ async function createTaskCore(input) {
4625
4924
  ${laneWarning}` : laneWarning;
4626
4925
  }
4627
4926
  }
4927
+ if (scopeMismatchWarning) {
4928
+ warning = warning ? `${warning}
4929
+ ${scopeMismatchWarning}` : scopeMismatchWarning;
4930
+ }
4628
4931
  if (input.baseDir) {
4629
4932
  try {
4630
- await mkdir4(path13.join(input.baseDir, "exe", "output"), { recursive: true });
4631
- await mkdir4(path13.join(input.baseDir, "exe", "research"), { recursive: true });
4933
+ await mkdir4(path15.join(input.baseDir, "exe", "output"), { recursive: true });
4934
+ await mkdir4(path15.join(input.baseDir, "exe", "research"), { recursive: true });
4632
4935
  await ensureArchitectureDoc(input.baseDir, input.projectName);
4633
4936
  await ensureGitignoreExe(input.baseDir);
4634
4937
  } catch {
@@ -4664,13 +4967,19 @@ ${laneWarning}` : laneWarning;
4664
4967
  });
4665
4968
  if (input.baseDir) {
4666
4969
  try {
4667
- const EXE_OS_DIR = path13.join(os9.homedir(), ".exe-os");
4668
- const mdPath = path13.join(EXE_OS_DIR, taskFile);
4669
- const mdDir = path13.dirname(mdPath);
4670
- if (!existsSync12(mdDir)) await mkdir4(mdDir, { recursive: true });
4970
+ const EXE_OS_DIR = path15.join(os10.homedir(), ".exe-os");
4971
+ const mdPath = path15.join(EXE_OS_DIR, taskFile);
4972
+ const mdDir = path15.dirname(mdPath);
4973
+ if (!existsSync14(mdDir)) await mkdir4(mdDir, { recursive: true });
4671
4974
  const reviewer = input.reviewer ?? input.assignedBy;
4672
4975
  const mdContent = `# ${input.title}
4673
4976
 
4977
+ ## MANDATORY: When done
4978
+
4979
+ You MUST call update_task with status "done" and a result summary when finished.
4980
+ If you skip this, your reviewer will not know you're done and your work won't be reviewed.
4981
+ Do NOT let a failed commit or any error prevent you from calling update_task(done).
4982
+
4674
4983
  **ID:** ${id}
4675
4984
  **Status:** ${initialStatus}
4676
4985
  **Priority:** ${input.priority}
@@ -4684,12 +4993,6 @@ ${laneWarning}` : laneWarning;
4684
4993
  ## Context
4685
4994
 
4686
4995
  ${input.context}
4687
-
4688
- ## MANDATORY: When done
4689
-
4690
- You MUST call update_task with status "done" and a result summary when finished.
4691
- If you skip this, your reviewer will not know you're done and your work won't be reviewed.
4692
- Do NOT let a failed commit or any error prevent you from calling update_task(done).
4693
4996
  `;
4694
4997
  await writeFile4(mdPath, mdContent, "utf-8");
4695
4998
  } catch (err) {
@@ -4771,14 +5074,14 @@ function isTmuxSessionAlive(identifier) {
4771
5074
  if (!identifier || identifier === "unknown") return true;
4772
5075
  try {
4773
5076
  if (identifier.startsWith("%")) {
4774
- const output = execSync4("tmux list-panes -a -F '#{pane_id}'", {
5077
+ const output = execSync5("tmux list-panes -a -F '#{pane_id}'", {
4775
5078
  timeout: 2e3,
4776
5079
  encoding: "utf8",
4777
5080
  stdio: ["pipe", "pipe", "pipe"]
4778
5081
  });
4779
5082
  return output.split("\n").some((l) => l.trim() === identifier);
4780
5083
  } else {
4781
- execSync4(`tmux has-session -t ${JSON.stringify(identifier)}`, {
5084
+ execSync5(`tmux has-session -t ${JSON.stringify(identifier)}`, {
4782
5085
  timeout: 2e3,
4783
5086
  stdio: ["pipe", "pipe", "pipe"]
4784
5087
  });
@@ -4787,7 +5090,7 @@ function isTmuxSessionAlive(identifier) {
4787
5090
  } catch {
4788
5091
  if (identifier.startsWith("%")) return true;
4789
5092
  try {
4790
- execSync4("tmux list-sessions", {
5093
+ execSync5("tmux list-sessions", {
4791
5094
  timeout: 2e3,
4792
5095
  stdio: ["pipe", "pipe", "pipe"]
4793
5096
  });
@@ -4802,12 +5105,12 @@ function checkStaleCompletion(taskContext, taskCreatedAt) {
4802
5105
  if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
4803
5106
  try {
4804
5107
  const since = new Date(taskCreatedAt).toISOString();
4805
- const branch = execSync4(
5108
+ const branch = execSync5(
4806
5109
  "git rev-parse --abbrev-ref HEAD 2>/dev/null",
4807
5110
  { encoding: "utf8", timeout: 3e3 }
4808
5111
  ).trim();
4809
5112
  const branchArg = branch && branch !== "HEAD" ? branch : "";
4810
- const commitCount = execSync4(
5113
+ const commitCount = execSync5(
4811
5114
  `git log --oneline --since="${since}" ${branchArg} 2>/dev/null | wc -l`,
4812
5115
  { encoding: "utf8", timeout: 5e3 }
4813
5116
  ).trim();
@@ -4938,7 +5241,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
4938
5241
  await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
4939
5242
  } catch {
4940
5243
  }
4941
- if (input.status === "done" || input.status === "cancelled") {
5244
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
4942
5245
  try {
4943
5246
  const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
4944
5247
  clearQueueForAgent2(String(row.assigned_to));
@@ -4967,9 +5270,9 @@ async function deleteTaskCore(taskId, _baseDir) {
4967
5270
  return { taskFile, assignedTo, assignedBy, taskSlug };
4968
5271
  }
4969
5272
  async function ensureArchitectureDoc(baseDir, projectName) {
4970
- const archPath = path13.join(baseDir, "exe", "ARCHITECTURE.md");
5273
+ const archPath = path15.join(baseDir, "exe", "ARCHITECTURE.md");
4971
5274
  try {
4972
- if (existsSync12(archPath)) return;
5275
+ if (existsSync14(archPath)) return;
4973
5276
  const template = [
4974
5277
  `# ${projectName} \u2014 System Architecture`,
4975
5278
  "",
@@ -5002,10 +5305,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
5002
5305
  }
5003
5306
  }
5004
5307
  async function ensureGitignoreExe(baseDir) {
5005
- const gitignorePath = path13.join(baseDir, ".gitignore");
5308
+ const gitignorePath = path15.join(baseDir, ".gitignore");
5006
5309
  try {
5007
- if (existsSync12(gitignorePath)) {
5008
- const content = readFileSync10(gitignorePath, "utf-8");
5310
+ if (existsSync14(gitignorePath)) {
5311
+ const content = readFileSync11(gitignorePath, "utf-8");
5009
5312
  if (/^\/?exe\/?$/m.test(content)) return;
5010
5313
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
5011
5314
  } else {
@@ -5048,8 +5351,8 @@ __export(tasks_review_exports, {
5048
5351
  isStale: () => isStale,
5049
5352
  listPendingReviews: () => listPendingReviews
5050
5353
  });
5051
- import path14 from "path";
5052
- import { existsSync as existsSync13, readdirSync as readdirSync3, unlinkSync as unlinkSync4 } from "fs";
5354
+ import path16 from "path";
5355
+ import { existsSync as existsSync15, readdirSync as readdirSync3, unlinkSync as unlinkSync4 } from "fs";
5053
5356
  function formatAge(isoTimestamp) {
5054
5357
  if (!isoTimestamp) return "";
5055
5358
  const ms = Date.now() - new Date(isoTimestamp).getTime();
@@ -5067,54 +5370,38 @@ function isStale(isoTimestamp) {
5067
5370
  }
5068
5371
  async function countPendingReviews(sessionScope) {
5069
5372
  const client = getClient();
5070
- if (sessionScope) {
5071
- const result2 = await client.execute({
5072
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review' AND session_scope = ?",
5073
- args: [sessionScope]
5074
- });
5075
- return Number(result2.rows[0]?.cnt) || 0;
5076
- }
5373
+ const scope = strictSessionScopeFilter(
5374
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
5375
+ );
5077
5376
  const result = await client.execute({
5078
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review'",
5079
- args: []
5377
+ sql: `SELECT COUNT(*) as cnt FROM tasks
5378
+ WHERE status = 'needs_review'${scope.sql}`,
5379
+ args: [...scope.args]
5080
5380
  });
5081
5381
  return Number(result.rows[0]?.cnt) || 0;
5082
5382
  }
5083
5383
  async function countNewPendingReviewsSince(sinceIso, sessionScope) {
5084
5384
  const client = getClient();
5085
- if (sessionScope) {
5086
- const result2 = await client.execute({
5087
- sql: `SELECT COUNT(*) as cnt FROM tasks
5088
- WHERE status = 'needs_review' AND updated_at > ?
5089
- AND session_scope = ?`,
5090
- args: [sinceIso, sessionScope]
5091
- });
5092
- return Number(result2.rows[0]?.cnt) || 0;
5093
- }
5385
+ const scope = strictSessionScopeFilter(
5386
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
5387
+ );
5094
5388
  const result = await client.execute({
5095
5389
  sql: `SELECT COUNT(*) as cnt FROM tasks
5096
- WHERE status = 'needs_review' AND updated_at > ?`,
5097
- args: [sinceIso]
5390
+ WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
5391
+ args: [sinceIso, ...scope.args]
5098
5392
  });
5099
5393
  return Number(result.rows[0]?.cnt) || 0;
5100
5394
  }
5101
5395
  async function listPendingReviews(limit, sessionScope) {
5102
5396
  const client = getClient();
5103
- if (sessionScope) {
5104
- const result2 = await client.execute({
5105
- sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
5106
- WHERE status = 'needs_review'
5107
- AND session_scope = ?
5108
- ORDER BY updated_at ASC LIMIT ?`,
5109
- args: [sessionScope, limit]
5110
- });
5111
- return result2.rows;
5112
- }
5397
+ const scope = strictSessionScopeFilter(
5398
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
5399
+ );
5113
5400
  const result = await client.execute({
5114
5401
  sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
5115
- WHERE status = 'needs_review'
5402
+ WHERE status = 'needs_review'${scope.sql}
5116
5403
  ORDER BY updated_at ASC LIMIT ?`,
5117
- args: [limit]
5404
+ args: [...scope.args, limit]
5118
5405
  });
5119
5406
  return result.rows;
5120
5407
  }
@@ -5126,7 +5413,7 @@ async function cleanupOrphanedReviews() {
5126
5413
  WHERE status IN ('open', 'needs_review', 'in_progress')
5127
5414
  AND assigned_by = 'system'
5128
5415
  AND title LIKE 'Review:%'
5129
- AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
5416
+ AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
5130
5417
  args: [now]
5131
5418
  });
5132
5419
  const r1b = await client.execute({
@@ -5334,11 +5621,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
5334
5621
  );
5335
5622
  }
5336
5623
  try {
5337
- const cacheDir = path14.join(EXE_AI_DIR, "session-cache");
5338
- if (existsSync13(cacheDir)) {
5624
+ const cacheDir = path16.join(EXE_AI_DIR, "session-cache");
5625
+ if (existsSync15(cacheDir)) {
5339
5626
  for (const f of readdirSync3(cacheDir)) {
5340
5627
  if (f.startsWith("review-notified-")) {
5341
- unlinkSync4(path14.join(cacheDir, f));
5628
+ unlinkSync4(path16.join(cacheDir, f));
5342
5629
  }
5343
5630
  }
5344
5631
  }
@@ -5355,11 +5642,12 @@ var init_tasks_review = __esm({
5355
5642
  init_tmux_routing();
5356
5643
  init_session_key();
5357
5644
  init_state_bus();
5645
+ init_task_scope();
5358
5646
  }
5359
5647
  });
5360
5648
 
5361
5649
  // src/lib/tasks-chain.ts
5362
- import path15 from "path";
5650
+ import path17 from "path";
5363
5651
  import { readFile as readFile4, writeFile as writeFile5 } from "fs/promises";
5364
5652
  async function cascadeUnblock(taskId, baseDir, now) {
5365
5653
  const client = getClient();
@@ -5376,7 +5664,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
5376
5664
  });
5377
5665
  for (const ur of unblockedRows.rows) {
5378
5666
  try {
5379
- const ubFile = path15.join(baseDir, String(ur.task_file));
5667
+ const ubFile = path17.join(baseDir, String(ur.task_file));
5380
5668
  let ubContent = await readFile4(ubFile, "utf-8");
5381
5669
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
5382
5670
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -5411,7 +5699,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
5411
5699
  const scScope = sessionScopeFilter();
5412
5700
  const remaining = await client.execute({
5413
5701
  sql: `SELECT COUNT(*) as cnt FROM tasks
5414
- WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
5702
+ WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
5415
5703
  args: [parentTaskId, ...scScope.args]
5416
5704
  });
5417
5705
  const cnt = Number(remaining.rows[0]?.cnt ?? 1);
@@ -5443,110 +5731,6 @@ var init_tasks_chain = __esm({
5443
5731
  }
5444
5732
  });
5445
5733
 
5446
- // src/lib/project-name.ts
5447
- import { execSync as execSync5 } from "child_process";
5448
- import path16 from "path";
5449
- function getProjectName(cwd) {
5450
- const dir = cwd ?? process.cwd();
5451
- if (_cached2 && _cachedCwd === dir) return _cached2;
5452
- try {
5453
- let repoRoot;
5454
- try {
5455
- const gitCommonDir = execSync5("git rev-parse --path-format=absolute --git-common-dir", {
5456
- cwd: dir,
5457
- encoding: "utf8",
5458
- timeout: 2e3,
5459
- stdio: ["pipe", "pipe", "pipe"]
5460
- }).trim();
5461
- repoRoot = path16.dirname(gitCommonDir);
5462
- } catch {
5463
- repoRoot = execSync5("git rev-parse --show-toplevel", {
5464
- cwd: dir,
5465
- encoding: "utf8",
5466
- timeout: 2e3,
5467
- stdio: ["pipe", "pipe", "pipe"]
5468
- }).trim();
5469
- }
5470
- _cached2 = path16.basename(repoRoot);
5471
- _cachedCwd = dir;
5472
- return _cached2;
5473
- } catch {
5474
- _cached2 = path16.basename(dir);
5475
- _cachedCwd = dir;
5476
- return _cached2;
5477
- }
5478
- }
5479
- var _cached2, _cachedCwd;
5480
- var init_project_name = __esm({
5481
- "src/lib/project-name.ts"() {
5482
- "use strict";
5483
- _cached2 = null;
5484
- _cachedCwd = null;
5485
- }
5486
- });
5487
-
5488
- // src/lib/session-scope.ts
5489
- var session_scope_exports = {};
5490
- __export(session_scope_exports, {
5491
- assertSessionScope: () => assertSessionScope,
5492
- findSessionForProject: () => findSessionForProject,
5493
- getSessionProject: () => getSessionProject
5494
- });
5495
- function getSessionProject(sessionName) {
5496
- const sessions = listSessions();
5497
- const entry = sessions.find((s) => s.windowName === sessionName);
5498
- if (!entry) return null;
5499
- const parts = entry.projectDir.split("/").filter(Boolean);
5500
- return parts[parts.length - 1] ?? null;
5501
- }
5502
- function findSessionForProject(projectName) {
5503
- const sessions = listSessions();
5504
- for (const s of sessions) {
5505
- const proj = s.projectDir.split("/").filter(Boolean).pop();
5506
- if (proj === projectName && isCoordinatorName(s.agentId)) return s;
5507
- }
5508
- return null;
5509
- }
5510
- function assertSessionScope(actionType, targetProject) {
5511
- try {
5512
- const currentProject = getProjectName();
5513
- const exeSession2 = resolveExeSession();
5514
- if (!exeSession2) {
5515
- return { allowed: true, reason: "no_session" };
5516
- }
5517
- if (currentProject === targetProject) {
5518
- return {
5519
- allowed: true,
5520
- reason: "same_session",
5521
- currentProject,
5522
- targetProject
5523
- };
5524
- }
5525
- process.stderr.write(
5526
- `[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
5527
- `
5528
- );
5529
- return {
5530
- allowed: false,
5531
- reason: "cross_session_denied",
5532
- currentProject,
5533
- targetProject,
5534
- targetSession: findSessionForProject(targetProject)?.windowName
5535
- };
5536
- } catch {
5537
- return { allowed: true, reason: "no_session" };
5538
- }
5539
- }
5540
- var init_session_scope = __esm({
5541
- "src/lib/session-scope.ts"() {
5542
- "use strict";
5543
- init_session_registry();
5544
- init_project_name();
5545
- init_tmux_routing();
5546
- init_employees();
5547
- }
5548
- });
5549
-
5550
5734
  // src/lib/tasks-notify.ts
5551
5735
  async function dispatchTaskToEmployee(input) {
5552
5736
  if (isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
@@ -5614,10 +5798,10 @@ var init_tasks_notify = __esm({
5614
5798
  });
5615
5799
 
5616
5800
  // src/lib/behaviors.ts
5617
- import crypto4 from "crypto";
5801
+ import crypto5 from "crypto";
5618
5802
  async function storeBehavior(opts) {
5619
5803
  const client = getClient();
5620
- const id = crypto4.randomUUID();
5804
+ const id = crypto5.randomUUID();
5621
5805
  const now = (/* @__PURE__ */ new Date()).toISOString();
5622
5806
  await client.execute({
5623
5807
  sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
@@ -5646,7 +5830,7 @@ __export(skill_learning_exports, {
5646
5830
  storeTrajectory: () => storeTrajectory,
5647
5831
  sweepTrajectories: () => sweepTrajectories
5648
5832
  });
5649
- import crypto5 from "crypto";
5833
+ import crypto6 from "crypto";
5650
5834
  async function extractTrajectory(taskId, agentId) {
5651
5835
  const client = getClient();
5652
5836
  const result = await client.execute({
@@ -5675,11 +5859,11 @@ async function extractTrajectory(taskId, agentId) {
5675
5859
  return signature;
5676
5860
  }
5677
5861
  function hashSignature(signature) {
5678
- return crypto5.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
5862
+ return crypto6.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
5679
5863
  }
5680
5864
  async function storeTrajectory(opts) {
5681
5865
  const client = getClient();
5682
- const id = crypto5.randomUUID();
5866
+ const id = crypto6.randomUUID();
5683
5867
  const now = (/* @__PURE__ */ new Date()).toISOString();
5684
5868
  const signatureHash = hashSignature(opts.signature);
5685
5869
  await client.execute({
@@ -5944,8 +6128,8 @@ __export(tasks_exports, {
5944
6128
  updateTaskStatus: () => updateTaskStatus,
5945
6129
  writeCheckpoint: () => writeCheckpoint
5946
6130
  });
5947
- import path17 from "path";
5948
- import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync6, unlinkSync as unlinkSync5 } from "fs";
6131
+ import path18 from "path";
6132
+ import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, unlinkSync as unlinkSync5 } from "fs";
5949
6133
  async function createTask(input) {
5950
6134
  const result = await createTaskCore(input);
5951
6135
  if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -5964,12 +6148,12 @@ async function updateTask(input) {
5964
6148
  const { row, taskFile, now, taskId } = await updateTaskStatus(input);
5965
6149
  try {
5966
6150
  const agent = String(row.assigned_to);
5967
- const cacheDir = path17.join(EXE_AI_DIR, "session-cache");
5968
- const cachePath = path17.join(cacheDir, `current-task-${agent}.json`);
6151
+ const cacheDir = path18.join(EXE_AI_DIR, "session-cache");
6152
+ const cachePath = path18.join(cacheDir, `current-task-${agent}.json`);
5969
6153
  if (input.status === "in_progress") {
5970
6154
  mkdirSync6(cacheDir, { recursive: true });
5971
- writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
5972
- } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
6155
+ writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
6156
+ } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
5973
6157
  try {
5974
6158
  unlinkSync5(cachePath);
5975
6159
  } catch {
@@ -5977,10 +6161,10 @@ async function updateTask(input) {
5977
6161
  }
5978
6162
  } catch {
5979
6163
  }
5980
- if (input.status === "done") {
6164
+ if (input.status === "done" || input.status === "closed") {
5981
6165
  await cleanupReviewFile(row, taskFile, input.baseDir);
5982
6166
  }
5983
- if (input.status === "done" || input.status === "cancelled") {
6167
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
5984
6168
  try {
5985
6169
  const client = getClient();
5986
6170
  const taskTitle = String(row.title);
@@ -5996,7 +6180,7 @@ async function updateTask(input) {
5996
6180
  if (!isCoordinatorName(assignedAgent)) {
5997
6181
  try {
5998
6182
  const draftClient = getClient();
5999
- if (input.status === "done") {
6183
+ if (input.status === "done" || input.status === "closed") {
6000
6184
  await draftClient.execute({
6001
6185
  sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
6002
6186
  args: [assignedAgent]
@@ -6013,7 +6197,7 @@ async function updateTask(input) {
6013
6197
  try {
6014
6198
  const client = getClient();
6015
6199
  const cascaded = await client.execute({
6016
- sql: `UPDATE tasks SET status = 'done', updated_at = ?
6200
+ sql: `UPDATE tasks SET status = 'closed', updated_at = ?
6017
6201
  WHERE parent_task_id = ? AND status = 'needs_review'`,
6018
6202
  args: [now, taskId]
6019
6203
  });
@@ -6026,14 +6210,14 @@ async function updateTask(input) {
6026
6210
  } catch {
6027
6211
  }
6028
6212
  }
6029
- const isTerminal = input.status === "done" || input.status === "needs_review";
6213
+ const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
6030
6214
  if (isTerminal) {
6031
6215
  const isCoordinator = isCoordinatorName(String(row.assigned_to));
6032
6216
  if (!isCoordinator) {
6033
6217
  notifyTaskDone();
6034
6218
  }
6035
6219
  await markTaskNotificationsRead(taskFile);
6036
- if (input.status === "done") {
6220
+ if (input.status === "done" || input.status === "closed") {
6037
6221
  try {
6038
6222
  await cascadeUnblock(taskId, input.baseDir, now);
6039
6223
  } catch {
@@ -6053,7 +6237,7 @@ async function updateTask(input) {
6053
6237
  }
6054
6238
  }
6055
6239
  }
6056
- if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
6240
+ if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
6057
6241
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
6058
6242
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
6059
6243
  taskId,
@@ -6425,6 +6609,7 @@ __export(tmux_routing_exports, {
6425
6609
  isEmployeeAlive: () => isEmployeeAlive,
6426
6610
  isExeSession: () => isExeSession,
6427
6611
  isSessionBusy: () => isSessionBusy,
6612
+ notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
6428
6613
  notifyParentExe: () => notifyParentExe,
6429
6614
  parseParentExe: () => parseParentExe,
6430
6615
  registerParentExe: () => registerParentExe,
@@ -6435,13 +6620,13 @@ __export(tmux_routing_exports, {
6435
6620
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
6436
6621
  });
6437
6622
  import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
6438
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync7, mkdirSync as mkdirSync7, existsSync as existsSync14, appendFileSync, readdirSync as readdirSync4 } from "fs";
6439
- import path18 from "path";
6440
- import os10 from "os";
6623
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, mkdirSync as mkdirSync7, existsSync as existsSync16, appendFileSync, readdirSync as readdirSync4 } from "fs";
6624
+ import path19 from "path";
6625
+ import os11 from "os";
6441
6626
  import { fileURLToPath as fileURLToPath2 } from "url";
6442
6627
  import { unlinkSync as unlinkSync6 } from "fs";
6443
6628
  function spawnLockPath(sessionName) {
6444
- return path18.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
6629
+ return path19.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
6445
6630
  }
6446
6631
  function isProcessAlive(pid) {
6447
6632
  try {
@@ -6452,13 +6637,13 @@ function isProcessAlive(pid) {
6452
6637
  }
6453
6638
  }
6454
6639
  function acquireSpawnLock2(sessionName) {
6455
- if (!existsSync14(SPAWN_LOCK_DIR)) {
6640
+ if (!existsSync16(SPAWN_LOCK_DIR)) {
6456
6641
  mkdirSync7(SPAWN_LOCK_DIR, { recursive: true });
6457
6642
  }
6458
6643
  const lockFile = spawnLockPath(sessionName);
6459
- if (existsSync14(lockFile)) {
6644
+ if (existsSync16(lockFile)) {
6460
6645
  try {
6461
- const lock = JSON.parse(readFileSync11(lockFile, "utf8"));
6646
+ const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
6462
6647
  const age = Date.now() - lock.timestamp;
6463
6648
  if (isProcessAlive(lock.pid) && age < 6e4) {
6464
6649
  return false;
@@ -6466,7 +6651,7 @@ function acquireSpawnLock2(sessionName) {
6466
6651
  } catch {
6467
6652
  }
6468
6653
  }
6469
- writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
6654
+ writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
6470
6655
  return true;
6471
6656
  }
6472
6657
  function releaseSpawnLock2(sessionName) {
@@ -6478,13 +6663,13 @@ function releaseSpawnLock2(sessionName) {
6478
6663
  function resolveBehaviorsExporterScript() {
6479
6664
  try {
6480
6665
  const thisFile = fileURLToPath2(import.meta.url);
6481
- const scriptPath = path18.join(
6482
- path18.dirname(thisFile),
6666
+ const scriptPath = path19.join(
6667
+ path19.dirname(thisFile),
6483
6668
  "..",
6484
6669
  "bin",
6485
6670
  "exe-export-behaviors.js"
6486
6671
  );
6487
- return existsSync14(scriptPath) ? scriptPath : null;
6672
+ return existsSync16(scriptPath) ? scriptPath : null;
6488
6673
  } catch {
6489
6674
  return null;
6490
6675
  }
@@ -6550,12 +6735,12 @@ function extractRootExe(name) {
6550
6735
  return parts.length > 0 ? parts[parts.length - 1] : null;
6551
6736
  }
6552
6737
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
6553
- if (!existsSync14(SESSION_CACHE)) {
6738
+ if (!existsSync16(SESSION_CACHE)) {
6554
6739
  mkdirSync7(SESSION_CACHE, { recursive: true });
6555
6740
  }
6556
6741
  const rootExe = extractRootExe(parentExe) ?? parentExe;
6557
- const filePath = path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
6558
- writeFileSync7(filePath, JSON.stringify({
6742
+ const filePath = path19.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
6743
+ writeFileSync8(filePath, JSON.stringify({
6559
6744
  parentExe: rootExe,
6560
6745
  dispatchedBy: dispatchedBy || rootExe,
6561
6746
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -6563,7 +6748,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
6563
6748
  }
6564
6749
  function getParentExe(sessionKey) {
6565
6750
  try {
6566
- const data = JSON.parse(readFileSync11(path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
6751
+ const data = JSON.parse(readFileSync12(path19.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
6567
6752
  return data.parentExe || null;
6568
6753
  } catch {
6569
6754
  return null;
@@ -6571,8 +6756,8 @@ function getParentExe(sessionKey) {
6571
6756
  }
6572
6757
  function getDispatchedBy(sessionKey) {
6573
6758
  try {
6574
- const data = JSON.parse(readFileSync11(
6575
- path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
6759
+ const data = JSON.parse(readFileSync12(
6760
+ path19.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
6576
6761
  "utf8"
6577
6762
  ));
6578
6763
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -6642,8 +6827,8 @@ async function verifyPaneAtCapacity(sessionName) {
6642
6827
  }
6643
6828
  function readDebounceState() {
6644
6829
  try {
6645
- if (!existsSync14(DEBOUNCE_FILE)) return {};
6646
- const raw = JSON.parse(readFileSync11(DEBOUNCE_FILE, "utf8"));
6830
+ if (!existsSync16(DEBOUNCE_FILE)) return {};
6831
+ const raw = JSON.parse(readFileSync12(DEBOUNCE_FILE, "utf8"));
6647
6832
  const state = {};
6648
6833
  for (const [key, val] of Object.entries(raw)) {
6649
6834
  if (typeof val === "number") {
@@ -6659,8 +6844,8 @@ function readDebounceState() {
6659
6844
  }
6660
6845
  function writeDebounceState(state) {
6661
6846
  try {
6662
- if (!existsSync14(SESSION_CACHE)) mkdirSync7(SESSION_CACHE, { recursive: true });
6663
- writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
6847
+ if (!existsSync16(SESSION_CACHE)) mkdirSync7(SESSION_CACHE, { recursive: true });
6848
+ writeFileSync8(DEBOUNCE_FILE, JSON.stringify(state));
6664
6849
  } catch {
6665
6850
  }
6666
6851
  }
@@ -6758,8 +6943,8 @@ function sendIntercom(targetSession) {
6758
6943
  try {
6759
6944
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
6760
6945
  const agent = baseAgentName(rawAgent);
6761
- const markerPath = path18.join(SESSION_CACHE, `current-task-${agent}.json`);
6762
- if (existsSync14(markerPath)) {
6946
+ const markerPath = path19.join(SESSION_CACHE, `current-task-${agent}.json`);
6947
+ if (existsSync16(markerPath)) {
6763
6948
  logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
6764
6949
  return "debounced";
6765
6950
  }
@@ -6768,8 +6953,8 @@ function sendIntercom(targetSession) {
6768
6953
  try {
6769
6954
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
6770
6955
  const agent = baseAgentName(rawAgent);
6771
- const taskDir = path18.join(process.cwd(), "exe", agent);
6772
- if (existsSync14(taskDir)) {
6956
+ const taskDir = path19.join(process.cwd(), "exe", agent);
6957
+ if (existsSync16(taskDir)) {
6773
6958
  const files = readdirSync4(taskDir).filter(
6774
6959
  (f) => f.endsWith(".md") && f !== "DONE.txt"
6775
6960
  );
@@ -6829,6 +7014,21 @@ function notifyParentExe(sessionKey) {
6829
7014
  }
6830
7015
  return true;
6831
7016
  }
7017
+ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName2, taskTitle) {
7018
+ const transport = getTransport();
7019
+ try {
7020
+ const sessions = transport.listSessions();
7021
+ if (!sessions.includes(coordinatorSession)) return false;
7022
+ execSync6(
7023
+ `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
7024
+ { timeout: 3e3 }
7025
+ );
7026
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName2} completed "${taskTitle.slice(0, 50)}")`);
7027
+ return true;
7028
+ } catch {
7029
+ return false;
7030
+ }
7031
+ }
6832
7032
  function ensureEmployee(employeeName, exeSession2, projectDir, opts) {
6833
7033
  if (isCoordinatorName(employeeName)) {
6834
7034
  return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
@@ -6902,26 +7102,26 @@ function spawnEmployee(employeeName, exeSession2, projectDir, opts) {
6902
7102
  const transport = getTransport();
6903
7103
  const sessionName = employeeSessionName(employeeName, exeSession2, opts?.instance);
6904
7104
  const instanceLabel2 = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
6905
- const logDir = path18.join(os10.homedir(), ".exe-os", "session-logs");
6906
- const logFile = path18.join(logDir, `${instanceLabel2}-${Date.now()}.log`);
6907
- if (!existsSync14(logDir)) {
7105
+ const logDir = path19.join(os11.homedir(), ".exe-os", "session-logs");
7106
+ const logFile = path19.join(logDir, `${instanceLabel2}-${Date.now()}.log`);
7107
+ if (!existsSync16(logDir)) {
6908
7108
  mkdirSync7(logDir, { recursive: true });
6909
7109
  }
6910
7110
  transport.kill(sessionName);
6911
7111
  let cleanupSuffix = "";
6912
7112
  try {
6913
7113
  const thisFile = fileURLToPath2(import.meta.url);
6914
- const cleanupScript = path18.join(path18.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
6915
- if (existsSync14(cleanupScript)) {
7114
+ const cleanupScript = path19.join(path19.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
7115
+ if (existsSync16(cleanupScript)) {
6916
7116
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession2}"`;
6917
7117
  }
6918
7118
  } catch {
6919
7119
  }
6920
7120
  try {
6921
- const claudeJsonPath = path18.join(os10.homedir(), ".claude.json");
7121
+ const claudeJsonPath = path19.join(os11.homedir(), ".claude.json");
6922
7122
  let claudeJson = {};
6923
7123
  try {
6924
- claudeJson = JSON.parse(readFileSync11(claudeJsonPath, "utf8"));
7124
+ claudeJson = JSON.parse(readFileSync12(claudeJsonPath, "utf8"));
6925
7125
  } catch {
6926
7126
  }
6927
7127
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -6929,17 +7129,17 @@ function spawnEmployee(employeeName, exeSession2, projectDir, opts) {
6929
7129
  const trustDir = opts?.cwd ?? projectDir;
6930
7130
  if (!projects[trustDir]) projects[trustDir] = {};
6931
7131
  projects[trustDir].hasTrustDialogAccepted = true;
6932
- writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
7132
+ writeFileSync8(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
6933
7133
  } catch {
6934
7134
  }
6935
7135
  try {
6936
- const settingsDir = path18.join(os10.homedir(), ".claude", "projects");
7136
+ const settingsDir = path19.join(os11.homedir(), ".claude", "projects");
6937
7137
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
6938
- const projSettingsDir = path18.join(settingsDir, normalizedKey);
6939
- const settingsPath = path18.join(projSettingsDir, "settings.json");
7138
+ const projSettingsDir = path19.join(settingsDir, normalizedKey);
7139
+ const settingsPath = path19.join(projSettingsDir, "settings.json");
6940
7140
  let settings = {};
6941
7141
  try {
6942
- settings = JSON.parse(readFileSync11(settingsPath, "utf8"));
7142
+ settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
6943
7143
  } catch {
6944
7144
  }
6945
7145
  const perms = settings.permissions ?? {};
@@ -6968,7 +7168,7 @@ function spawnEmployee(employeeName, exeSession2, projectDir, opts) {
6968
7168
  perms.allow = allow;
6969
7169
  settings.permissions = perms;
6970
7170
  mkdirSync7(projSettingsDir, { recursive: true });
6971
- writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + "\n");
7171
+ writeFileSync8(settingsPath, JSON.stringify(settings, null, 2) + "\n");
6972
7172
  }
6973
7173
  } catch {
6974
7174
  }
@@ -6983,8 +7183,8 @@ function spawnEmployee(employeeName, exeSession2, projectDir, opts) {
6983
7183
  let behaviorsFlag = "";
6984
7184
  let legacyFallbackWarned = false;
6985
7185
  if (!useExeAgent && !useBinSymlink) {
6986
- const identityPath = path18.join(
6987
- os10.homedir(),
7186
+ const identityPath = path19.join(
7187
+ os11.homedir(),
6988
7188
  ".exe-os",
6989
7189
  "identity",
6990
7190
  `${employeeName}.md`
@@ -6993,13 +7193,13 @@ function spawnEmployee(employeeName, exeSession2, projectDir, opts) {
6993
7193
  const hasAgentFlag = claudeSupportsAgentFlag();
6994
7194
  if (hasAgentFlag) {
6995
7195
  identityFlag = ` --agent ${employeeName}`;
6996
- } else if (existsSync14(identityPath)) {
7196
+ } else if (existsSync16(identityPath)) {
6997
7197
  identityFlag = ` --append-system-prompt-file ${identityPath}`;
6998
7198
  legacyFallbackWarned = true;
6999
7199
  }
7000
7200
  const behaviorsFile = exportBehaviorsSync(
7001
7201
  employeeName,
7002
- path18.basename(spawnCwd),
7202
+ path19.basename(spawnCwd),
7003
7203
  sessionName
7004
7204
  );
7005
7205
  if (behaviorsFile) {
@@ -7014,16 +7214,16 @@ function spawnEmployee(employeeName, exeSession2, projectDir, opts) {
7014
7214
  }
7015
7215
  let sessionContextFlag = "";
7016
7216
  try {
7017
- const ctxDir = path18.join(os10.homedir(), ".exe-os", "session-cache");
7217
+ const ctxDir = path19.join(os11.homedir(), ".exe-os", "session-cache");
7018
7218
  mkdirSync7(ctxDir, { recursive: true });
7019
- const ctxFile = path18.join(ctxDir, `session-context-${sessionName}.md`);
7219
+ const ctxFile = path19.join(ctxDir, `session-context-${sessionName}.md`);
7020
7220
  const ctxContent = [
7021
7221
  `## Session Context`,
7022
7222
  `You are running in tmux session: ${sessionName}.`,
7023
7223
  `Your parent coordinator session is ${exeSession2}.`,
7024
7224
  `Your employees (if any) use the -${exeSession2} suffix.`
7025
7225
  ].join("\n");
7026
- writeFileSync7(ctxFile, ctxContent);
7226
+ writeFileSync8(ctxFile, ctxContent);
7027
7227
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
7028
7228
  } catch {
7029
7229
  }
@@ -7100,8 +7300,8 @@ function spawnEmployee(employeeName, exeSession2, projectDir, opts) {
7100
7300
  transport.pipeLog(sessionName, logFile);
7101
7301
  try {
7102
7302
  const mySession = getMySession();
7103
- const dispatchInfo = path18.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
7104
- writeFileSync7(dispatchInfo, JSON.stringify({
7303
+ const dispatchInfo = path19.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
7304
+ writeFileSync8(dispatchInfo, JSON.stringify({
7105
7305
  dispatchedBy: mySession,
7106
7306
  rootExe: exeSession2,
7107
7307
  provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
@@ -7175,15 +7375,15 @@ var init_tmux_routing = __esm({
7175
7375
  init_intercom_queue();
7176
7376
  init_plan_limits();
7177
7377
  init_employees();
7178
- SPAWN_LOCK_DIR = path18.join(os10.homedir(), ".exe-os", "spawn-locks");
7179
- SESSION_CACHE = path18.join(os10.homedir(), ".exe-os", "session-cache");
7378
+ SPAWN_LOCK_DIR = path19.join(os11.homedir(), ".exe-os", "spawn-locks");
7379
+ SESSION_CACHE = path19.join(os11.homedir(), ".exe-os", "session-cache");
7180
7380
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
7181
7381
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
7182
7382
  VERIFY_PANE_LINES = 200;
7183
7383
  INTERCOM_DEBOUNCE_MS = 3e4;
7184
7384
  CODEX_DEBOUNCE_MS = 12e4;
7185
- INTERCOM_LOG2 = path18.join(os10.homedir(), ".exe-os", "intercom.log");
7186
- DEBOUNCE_FILE = path18.join(SESSION_CACHE, "intercom-debounce.json");
7385
+ INTERCOM_LOG2 = path19.join(os11.homedir(), ".exe-os", "intercom.log");
7386
+ DEBOUNCE_FILE = path19.join(SESSION_CACHE, "intercom-debounce.json");
7187
7387
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
7188
7388
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
7189
7389
  }
@@ -7206,6 +7406,15 @@ function sessionScopeFilter(sessionScope, tableAlias) {
7206
7406
  args: [scope]
7207
7407
  };
7208
7408
  }
7409
+ function strictSessionScopeFilter(sessionScope, tableAlias) {
7410
+ const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
7411
+ if (!scope) return { sql: "", args: [] };
7412
+ const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
7413
+ return {
7414
+ sql: ` AND ${col} = ?`,
7415
+ args: [scope]
7416
+ };
7417
+ }
7209
7418
  var init_task_scope = __esm({
7210
7419
  "src/lib/task-scope.ts"() {
7211
7420
  "use strict";
@@ -7250,10 +7459,10 @@ async function disposeEmbedder() {
7250
7459
  async function embedDirect(text) {
7251
7460
  const llamaCpp = await import("node-llama-cpp");
7252
7461
  const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
7253
- const { existsSync: existsSync16 } = await import("fs");
7254
- const path20 = await import("path");
7255
- const modelPath = path20.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
7256
- if (!existsSync16(modelPath)) {
7462
+ const { existsSync: existsSync18 } = await import("fs");
7463
+ const path21 = await import("path");
7464
+ const modelPath = path21.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
7465
+ if (!existsSync18(modelPath)) {
7257
7466
  throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
7258
7467
  }
7259
7468
  const llama = await llamaCpp.getLlama();
@@ -7511,8 +7720,8 @@ __export(worktree_exports, {
7511
7720
  worktreePath: () => worktreePath
7512
7721
  });
7513
7722
  import { execSync as execSync8 } from "child_process";
7514
- import { existsSync as existsSync15, readFileSync as readFileSync12, appendFileSync as appendFileSync2, mkdirSync as mkdirSync8, realpathSync } from "fs";
7515
- import path19 from "path";
7723
+ import { existsSync as existsSync17, readFileSync as readFileSync13, appendFileSync as appendFileSync2, mkdirSync as mkdirSync8, realpathSync } from "fs";
7724
+ import path20 from "path";
7516
7725
  function getGitRoot(dir) {
7517
7726
  try {
7518
7727
  const root = execSync8("git rev-parse --show-toplevel", {
@@ -7532,14 +7741,14 @@ function getMainRepoRoot(dir) {
7532
7741
  "git rev-parse --path-format=absolute --git-common-dir",
7533
7742
  { cwd: dir, encoding: "utf-8", timeout: GIT_TIMEOUT_MS, stdio: ["pipe", "pipe", "pipe"] }
7534
7743
  ).trim();
7535
- return realpath(path19.dirname(commonDir));
7744
+ return realpath(path20.dirname(commonDir));
7536
7745
  } catch {
7537
7746
  return null;
7538
7747
  }
7539
7748
  }
7540
7749
  function worktreePath(repoRoot, employeeName, instance) {
7541
7750
  const label = instanceLabel(employeeName, instance);
7542
- return path19.join(repoRoot, ".worktrees", label);
7751
+ return path20.join(repoRoot, ".worktrees", label);
7543
7752
  }
7544
7753
  function worktreeBranch(employeeName, instance) {
7545
7754
  return `${instanceLabel(employeeName, instance)}-work`;
@@ -7552,10 +7761,10 @@ function ensureWorktree(projectDir, employeeName, instance) {
7552
7761
  if (!repoRoot) return null;
7553
7762
  const wtPath = worktreePath(repoRoot, employeeName, instance);
7554
7763
  const branch = worktreeBranch(employeeName, instance);
7555
- if (existsSync15(path19.join(wtPath, ".git"))) {
7764
+ if (existsSync17(path20.join(wtPath, ".git"))) {
7556
7765
  return wtPath;
7557
7766
  }
7558
- const worktreesDir = path19.join(repoRoot, ".worktrees");
7767
+ const worktreesDir = path20.join(repoRoot, ".worktrees");
7559
7768
  mkdirSync8(worktreesDir, { recursive: true });
7560
7769
  ensureGitignoreEntry(repoRoot, "/.worktrees/");
7561
7770
  try {
@@ -7611,7 +7820,7 @@ function cleanupWorktree(projectDir, employeeName, instance) {
7611
7820
  if (!repoRoot) return { cleaned: false, reason: "not a git repo" };
7612
7821
  const wtPath = worktreePath(repoRoot, employeeName, instance);
7613
7822
  const branch = worktreeBranch(employeeName, instance);
7614
- if (!existsSync15(wtPath)) {
7823
+ if (!existsSync17(wtPath)) {
7615
7824
  return { cleaned: false, reason: "worktree does not exist" };
7616
7825
  }
7617
7826
  if (isWorktreeDirty(wtPath)) {
@@ -7689,9 +7898,9 @@ function realpath(p) {
7689
7898
  }
7690
7899
  function ensureGitignoreEntry(repoRoot, entry) {
7691
7900
  try {
7692
- const gitignorePath = path19.join(repoRoot, ".gitignore");
7693
- if (existsSync15(gitignorePath)) {
7694
- const content = readFileSync12(gitignorePath, "utf-8");
7901
+ const gitignorePath = path20.join(repoRoot, ".gitignore");
7902
+ if (existsSync17(gitignorePath)) {
7903
+ const content = readFileSync13(gitignorePath, "utf-8");
7695
7904
  if (content.includes(entry)) return;
7696
7905
  appendFileSync2(gitignorePath, `
7697
7906
  # Agent worktrees (exe-os)
@@ -7715,7 +7924,7 @@ init_store();
7715
7924
  init_database();
7716
7925
  init_task_scope();
7717
7926
  init_project_name();
7718
- import crypto6 from "crypto";
7927
+ import crypto7 from "crypto";
7719
7928
  import { execSync as execSync9 } from "child_process";
7720
7929
  var agentName = process.argv[2];
7721
7930
  var exeSession = process.argv[3];
@@ -7769,7 +7978,7 @@ try {
7769
7978
  } catch {
7770
7979
  }
7771
7980
  await writeMemory({
7772
- id: crypto6.randomUUID(),
7981
+ id: crypto7.randomUUID(),
7773
7982
  agent_id: agentName,
7774
7983
  agent_role: "employee",
7775
7984
  session_id: `cleanup-${Date.now()}`,