@askexenow/exe-os 0.9.8 → 0.9.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/dist/bin/backfill-conversations.js +222 -49
  2. package/dist/bin/backfill-responses.js +221 -48
  3. package/dist/bin/backfill-vectors.js +225 -52
  4. package/dist/bin/cleanup-stale-review-tasks.js +150 -28
  5. package/dist/bin/cli.js +1295 -856
  6. package/dist/bin/exe-agent-config.js +36 -8
  7. package/dist/bin/exe-agent.js +14 -4
  8. package/dist/bin/exe-assign.js +221 -48
  9. package/dist/bin/exe-boot.js +778 -427
  10. package/dist/bin/exe-call.js +41 -13
  11. package/dist/bin/exe-cloud.js +163 -58
  12. package/dist/bin/exe-dispatch.js +276 -139
  13. package/dist/bin/exe-doctor.js +145 -27
  14. package/dist/bin/exe-export-behaviors.js +141 -23
  15. package/dist/bin/exe-forget.js +137 -19
  16. package/dist/bin/exe-gateway.js +677 -388
  17. package/dist/bin/exe-heartbeat.js +227 -108
  18. package/dist/bin/exe-kill.js +138 -20
  19. package/dist/bin/exe-launch-agent.js +172 -39
  20. package/dist/bin/exe-link.js +291 -100
  21. package/dist/bin/exe-new-employee.js +214 -106
  22. package/dist/bin/exe-pending-messages.js +395 -33
  23. package/dist/bin/exe-pending-notifications.js +684 -99
  24. package/dist/bin/exe-pending-reviews.js +420 -74
  25. package/dist/bin/exe-rename.js +147 -49
  26. package/dist/bin/exe-review.js +138 -20
  27. package/dist/bin/exe-search.js +240 -69
  28. package/dist/bin/exe-session-cleanup.js +440 -250
  29. package/dist/bin/exe-settings.js +61 -17
  30. package/dist/bin/exe-start-codex.js +158 -39
  31. package/dist/bin/exe-start-opencode.js +157 -38
  32. package/dist/bin/exe-status.js +151 -29
  33. package/dist/bin/exe-team.js +138 -20
  34. package/dist/bin/git-sweep.js +404 -212
  35. package/dist/bin/graph-backfill.js +137 -19
  36. package/dist/bin/graph-export.js +140 -22
  37. package/dist/bin/install.js +90 -61
  38. package/dist/bin/scan-tasks.js +412 -220
  39. package/dist/bin/setup.js +564 -293
  40. package/dist/bin/shard-migrate.js +139 -21
  41. package/dist/bin/update.js +138 -49
  42. package/dist/bin/wiki-sync.js +137 -19
  43. package/dist/gateway/index.js +533 -320
  44. package/dist/hooks/bug-report-worker.js +344 -193
  45. package/dist/hooks/codex-stop-task-finalizer.js +4678 -0
  46. package/dist/hooks/commit-complete.js +402 -210
  47. package/dist/hooks/error-recall.js +245 -74
  48. package/dist/hooks/exe-heartbeat-hook.js +16 -6
  49. package/dist/hooks/ingest-worker.js +3423 -3157
  50. package/dist/hooks/ingest.js +832 -97
  51. package/dist/hooks/instructions-loaded.js +227 -54
  52. package/dist/hooks/notification.js +216 -43
  53. package/dist/hooks/post-compact.js +239 -62
  54. package/dist/hooks/pre-compact.js +408 -216
  55. package/dist/hooks/pre-tool-use.js +268 -90
  56. package/dist/hooks/prompt-ingest-worker.js +352 -102
  57. package/dist/hooks/prompt-submit.js +541 -328
  58. package/dist/hooks/response-ingest-worker.js +372 -122
  59. package/dist/hooks/session-end.js +443 -240
  60. package/dist/hooks/session-start.js +313 -127
  61. package/dist/hooks/stop.js +293 -98
  62. package/dist/hooks/subagent-stop.js +239 -62
  63. package/dist/hooks/summary-worker.js +568 -236
  64. package/dist/index.js +538 -324
  65. package/dist/lib/agent-config.js +28 -6
  66. package/dist/lib/cloud-sync.js +284 -105
  67. package/dist/lib/config.js +30 -10
  68. package/dist/lib/consolidation.js +16 -6
  69. package/dist/lib/database.js +123 -25
  70. package/dist/lib/db-daemon-client.js +73 -19
  71. package/dist/lib/db.js +123 -25
  72. package/dist/lib/device-registry.js +133 -35
  73. package/dist/lib/embedder.js +107 -32
  74. package/dist/lib/employee-templates.js +14 -4
  75. package/dist/lib/employees.js +41 -13
  76. package/dist/lib/exe-daemon-client.js +88 -22
  77. package/dist/lib/exe-daemon.js +935 -587
  78. package/dist/lib/hybrid-search.js +240 -69
  79. package/dist/lib/identity.js +18 -8
  80. package/dist/lib/license.js +133 -48
  81. package/dist/lib/messaging.js +116 -56
  82. package/dist/lib/reminders.js +14 -4
  83. package/dist/lib/schedules.js +137 -19
  84. package/dist/lib/skill-learning.js +33 -6
  85. package/dist/lib/store.js +137 -19
  86. package/dist/lib/task-router.js +14 -4
  87. package/dist/lib/tasks.js +280 -234
  88. package/dist/lib/tmux-routing.js +172 -125
  89. package/dist/lib/token-spend.js +26 -8
  90. package/dist/mcp/server.js +1326 -609
  91. package/dist/mcp/tools/complete-reminder.js +14 -4
  92. package/dist/mcp/tools/create-reminder.js +14 -4
  93. package/dist/mcp/tools/create-task.js +306 -248
  94. package/dist/mcp/tools/deactivate-behavior.js +16 -6
  95. package/dist/mcp/tools/list-reminders.js +14 -4
  96. package/dist/mcp/tools/list-tasks.js +123 -107
  97. package/dist/mcp/tools/send-message.js +75 -29
  98. package/dist/mcp/tools/update-task.js +1848 -199
  99. package/dist/runtime/index.js +441 -248
  100. package/dist/tui/App.js +761 -424
  101. package/package.json +1 -1
@@ -25,9 +25,47 @@ var __copyProps = (to, from, except, desc) => {
25
25
  };
26
26
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
27
27
 
28
+ // src/lib/secure-files.ts
29
+ import { chmodSync, existsSync, mkdirSync } from "fs";
30
+ import { chmod, mkdir } from "fs/promises";
31
+ async function ensurePrivateDir(dirPath) {
32
+ await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
33
+ try {
34
+ await chmod(dirPath, PRIVATE_DIR_MODE);
35
+ } catch {
36
+ }
37
+ }
38
+ function ensurePrivateDirSync(dirPath) {
39
+ mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
40
+ try {
41
+ chmodSync(dirPath, PRIVATE_DIR_MODE);
42
+ } catch {
43
+ }
44
+ }
45
+ async function enforcePrivateFile(filePath) {
46
+ try {
47
+ await chmod(filePath, PRIVATE_FILE_MODE);
48
+ } catch {
49
+ }
50
+ }
51
+ function enforcePrivateFileSync(filePath) {
52
+ try {
53
+ if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
54
+ } catch {
55
+ }
56
+ }
57
+ var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
58
+ var init_secure_files = __esm({
59
+ "src/lib/secure-files.ts"() {
60
+ "use strict";
61
+ PRIVATE_DIR_MODE = 448;
62
+ PRIVATE_FILE_MODE = 384;
63
+ }
64
+ });
65
+
28
66
  // src/lib/config.ts
29
- import { readFile, writeFile, mkdir, chmod } from "fs/promises";
30
- import { readFileSync, existsSync, renameSync } from "fs";
67
+ import { readFile, writeFile } from "fs/promises";
68
+ import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
31
69
  import path from "path";
32
70
  import os from "os";
33
71
  function resolveDataDir() {
@@ -35,7 +73,7 @@ function resolveDataDir() {
35
73
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
36
74
  const newDir = path.join(os.homedir(), ".exe-os");
37
75
  const legacyDir = path.join(os.homedir(), ".exe-mem");
38
- if (!existsSync(newDir) && existsSync(legacyDir)) {
76
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
39
77
  try {
40
78
  renameSync(legacyDir, newDir);
41
79
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -98,9 +136,9 @@ function normalizeAutoUpdate(raw) {
98
136
  }
99
137
  async function loadConfig() {
100
138
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
101
- await mkdir(dir, { recursive: true });
139
+ await ensurePrivateDir(dir);
102
140
  const configPath = path.join(dir, "config.json");
103
- if (!existsSync(configPath)) {
141
+ if (!existsSync2(configPath)) {
104
142
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
105
143
  }
106
144
  const raw = await readFile(configPath, "utf-8");
@@ -113,6 +151,7 @@ async function loadConfig() {
113
151
  `);
114
152
  try {
115
153
  await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
154
+ await enforcePrivateFile(configPath);
116
155
  } catch {
117
156
  }
118
157
  }
@@ -131,7 +170,7 @@ async function loadConfig() {
131
170
  function loadConfigSync() {
132
171
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
133
172
  const configPath = path.join(dir, "config.json");
134
- if (!existsSync(configPath)) {
173
+ if (!existsSync2(configPath)) {
135
174
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
136
175
  }
137
176
  try {
@@ -151,6 +190,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
151
190
  var init_config = __esm({
152
191
  "src/lib/config.ts"() {
153
192
  "use strict";
193
+ init_secure_files();
154
194
  EXE_AI_DIR = resolveDataDir();
155
195
  DB_PATH = path.join(EXE_AI_DIR, "memories.db");
156
196
  MODELS_DIR = path.join(EXE_AI_DIR, "models");
@@ -297,7 +337,7 @@ var init_session_key = __esm({
297
337
 
298
338
  // src/lib/employees.ts
299
339
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
300
- import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
340
+ import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
301
341
  import { execSync as execSync2 } from "child_process";
302
342
  import path2 from "path";
303
343
  import os2 from "os";
@@ -321,7 +361,7 @@ function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
321
361
  return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
322
362
  }
323
363
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
324
- if (!existsSync2(employeesPath)) return [];
364
+ if (!existsSync3(employeesPath)) return [];
325
365
  try {
326
366
  return JSON.parse(readFileSync2(employeesPath, "utf-8"));
327
367
  } catch {
@@ -517,7 +557,7 @@ var init_runtime_table = __esm({
517
557
  });
518
558
 
519
559
  // src/lib/agent-config.ts
520
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
560
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync4 } from "fs";
521
561
  import path5 from "path";
522
562
  var AGENT_CONFIG_PATH, DEFAULT_MODELS;
523
563
  var init_agent_config = __esm({
@@ -525,6 +565,7 @@ var init_agent_config = __esm({
525
565
  "use strict";
526
566
  init_config();
527
567
  init_runtime_table();
568
+ init_secure_files();
528
569
  AGENT_CONFIG_PATH = path5.join(EXE_AI_DIR, "agent-config.json");
529
570
  DEFAULT_MODELS = {
530
571
  claude: "claude-opus-4",
@@ -535,7 +576,7 @@ var init_agent_config = __esm({
535
576
  });
536
577
 
537
578
  // src/lib/intercom-queue.ts
538
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
579
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
539
580
  import path6 from "path";
540
581
  import os4 from "os";
541
582
  var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
@@ -1187,13 +1228,50 @@ var init_database_adapter = __esm({
1187
1228
  }
1188
1229
  });
1189
1230
 
1231
+ // src/lib/daemon-auth.ts
1232
+ import crypto from "crypto";
1233
+ import path8 from "path";
1234
+ import { existsSync as existsSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
1235
+ function normalizeToken(token) {
1236
+ if (!token) return null;
1237
+ const trimmed = token.trim();
1238
+ return trimmed.length > 0 ? trimmed : null;
1239
+ }
1240
+ function readDaemonToken() {
1241
+ try {
1242
+ if (!existsSync6(DAEMON_TOKEN_PATH)) return null;
1243
+ return normalizeToken(readFileSync6(DAEMON_TOKEN_PATH, "utf8"));
1244
+ } catch {
1245
+ return null;
1246
+ }
1247
+ }
1248
+ function ensureDaemonToken(seed) {
1249
+ const existing = readDaemonToken();
1250
+ if (existing) return existing;
1251
+ const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
1252
+ ensurePrivateDirSync(EXE_AI_DIR);
1253
+ writeFileSync5(DAEMON_TOKEN_PATH, `${token}
1254
+ `, "utf8");
1255
+ enforcePrivateFileSync(DAEMON_TOKEN_PATH);
1256
+ return token;
1257
+ }
1258
+ var DAEMON_TOKEN_PATH;
1259
+ var init_daemon_auth = __esm({
1260
+ "src/lib/daemon-auth.ts"() {
1261
+ "use strict";
1262
+ init_config();
1263
+ init_secure_files();
1264
+ DAEMON_TOKEN_PATH = path8.join(EXE_AI_DIR, "exed.token");
1265
+ }
1266
+ });
1267
+
1190
1268
  // src/lib/exe-daemon-client.ts
1191
1269
  import net from "net";
1192
1270
  import os6 from "os";
1193
1271
  import { spawn } from "child_process";
1194
1272
  import { randomUUID } from "crypto";
1195
- import { existsSync as existsSync5, unlinkSync as unlinkSync3, readFileSync as readFileSync6, openSync, closeSync, statSync } from "fs";
1196
- import path8 from "path";
1273
+ import { existsSync as existsSync7, unlinkSync as unlinkSync3, readFileSync as readFileSync7, openSync, closeSync, statSync } from "fs";
1274
+ import path9 from "path";
1197
1275
  import { fileURLToPath } from "url";
1198
1276
  function handleData(chunk) {
1199
1277
  _buffer += chunk.toString();
@@ -1221,9 +1299,9 @@ function handleData(chunk) {
1221
1299
  }
1222
1300
  }
1223
1301
  function cleanupStaleFiles() {
1224
- if (existsSync5(PID_PATH)) {
1302
+ if (existsSync7(PID_PATH)) {
1225
1303
  try {
1226
- const pid = parseInt(readFileSync6(PID_PATH, "utf8").trim(), 10);
1304
+ const pid = parseInt(readFileSync7(PID_PATH, "utf8").trim(), 10);
1227
1305
  if (pid > 0) {
1228
1306
  try {
1229
1307
  process.kill(pid, 0);
@@ -1244,11 +1322,11 @@ function cleanupStaleFiles() {
1244
1322
  }
1245
1323
  }
1246
1324
  function findPackageRoot() {
1247
- let dir = path8.dirname(fileURLToPath(import.meta.url));
1248
- const { root } = path8.parse(dir);
1325
+ let dir = path9.dirname(fileURLToPath(import.meta.url));
1326
+ const { root } = path9.parse(dir);
1249
1327
  while (dir !== root) {
1250
- if (existsSync5(path8.join(dir, "package.json"))) return dir;
1251
- dir = path8.dirname(dir);
1328
+ if (existsSync7(path9.join(dir, "package.json"))) return dir;
1329
+ dir = path9.dirname(dir);
1252
1330
  }
1253
1331
  return null;
1254
1332
  }
@@ -1274,16 +1352,17 @@ function spawnDaemon() {
1274
1352
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
1275
1353
  return;
1276
1354
  }
1277
- const daemonPath = path8.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1278
- if (!existsSync5(daemonPath)) {
1355
+ const daemonPath = path9.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1356
+ if (!existsSync7(daemonPath)) {
1279
1357
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
1280
1358
  `);
1281
1359
  return;
1282
1360
  }
1283
1361
  const resolvedPath = daemonPath;
1362
+ const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
1284
1363
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
1285
1364
  `);
1286
- const logPath = path8.join(path8.dirname(SOCKET_PATH), "exed.log");
1365
+ const logPath = path9.join(path9.dirname(SOCKET_PATH), "exed.log");
1287
1366
  let stderrFd = "ignore";
1288
1367
  try {
1289
1368
  stderrFd = openSync(logPath, "a");
@@ -1301,7 +1380,8 @@ function spawnDaemon() {
1301
1380
  TMUX_PANE: void 0,
1302
1381
  // Prevents resolveExeSession() from scoping to one session
1303
1382
  EXE_DAEMON_SOCK: SOCKET_PATH,
1304
- EXE_DAEMON_PID: PID_PATH
1383
+ EXE_DAEMON_PID: PID_PATH,
1384
+ [DAEMON_TOKEN_ENV]: daemonToken
1305
1385
  }
1306
1386
  });
1307
1387
  child.unref();
@@ -1408,13 +1488,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
1408
1488
  return;
1409
1489
  }
1410
1490
  const id = randomUUID();
1491
+ const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
1411
1492
  const timer = setTimeout(() => {
1412
1493
  _pending.delete(id);
1413
1494
  resolve({ error: "Request timeout" });
1414
1495
  }, timeoutMs);
1415
1496
  _pending.set(id, { resolve, timer });
1416
1497
  try {
1417
- _socket.write(JSON.stringify({ id, ...payload }) + "\n");
1498
+ _socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
1418
1499
  } catch {
1419
1500
  clearTimeout(timer);
1420
1501
  _pending.delete(id);
@@ -1425,17 +1506,19 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
1425
1506
  function isClientConnected() {
1426
1507
  return _connected;
1427
1508
  }
1428
- var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
1509
+ 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;
1429
1510
  var init_exe_daemon_client = __esm({
1430
1511
  "src/lib/exe-daemon-client.ts"() {
1431
1512
  "use strict";
1432
1513
  init_config();
1433
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path8.join(EXE_AI_DIR, "exed.sock");
1434
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path8.join(EXE_AI_DIR, "exed.pid");
1435
- SPAWN_LOCK_PATH = path8.join(EXE_AI_DIR, "exed-spawn.lock");
1514
+ init_daemon_auth();
1515
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path9.join(EXE_AI_DIR, "exed.sock");
1516
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path9.join(EXE_AI_DIR, "exed.pid");
1517
+ SPAWN_LOCK_PATH = path9.join(EXE_AI_DIR, "exed-spawn.lock");
1436
1518
  SPAWN_LOCK_STALE_MS = 3e4;
1437
1519
  CONNECT_TIMEOUT_MS = 15e3;
1438
1520
  REQUEST_TIMEOUT_MS = 3e4;
1521
+ DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
1439
1522
  _socket = null;
1440
1523
  _connected = false;
1441
1524
  _buffer = "";
@@ -2014,6 +2097,7 @@ async function ensureSchema() {
2014
2097
  project TEXT NOT NULL,
2015
2098
  summary TEXT NOT NULL,
2016
2099
  task_file TEXT,
2100
+ session_scope TEXT,
2017
2101
  read INTEGER NOT NULL DEFAULT 0,
2018
2102
  created_at TEXT NOT NULL
2019
2103
  );
@@ -2022,7 +2106,7 @@ async function ensureSchema() {
2022
2106
  ON notifications(read);
2023
2107
 
2024
2108
  CREATE INDEX IF NOT EXISTS idx_notifications_agent
2025
- ON notifications(agent_id);
2109
+ ON notifications(agent_id, session_scope);
2026
2110
 
2027
2111
  CREATE INDEX IF NOT EXISTS idx_notifications_task_file
2028
2112
  ON notifications(task_file);
@@ -2060,6 +2144,7 @@ async function ensureSchema() {
2060
2144
  target_agent TEXT NOT NULL,
2061
2145
  target_project TEXT,
2062
2146
  target_device TEXT NOT NULL DEFAULT 'local',
2147
+ session_scope TEXT,
2063
2148
  content TEXT NOT NULL,
2064
2149
  priority TEXT DEFAULT 'normal',
2065
2150
  status TEXT DEFAULT 'pending',
@@ -2073,10 +2158,31 @@ async function ensureSchema() {
2073
2158
  );
2074
2159
 
2075
2160
  CREATE INDEX IF NOT EXISTS idx_messages_target
2076
- ON messages(target_agent, status);
2161
+ ON messages(target_agent, session_scope, status);
2077
2162
 
2078
2163
  CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
2079
- ON messages(target_agent, from_agent, server_seq);
2164
+ ON messages(target_agent, session_scope, from_agent, server_seq);
2165
+ `);
2166
+ try {
2167
+ await client.execute({
2168
+ sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
2169
+ args: []
2170
+ });
2171
+ } catch {
2172
+ }
2173
+ try {
2174
+ await client.execute({
2175
+ sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
2176
+ args: []
2177
+ });
2178
+ } catch {
2179
+ }
2180
+ await client.executeMultiple(`
2181
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
2182
+ ON notifications(agent_id, session_scope, read, created_at);
2183
+
2184
+ CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
2185
+ ON messages(target_agent, session_scope, status, created_at);
2080
2186
  `);
2081
2187
  try {
2082
2188
  await client.execute({
@@ -2660,6 +2766,13 @@ async function ensureSchema() {
2660
2766
  } catch {
2661
2767
  }
2662
2768
  }
2769
+ try {
2770
+ await client.execute({
2771
+ sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
2772
+ args: []
2773
+ });
2774
+ } catch {
2775
+ }
2663
2776
  }
2664
2777
  async function disposeDatabase() {
2665
2778
  if (_walCheckpointTimer) {
@@ -2698,24 +2811,27 @@ var init_database = __esm({
2698
2811
  });
2699
2812
 
2700
2813
  // src/lib/license.ts
2701
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
2814
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync8, mkdirSync as mkdirSync4 } from "fs";
2702
2815
  import { randomUUID as randomUUID2 } from "crypto";
2703
- import path9 from "path";
2816
+ import { createRequire as createRequire2 } from "module";
2817
+ import { pathToFileURL as pathToFileURL2 } from "url";
2818
+ import os7 from "os";
2819
+ import path10 from "path";
2704
2820
  import { jwtVerify, importSPKI } from "jose";
2705
2821
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH;
2706
2822
  var init_license = __esm({
2707
2823
  "src/lib/license.ts"() {
2708
2824
  "use strict";
2709
2825
  init_config();
2710
- LICENSE_PATH = path9.join(EXE_AI_DIR, "license.key");
2711
- CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
2712
- DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
2826
+ LICENSE_PATH = path10.join(EXE_AI_DIR, "license.key");
2827
+ CACHE_PATH = path10.join(EXE_AI_DIR, "license-cache.json");
2828
+ DEVICE_ID_PATH = path10.join(EXE_AI_DIR, "device-id");
2713
2829
  }
2714
2830
  });
2715
2831
 
2716
2832
  // src/lib/plan-limits.ts
2717
- import { readFileSync as readFileSync8, existsSync as existsSync7 } from "fs";
2718
- import path10 from "path";
2833
+ import { readFileSync as readFileSync9, existsSync as existsSync9 } from "fs";
2834
+ import path11 from "path";
2719
2835
  var CACHE_PATH2;
2720
2836
  var init_plan_limits = __esm({
2721
2837
  "src/lib/plan-limits.ts"() {
@@ -2724,14 +2840,14 @@ var init_plan_limits = __esm({
2724
2840
  init_employees();
2725
2841
  init_license();
2726
2842
  init_config();
2727
- CACHE_PATH2 = path10.join(EXE_AI_DIR, "license-cache.json");
2843
+ CACHE_PATH2 = path11.join(EXE_AI_DIR, "license-cache.json");
2728
2844
  }
2729
2845
  });
2730
2846
 
2731
2847
  // src/lib/tmux-routing.ts
2732
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, existsSync as existsSync8, appendFileSync, readdirSync as readdirSync2 } from "fs";
2733
- import path11 from "path";
2734
- import os7 from "os";
2848
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, mkdirSync as mkdirSync5, existsSync as existsSync10, appendFileSync, readdirSync as readdirSync2 } from "fs";
2849
+ import path12 from "path";
2850
+ import os8 from "os";
2735
2851
  import { fileURLToPath as fileURLToPath2 } from "url";
2736
2852
  function getMySession() {
2737
2853
  return getTransport().getMySession();
@@ -2744,7 +2860,7 @@ function extractRootExe(name) {
2744
2860
  }
2745
2861
  function getParentExe(sessionKey) {
2746
2862
  try {
2747
- const data = JSON.parse(readFileSync9(path11.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
2863
+ const data = JSON.parse(readFileSync10(path12.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
2748
2864
  return data.parentExe || null;
2749
2865
  } catch {
2750
2866
  return null;
@@ -2787,10 +2903,10 @@ var init_tmux_routing = __esm({
2787
2903
  init_intercom_queue();
2788
2904
  init_plan_limits();
2789
2905
  init_employees();
2790
- SPAWN_LOCK_DIR = path11.join(os7.homedir(), ".exe-os", "spawn-locks");
2791
- SESSION_CACHE = path11.join(os7.homedir(), ".exe-os", "session-cache");
2792
- INTERCOM_LOG2 = path11.join(os7.homedir(), ".exe-os", "intercom.log");
2793
- DEBOUNCE_FILE = path11.join(SESSION_CACHE, "intercom-debounce.json");
2906
+ SPAWN_LOCK_DIR = path12.join(os8.homedir(), ".exe-os", "spawn-locks");
2907
+ SESSION_CACHE = path12.join(os8.homedir(), ".exe-os", "session-cache");
2908
+ INTERCOM_LOG2 = path12.join(os8.homedir(), ".exe-os", "intercom.log");
2909
+ DEBOUNCE_FILE = path12.join(SESSION_CACHE, "intercom-debounce.json");
2794
2910
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
2795
2911
  }
2796
2912
  });
@@ -2830,14 +2946,14 @@ var init_memory = __esm({
2830
2946
 
2831
2947
  // src/lib/keychain.ts
2832
2948
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
2833
- import { existsSync as existsSync9 } from "fs";
2834
- import path12 from "path";
2835
- import os8 from "os";
2949
+ import { existsSync as existsSync11 } from "fs";
2950
+ import path13 from "path";
2951
+ import os9 from "os";
2836
2952
  function getKeyDir() {
2837
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path12.join(os8.homedir(), ".exe-os");
2953
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path13.join(os9.homedir(), ".exe-os");
2838
2954
  }
2839
2955
  function getKeyPath() {
2840
- return path12.join(getKeyDir(), "master.key");
2956
+ return path13.join(getKeyDir(), "master.key");
2841
2957
  }
2842
2958
  async function tryKeytar() {
2843
2959
  try {
@@ -2858,9 +2974,9 @@ async function getMasterKey() {
2858
2974
  }
2859
2975
  }
2860
2976
  const keyPath = getKeyPath();
2861
- if (!existsSync9(keyPath)) {
2977
+ if (!existsSync11(keyPath)) {
2862
2978
  process.stderr.write(
2863
- `[keychain] Key not found at ${keyPath} (HOME=${os8.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
2979
+ `[keychain] Key not found at ${keyPath} (HOME=${os9.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
2864
2980
  `
2865
2981
  );
2866
2982
  return null;
@@ -2945,6 +3061,7 @@ var shard_manager_exports = {};
2945
3061
  __export(shard_manager_exports, {
2946
3062
  disposeShards: () => disposeShards,
2947
3063
  ensureShardSchema: () => ensureShardSchema,
3064
+ getOpenShardCount: () => getOpenShardCount,
2948
3065
  getReadyShardClient: () => getReadyShardClient,
2949
3066
  getShardClient: () => getShardClient,
2950
3067
  getShardsDir: () => getShardsDir,
@@ -2953,15 +3070,18 @@ __export(shard_manager_exports, {
2953
3070
  listShards: () => listShards,
2954
3071
  shardExists: () => shardExists
2955
3072
  });
2956
- import path13 from "path";
2957
- import { existsSync as existsSync10, mkdirSync as mkdirSync6, readdirSync as readdirSync3 } from "fs";
3073
+ import path14 from "path";
3074
+ import { existsSync as existsSync12, mkdirSync as mkdirSync6, readdirSync as readdirSync3 } from "fs";
2958
3075
  import { createClient as createClient2 } from "@libsql/client";
2959
3076
  function initShardManager(encryptionKey) {
2960
3077
  _encryptionKey = encryptionKey;
2961
- if (!existsSync10(SHARDS_DIR)) {
3078
+ if (!existsSync12(SHARDS_DIR)) {
2962
3079
  mkdirSync6(SHARDS_DIR, { recursive: true });
2963
3080
  }
2964
3081
  _shardingEnabled = true;
3082
+ if (_evictionTimer) clearInterval(_evictionTimer);
3083
+ _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
3084
+ _evictionTimer.unref();
2965
3085
  }
2966
3086
  function isShardingEnabled() {
2967
3087
  return _shardingEnabled;
@@ -2978,21 +3098,28 @@ function getShardClient(projectName) {
2978
3098
  throw new Error(`Invalid project name for shard: "${projectName}"`);
2979
3099
  }
2980
3100
  const cached = _shards.get(safeName);
2981
- if (cached) return cached;
2982
- const dbPath = path13.join(SHARDS_DIR, `${safeName}.db`);
3101
+ if (cached) {
3102
+ _shardLastAccess.set(safeName, Date.now());
3103
+ return cached;
3104
+ }
3105
+ while (_shards.size >= MAX_OPEN_SHARDS) {
3106
+ evictLRU();
3107
+ }
3108
+ const dbPath = path14.join(SHARDS_DIR, `${safeName}.db`);
2983
3109
  const client = createClient2({
2984
3110
  url: `file:${dbPath}`,
2985
3111
  encryptionKey: _encryptionKey
2986
3112
  });
2987
3113
  _shards.set(safeName, client);
3114
+ _shardLastAccess.set(safeName, Date.now());
2988
3115
  return client;
2989
3116
  }
2990
3117
  function shardExists(projectName) {
2991
3118
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
2992
- return existsSync10(path13.join(SHARDS_DIR, `${safeName}.db`));
3119
+ return existsSync12(path14.join(SHARDS_DIR, `${safeName}.db`));
2993
3120
  }
2994
3121
  function listShards() {
2995
- if (!existsSync10(SHARDS_DIR)) return [];
3122
+ if (!existsSync12(SHARDS_DIR)) return [];
2996
3123
  return readdirSync3(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
2997
3124
  }
2998
3125
  async function ensureShardSchema(client) {
@@ -3044,6 +3171,8 @@ async function ensureShardSchema(client) {
3044
3171
  for (const col of [
3045
3172
  "ALTER TABLE memories ADD COLUMN task_id TEXT",
3046
3173
  "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
3174
+ "ALTER TABLE memories ADD COLUMN author_device_id TEXT",
3175
+ "ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
3047
3176
  "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
3048
3177
  "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
3049
3178
  "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
@@ -3181,21 +3310,69 @@ async function getReadyShardClient(projectName) {
3181
3310
  await ensureShardSchema(client);
3182
3311
  return client;
3183
3312
  }
3313
+ function evictLRU() {
3314
+ let oldest = null;
3315
+ let oldestTime = Infinity;
3316
+ for (const [name, time] of _shardLastAccess) {
3317
+ if (time < oldestTime) {
3318
+ oldestTime = time;
3319
+ oldest = name;
3320
+ }
3321
+ }
3322
+ if (oldest) {
3323
+ const client = _shards.get(oldest);
3324
+ if (client) {
3325
+ client.close();
3326
+ }
3327
+ _shards.delete(oldest);
3328
+ _shardLastAccess.delete(oldest);
3329
+ }
3330
+ }
3331
+ function evictIdleShards() {
3332
+ const now = Date.now();
3333
+ const toEvict = [];
3334
+ for (const [name, lastAccess] of _shardLastAccess) {
3335
+ if (now - lastAccess > SHARD_IDLE_MS) {
3336
+ toEvict.push(name);
3337
+ }
3338
+ }
3339
+ for (const name of toEvict) {
3340
+ const client = _shards.get(name);
3341
+ if (client) {
3342
+ client.close();
3343
+ }
3344
+ _shards.delete(name);
3345
+ _shardLastAccess.delete(name);
3346
+ }
3347
+ }
3348
+ function getOpenShardCount() {
3349
+ return _shards.size;
3350
+ }
3184
3351
  function disposeShards() {
3352
+ if (_evictionTimer) {
3353
+ clearInterval(_evictionTimer);
3354
+ _evictionTimer = null;
3355
+ }
3185
3356
  for (const [, client] of _shards) {
3186
3357
  client.close();
3187
3358
  }
3188
3359
  _shards.clear();
3360
+ _shardLastAccess.clear();
3189
3361
  _shardingEnabled = false;
3190
3362
  _encryptionKey = null;
3191
3363
  }
3192
- var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
3364
+ var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
3193
3365
  var init_shard_manager = __esm({
3194
3366
  "src/lib/shard-manager.ts"() {
3195
3367
  "use strict";
3196
3368
  init_config();
3197
- SHARDS_DIR = path13.join(EXE_AI_DIR, "shards");
3369
+ SHARDS_DIR = path14.join(EXE_AI_DIR, "shards");
3370
+ SHARD_IDLE_MS = 5 * 60 * 1e3;
3371
+ MAX_OPEN_SHARDS = 10;
3372
+ EVICTION_INTERVAL_MS = 60 * 1e3;
3198
3373
  _shards = /* @__PURE__ */ new Map();
3374
+ _shardLastAccess = /* @__PURE__ */ new Map();
3375
+ _evictionTimer = null;
3199
3376
  _encryptionKey = null;
3200
3377
  _shardingEnabled = false;
3201
3378
  }
@@ -3962,15 +4139,15 @@ var init_store = __esm({
3962
4139
  init_config();
3963
4140
  init_config();
3964
4141
  import { spawn as spawn2 } from "child_process";
3965
- import { existsSync as existsSync11, openSync as openSync2, closeSync as closeSync2 } from "fs";
3966
- import path14 from "path";
4142
+ import { existsSync as existsSync13, openSync as openSync2, closeSync as closeSync2 } from "fs";
4143
+ import path15 from "path";
3967
4144
  import { fileURLToPath as fileURLToPath3 } from "url";
3968
4145
 
3969
4146
  // src/lib/active-agent.ts
3970
4147
  init_config();
3971
4148
  init_session_key();
3972
4149
  init_employees();
3973
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync, unlinkSync as unlinkSync2, readdirSync } from "fs";
4150
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2, readdirSync } from "fs";
3974
4151
  import { execSync as execSync3 } from "child_process";
3975
4152
  import path3 from "path";
3976
4153
  var CACHE_DIR = path3.join(EXE_AI_DIR, "session-cache");
@@ -4073,7 +4250,7 @@ if (!process.env.AGENT_ID) {
4073
4250
  if (!loadConfigSync().autoIngestion) {
4074
4251
  process.exit(0);
4075
4252
  }
4076
- var WORKER_LOG_PATH = path14.join(EXE_AI_DIR, "workers.log");
4253
+ var WORKER_LOG_PATH = path15.join(EXE_AI_DIR, "workers.log");
4077
4254
  function openWorkerLog() {
4078
4255
  try {
4079
4256
  return openSync2(WORKER_LOG_PATH, "a");
@@ -4081,6 +4258,25 @@ function openWorkerLog() {
4081
4258
  return "ignore";
4082
4259
  }
4083
4260
  }
4261
+ function spawnDetachedWorker(workerPath, env, cwd) {
4262
+ if (!existsSync13(workerPath)) {
4263
+ process.stderr.write(`[stop] WARN: worker not found at ${workerPath}
4264
+ `);
4265
+ return;
4266
+ }
4267
+ const stderrFd = openWorkerLog();
4268
+ const worker = spawn2(process.execPath, [workerPath], {
4269
+ cwd,
4270
+ detached: true,
4271
+ stdio: ["ignore", "ignore", stderrFd],
4272
+ env
4273
+ });
4274
+ worker.unref();
4275
+ if (typeof stderrFd === "number") try {
4276
+ closeSync2(stderrFd);
4277
+ } catch {
4278
+ }
4279
+ }
4084
4280
  var MIN_LENGTH = 100;
4085
4281
  var timeout = setTimeout(() => {
4086
4282
  process.exit(0);
@@ -4096,22 +4292,36 @@ process.stdin.on("end", () => {
4096
4292
  try {
4097
4293
  if (process.env.EXE_DEBUG_HOOKS || process.env.EXE_RUNTIME === "codex") {
4098
4294
  try {
4099
- const debugPath = path14.join(EXE_AI_DIR, "logs", "hook-stdin-stop.log");
4100
- const { mkdirSync: mkdirSync7, writeFileSync: writeFileSync7 } = __require("fs");
4101
- mkdirSync7(path14.dirname(debugPath), { recursive: true });
4295
+ const debugPath = path15.join(EXE_AI_DIR, "logs", "hook-stdin-stop.log");
4296
+ const { mkdirSync: mkdirSync7, writeFileSync: writeFileSync8 } = __require("fs");
4297
+ mkdirSync7(path15.dirname(debugPath), { recursive: true });
4102
4298
  const ts = (/* @__PURE__ */ new Date()).toISOString();
4103
4299
  const snippet = input.length > 500 ? input.slice(0, 500) + "...[truncated]" : input;
4104
- writeFileSync7(debugPath, `[${ts}] len=${input.length} ${snippet}
4300
+ writeFileSync8(debugPath, `[${ts}] len=${input.length} ${snippet}
4105
4301
  `, { flag: "a" });
4106
4302
  } catch {
4107
4303
  }
4108
4304
  }
4109
4305
  const data = JSON.parse(input);
4110
- const message = data.last_assistant_message;
4306
+ const agent = getActiveAgent();
4307
+ const message = data.last_assistant_message ?? "";
4308
+ const cwd = data.cwd ?? process.cwd();
4309
+ if (process.env.EXE_RUNTIME === "codex" && !canCoordinate(agent.agentId, agent.agentRole)) {
4310
+ const codexFinalizerPath = path15.resolve(
4311
+ path15.dirname(fileURLToPath3(import.meta.url)),
4312
+ "codex-stop-task-finalizer.js"
4313
+ );
4314
+ spawnDetachedWorker(codexFinalizerPath, {
4315
+ ...process.env,
4316
+ AGENT_ID: agent.agentId,
4317
+ AGENT_ROLE: agent.agentRole,
4318
+ EXE_RESPONSE_TEXT: message.slice(0, 5e3),
4319
+ EXE_SESSION_ID: data.session_id
4320
+ }, cwd);
4321
+ }
4111
4322
  if (!message || message.length < MIN_LENGTH) {
4112
4323
  process.exit(0);
4113
4324
  }
4114
- const agent = getActiveAgent();
4115
4325
  const CAPACITY_SIGNALS = /context[- ]?full|hit capacity|conversation is too long|maximum context length|context window.*(?:limit|exceed|full)/i;
4116
4326
  if (!canCoordinate(agent.agentId, agent.agentRole) && CAPACITY_SIGNALS.test(message)) {
4117
4327
  Promise.resolve().then(() => (init_store(), store_exports)).then(({ initStore: initStore2 }) => initStore2()).then(() => Promise.all([
@@ -4136,9 +4346,9 @@ process.stdin.on("end", () => {
4136
4346
  "",
4137
4347
  `Last response fragment: ${message.slice(0, 500)}`
4138
4348
  ].join("\n");
4139
- const crypto = await import("crypto");
4349
+ const crypto2 = await import("crypto");
4140
4350
  await writeMemory2({
4141
- id: crypto.randomUUID(),
4351
+ id: crypto2.randomUUID(),
4142
4352
  agent_id: agent.agentId,
4143
4353
  agent_role: agent.agentRole,
4144
4354
  session_id: data.session_id,
@@ -4177,32 +4387,17 @@ process.stdin.on("end", () => {
4177
4387
  }).catch(() => {
4178
4388
  });
4179
4389
  }
4180
- const workerPath = path14.resolve(
4181
- path14.dirname(fileURLToPath3(import.meta.url)),
4390
+ const workerPath = path15.resolve(
4391
+ path15.dirname(fileURLToPath3(import.meta.url)),
4182
4392
  "response-ingest-worker.js"
4183
4393
  );
4184
- if (!existsSync11(workerPath)) {
4185
- process.stderr.write(`[stop] WARN: response-ingest-worker not found at ${workerPath}
4186
- `);
4187
- process.exit(0);
4188
- }
4189
- const stderrFd = openWorkerLog();
4190
- const worker = spawn2(process.execPath, [workerPath], {
4191
- detached: true,
4192
- stdio: ["ignore", "ignore", stderrFd],
4193
- env: {
4194
- ...process.env,
4195
- AGENT_ID: agent.agentId,
4196
- AGENT_ROLE: agent.agentRole,
4197
- EXE_RESPONSE_TEXT: message.slice(0, 5e3),
4198
- EXE_SESSION_ID: data.session_id
4199
- }
4200
- });
4201
- worker.unref();
4202
- if (typeof stderrFd === "number") try {
4203
- closeSync2(stderrFd);
4204
- } catch {
4205
- }
4394
+ spawnDetachedWorker(workerPath, {
4395
+ ...process.env,
4396
+ AGENT_ID: agent.agentId,
4397
+ AGENT_ROLE: agent.agentRole,
4398
+ EXE_RESPONSE_TEXT: message.slice(0, 5e3),
4399
+ EXE_SESSION_ID: data.session_id
4400
+ }, cwd);
4206
4401
  } catch {
4207
4402
  }
4208
4403
  process.exit(0);