@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
@@ -19,9 +19,47 @@ var __copyProps = (to, from, except, desc) => {
19
19
  };
20
20
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
21
21
 
22
+ // src/lib/secure-files.ts
23
+ import { chmodSync, existsSync, mkdirSync } from "fs";
24
+ import { chmod, mkdir } from "fs/promises";
25
+ async function ensurePrivateDir(dirPath) {
26
+ await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
27
+ try {
28
+ await chmod(dirPath, PRIVATE_DIR_MODE);
29
+ } catch {
30
+ }
31
+ }
32
+ function ensurePrivateDirSync(dirPath) {
33
+ mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
34
+ try {
35
+ chmodSync(dirPath, PRIVATE_DIR_MODE);
36
+ } catch {
37
+ }
38
+ }
39
+ async function enforcePrivateFile(filePath) {
40
+ try {
41
+ await chmod(filePath, PRIVATE_FILE_MODE);
42
+ } catch {
43
+ }
44
+ }
45
+ function enforcePrivateFileSync(filePath) {
46
+ try {
47
+ if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
48
+ } catch {
49
+ }
50
+ }
51
+ var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
52
+ var init_secure_files = __esm({
53
+ "src/lib/secure-files.ts"() {
54
+ "use strict";
55
+ PRIVATE_DIR_MODE = 448;
56
+ PRIVATE_FILE_MODE = 384;
57
+ }
58
+ });
59
+
22
60
  // src/lib/config.ts
23
- import { readFile, writeFile, mkdir, chmod } from "fs/promises";
24
- import { readFileSync, existsSync, renameSync } from "fs";
61
+ import { readFile, writeFile } from "fs/promises";
62
+ import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
25
63
  import path from "path";
26
64
  import os from "os";
27
65
  function resolveDataDir() {
@@ -29,7 +67,7 @@ function resolveDataDir() {
29
67
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
30
68
  const newDir = path.join(os.homedir(), ".exe-os");
31
69
  const legacyDir = path.join(os.homedir(), ".exe-mem");
32
- if (!existsSync(newDir) && existsSync(legacyDir)) {
70
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
33
71
  try {
34
72
  renameSync(legacyDir, newDir);
35
73
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -92,9 +130,9 @@ function normalizeAutoUpdate(raw) {
92
130
  }
93
131
  async function loadConfig() {
94
132
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
95
- await mkdir(dir, { recursive: true });
133
+ await ensurePrivateDir(dir);
96
134
  const configPath = path.join(dir, "config.json");
97
- if (!existsSync(configPath)) {
135
+ if (!existsSync2(configPath)) {
98
136
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
99
137
  }
100
138
  const raw = await readFile(configPath, "utf-8");
@@ -107,6 +145,7 @@ async function loadConfig() {
107
145
  `);
108
146
  try {
109
147
  await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
148
+ await enforcePrivateFile(configPath);
110
149
  } catch {
111
150
  }
112
151
  }
@@ -126,6 +165,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
126
165
  var init_config = __esm({
127
166
  "src/lib/config.ts"() {
128
167
  "use strict";
168
+ init_secure_files();
129
169
  EXE_AI_DIR = resolveDataDir();
130
170
  DB_PATH = path.join(EXE_AI_DIR, "memories.db");
131
171
  MODELS_DIR = path.join(EXE_AI_DIR, "models");
@@ -272,7 +312,7 @@ var init_session_key = __esm({
272
312
 
273
313
  // src/lib/employees.ts
274
314
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
275
- import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
315
+ import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
276
316
  import { execSync as execSync2 } from "child_process";
277
317
  import path2 from "path";
278
318
  import os2 from "os";
@@ -296,7 +336,7 @@ function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
296
336
  return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
297
337
  }
298
338
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
299
- if (!existsSync2(employeesPath)) return [];
339
+ if (!existsSync3(employeesPath)) return [];
300
340
  try {
301
341
  return JSON.parse(readFileSync2(employeesPath, "utf-8"));
302
342
  } catch {
@@ -492,7 +532,7 @@ var init_runtime_table = __esm({
492
532
  });
493
533
 
494
534
  // src/lib/agent-config.ts
495
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
535
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync4 } from "fs";
496
536
  import path5 from "path";
497
537
  var AGENT_CONFIG_PATH, DEFAULT_MODELS;
498
538
  var init_agent_config = __esm({
@@ -500,6 +540,7 @@ var init_agent_config = __esm({
500
540
  "use strict";
501
541
  init_config();
502
542
  init_runtime_table();
543
+ init_secure_files();
503
544
  AGENT_CONFIG_PATH = path5.join(EXE_AI_DIR, "agent-config.json");
504
545
  DEFAULT_MODELS = {
505
546
  claude: "claude-opus-4",
@@ -510,7 +551,7 @@ var init_agent_config = __esm({
510
551
  });
511
552
 
512
553
  // src/lib/intercom-queue.ts
513
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
554
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
514
555
  import path6 from "path";
515
556
  import os4 from "os";
516
557
  var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
@@ -1162,13 +1203,50 @@ var init_database_adapter = __esm({
1162
1203
  }
1163
1204
  });
1164
1205
 
1206
+ // src/lib/daemon-auth.ts
1207
+ import crypto from "crypto";
1208
+ import path8 from "path";
1209
+ import { existsSync as existsSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
1210
+ function normalizeToken(token) {
1211
+ if (!token) return null;
1212
+ const trimmed = token.trim();
1213
+ return trimmed.length > 0 ? trimmed : null;
1214
+ }
1215
+ function readDaemonToken() {
1216
+ try {
1217
+ if (!existsSync6(DAEMON_TOKEN_PATH)) return null;
1218
+ return normalizeToken(readFileSync6(DAEMON_TOKEN_PATH, "utf8"));
1219
+ } catch {
1220
+ return null;
1221
+ }
1222
+ }
1223
+ function ensureDaemonToken(seed) {
1224
+ const existing = readDaemonToken();
1225
+ if (existing) return existing;
1226
+ const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
1227
+ ensurePrivateDirSync(EXE_AI_DIR);
1228
+ writeFileSync5(DAEMON_TOKEN_PATH, `${token}
1229
+ `, "utf8");
1230
+ enforcePrivateFileSync(DAEMON_TOKEN_PATH);
1231
+ return token;
1232
+ }
1233
+ var DAEMON_TOKEN_PATH;
1234
+ var init_daemon_auth = __esm({
1235
+ "src/lib/daemon-auth.ts"() {
1236
+ "use strict";
1237
+ init_config();
1238
+ init_secure_files();
1239
+ DAEMON_TOKEN_PATH = path8.join(EXE_AI_DIR, "exed.token");
1240
+ }
1241
+ });
1242
+
1165
1243
  // src/lib/exe-daemon-client.ts
1166
1244
  import net from "net";
1167
1245
  import os6 from "os";
1168
1246
  import { spawn } from "child_process";
1169
1247
  import { randomUUID } from "crypto";
1170
- import { existsSync as existsSync5, unlinkSync as unlinkSync3, readFileSync as readFileSync6, openSync, closeSync, statSync } from "fs";
1171
- import path8 from "path";
1248
+ import { existsSync as existsSync7, unlinkSync as unlinkSync3, readFileSync as readFileSync7, openSync, closeSync, statSync } from "fs";
1249
+ import path9 from "path";
1172
1250
  import { fileURLToPath } from "url";
1173
1251
  function handleData(chunk) {
1174
1252
  _buffer += chunk.toString();
@@ -1196,9 +1274,9 @@ function handleData(chunk) {
1196
1274
  }
1197
1275
  }
1198
1276
  function cleanupStaleFiles() {
1199
- if (existsSync5(PID_PATH)) {
1277
+ if (existsSync7(PID_PATH)) {
1200
1278
  try {
1201
- const pid = parseInt(readFileSync6(PID_PATH, "utf8").trim(), 10);
1279
+ const pid = parseInt(readFileSync7(PID_PATH, "utf8").trim(), 10);
1202
1280
  if (pid > 0) {
1203
1281
  try {
1204
1282
  process.kill(pid, 0);
@@ -1219,11 +1297,11 @@ function cleanupStaleFiles() {
1219
1297
  }
1220
1298
  }
1221
1299
  function findPackageRoot() {
1222
- let dir = path8.dirname(fileURLToPath(import.meta.url));
1223
- const { root } = path8.parse(dir);
1300
+ let dir = path9.dirname(fileURLToPath(import.meta.url));
1301
+ const { root } = path9.parse(dir);
1224
1302
  while (dir !== root) {
1225
- if (existsSync5(path8.join(dir, "package.json"))) return dir;
1226
- dir = path8.dirname(dir);
1303
+ if (existsSync7(path9.join(dir, "package.json"))) return dir;
1304
+ dir = path9.dirname(dir);
1227
1305
  }
1228
1306
  return null;
1229
1307
  }
@@ -1249,16 +1327,17 @@ function spawnDaemon() {
1249
1327
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
1250
1328
  return;
1251
1329
  }
1252
- const daemonPath = path8.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1253
- if (!existsSync5(daemonPath)) {
1330
+ const daemonPath = path9.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1331
+ if (!existsSync7(daemonPath)) {
1254
1332
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
1255
1333
  `);
1256
1334
  return;
1257
1335
  }
1258
1336
  const resolvedPath = daemonPath;
1337
+ const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
1259
1338
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
1260
1339
  `);
1261
- const logPath = path8.join(path8.dirname(SOCKET_PATH), "exed.log");
1340
+ const logPath = path9.join(path9.dirname(SOCKET_PATH), "exed.log");
1262
1341
  let stderrFd = "ignore";
1263
1342
  try {
1264
1343
  stderrFd = openSync(logPath, "a");
@@ -1276,7 +1355,8 @@ function spawnDaemon() {
1276
1355
  TMUX_PANE: void 0,
1277
1356
  // Prevents resolveExeSession() from scoping to one session
1278
1357
  EXE_DAEMON_SOCK: SOCKET_PATH,
1279
- EXE_DAEMON_PID: PID_PATH
1358
+ EXE_DAEMON_PID: PID_PATH,
1359
+ [DAEMON_TOKEN_ENV]: daemonToken
1280
1360
  }
1281
1361
  });
1282
1362
  child.unref();
@@ -1383,13 +1463,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
1383
1463
  return;
1384
1464
  }
1385
1465
  const id = randomUUID();
1466
+ const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
1386
1467
  const timer = setTimeout(() => {
1387
1468
  _pending.delete(id);
1388
1469
  resolve({ error: "Request timeout" });
1389
1470
  }, timeoutMs);
1390
1471
  _pending.set(id, { resolve, timer });
1391
1472
  try {
1392
- _socket.write(JSON.stringify({ id, ...payload }) + "\n");
1473
+ _socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
1393
1474
  } catch {
1394
1475
  clearTimeout(timer);
1395
1476
  _pending.delete(id);
@@ -1400,17 +1481,19 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
1400
1481
  function isClientConnected() {
1401
1482
  return _connected;
1402
1483
  }
1403
- var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
1484
+ 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;
1404
1485
  var init_exe_daemon_client = __esm({
1405
1486
  "src/lib/exe-daemon-client.ts"() {
1406
1487
  "use strict";
1407
1488
  init_config();
1408
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path8.join(EXE_AI_DIR, "exed.sock");
1409
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path8.join(EXE_AI_DIR, "exed.pid");
1410
- SPAWN_LOCK_PATH = path8.join(EXE_AI_DIR, "exed-spawn.lock");
1489
+ init_daemon_auth();
1490
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path9.join(EXE_AI_DIR, "exed.sock");
1491
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path9.join(EXE_AI_DIR, "exed.pid");
1492
+ SPAWN_LOCK_PATH = path9.join(EXE_AI_DIR, "exed-spawn.lock");
1411
1493
  SPAWN_LOCK_STALE_MS = 3e4;
1412
1494
  CONNECT_TIMEOUT_MS = 15e3;
1413
1495
  REQUEST_TIMEOUT_MS = 3e4;
1496
+ DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
1414
1497
  _socket = null;
1415
1498
  _connected = false;
1416
1499
  _buffer = "";
@@ -1989,6 +2072,7 @@ async function ensureSchema() {
1989
2072
  project TEXT NOT NULL,
1990
2073
  summary TEXT NOT NULL,
1991
2074
  task_file TEXT,
2075
+ session_scope TEXT,
1992
2076
  read INTEGER NOT NULL DEFAULT 0,
1993
2077
  created_at TEXT NOT NULL
1994
2078
  );
@@ -1997,7 +2081,7 @@ async function ensureSchema() {
1997
2081
  ON notifications(read);
1998
2082
 
1999
2083
  CREATE INDEX IF NOT EXISTS idx_notifications_agent
2000
- ON notifications(agent_id);
2084
+ ON notifications(agent_id, session_scope);
2001
2085
 
2002
2086
  CREATE INDEX IF NOT EXISTS idx_notifications_task_file
2003
2087
  ON notifications(task_file);
@@ -2035,6 +2119,7 @@ async function ensureSchema() {
2035
2119
  target_agent TEXT NOT NULL,
2036
2120
  target_project TEXT,
2037
2121
  target_device TEXT NOT NULL DEFAULT 'local',
2122
+ session_scope TEXT,
2038
2123
  content TEXT NOT NULL,
2039
2124
  priority TEXT DEFAULT 'normal',
2040
2125
  status TEXT DEFAULT 'pending',
@@ -2048,10 +2133,31 @@ async function ensureSchema() {
2048
2133
  );
2049
2134
 
2050
2135
  CREATE INDEX IF NOT EXISTS idx_messages_target
2051
- ON messages(target_agent, status);
2136
+ ON messages(target_agent, session_scope, status);
2052
2137
 
2053
2138
  CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
2054
- ON messages(target_agent, from_agent, server_seq);
2139
+ ON messages(target_agent, session_scope, from_agent, server_seq);
2140
+ `);
2141
+ try {
2142
+ await client.execute({
2143
+ sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
2144
+ args: []
2145
+ });
2146
+ } catch {
2147
+ }
2148
+ try {
2149
+ await client.execute({
2150
+ sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
2151
+ args: []
2152
+ });
2153
+ } catch {
2154
+ }
2155
+ await client.executeMultiple(`
2156
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
2157
+ ON notifications(agent_id, session_scope, read, created_at);
2158
+
2159
+ CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
2160
+ ON messages(target_agent, session_scope, status, created_at);
2055
2161
  `);
2056
2162
  try {
2057
2163
  await client.execute({
@@ -2635,6 +2741,13 @@ async function ensureSchema() {
2635
2741
  } catch {
2636
2742
  }
2637
2743
  }
2744
+ try {
2745
+ await client.execute({
2746
+ sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
2747
+ args: []
2748
+ });
2749
+ } catch {
2750
+ }
2638
2751
  }
2639
2752
  async function disposeDatabase() {
2640
2753
  if (_walCheckpointTimer) {
@@ -2673,24 +2786,27 @@ var init_database = __esm({
2673
2786
  });
2674
2787
 
2675
2788
  // src/lib/license.ts
2676
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
2789
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync8, mkdirSync as mkdirSync4 } from "fs";
2677
2790
  import { randomUUID as randomUUID2 } from "crypto";
2678
- import path9 from "path";
2791
+ import { createRequire as createRequire2 } from "module";
2792
+ import { pathToFileURL as pathToFileURL2 } from "url";
2793
+ import os7 from "os";
2794
+ import path10 from "path";
2679
2795
  import { jwtVerify, importSPKI } from "jose";
2680
2796
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH;
2681
2797
  var init_license = __esm({
2682
2798
  "src/lib/license.ts"() {
2683
2799
  "use strict";
2684
2800
  init_config();
2685
- LICENSE_PATH = path9.join(EXE_AI_DIR, "license.key");
2686
- CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
2687
- DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
2801
+ LICENSE_PATH = path10.join(EXE_AI_DIR, "license.key");
2802
+ CACHE_PATH = path10.join(EXE_AI_DIR, "license-cache.json");
2803
+ DEVICE_ID_PATH = path10.join(EXE_AI_DIR, "device-id");
2688
2804
  }
2689
2805
  });
2690
2806
 
2691
2807
  // src/lib/plan-limits.ts
2692
- import { readFileSync as readFileSync8, existsSync as existsSync7 } from "fs";
2693
- import path10 from "path";
2808
+ import { readFileSync as readFileSync9, existsSync as existsSync9 } from "fs";
2809
+ import path11 from "path";
2694
2810
  var CACHE_PATH2;
2695
2811
  var init_plan_limits = __esm({
2696
2812
  "src/lib/plan-limits.ts"() {
@@ -2699,14 +2815,14 @@ var init_plan_limits = __esm({
2699
2815
  init_employees();
2700
2816
  init_license();
2701
2817
  init_config();
2702
- CACHE_PATH2 = path10.join(EXE_AI_DIR, "license-cache.json");
2818
+ CACHE_PATH2 = path11.join(EXE_AI_DIR, "license-cache.json");
2703
2819
  }
2704
2820
  });
2705
2821
 
2706
2822
  // src/lib/tmux-routing.ts
2707
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, existsSync as existsSync8, appendFileSync, readdirSync as readdirSync2 } from "fs";
2708
- import path11 from "path";
2709
- import os7 from "os";
2823
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, mkdirSync as mkdirSync5, existsSync as existsSync10, appendFileSync, readdirSync as readdirSync2 } from "fs";
2824
+ import path12 from "path";
2825
+ import os8 from "os";
2710
2826
  import { fileURLToPath as fileURLToPath2 } from "url";
2711
2827
  function getMySession() {
2712
2828
  return getTransport().getMySession();
@@ -2719,7 +2835,7 @@ function extractRootExe(name) {
2719
2835
  }
2720
2836
  function getParentExe(sessionKey) {
2721
2837
  try {
2722
- const data = JSON.parse(readFileSync9(path11.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
2838
+ const data = JSON.parse(readFileSync10(path12.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
2723
2839
  return data.parentExe || null;
2724
2840
  } catch {
2725
2841
  return null;
@@ -2762,10 +2878,10 @@ var init_tmux_routing = __esm({
2762
2878
  init_intercom_queue();
2763
2879
  init_plan_limits();
2764
2880
  init_employees();
2765
- SPAWN_LOCK_DIR = path11.join(os7.homedir(), ".exe-os", "spawn-locks");
2766
- SESSION_CACHE = path11.join(os7.homedir(), ".exe-os", "session-cache");
2767
- INTERCOM_LOG2 = path11.join(os7.homedir(), ".exe-os", "intercom.log");
2768
- DEBOUNCE_FILE = path11.join(SESSION_CACHE, "intercom-debounce.json");
2881
+ SPAWN_LOCK_DIR = path12.join(os8.homedir(), ".exe-os", "spawn-locks");
2882
+ SESSION_CACHE = path12.join(os8.homedir(), ".exe-os", "session-cache");
2883
+ INTERCOM_LOG2 = path12.join(os8.homedir(), ".exe-os", "intercom.log");
2884
+ DEBOUNCE_FILE = path12.join(SESSION_CACHE, "intercom-debounce.json");
2769
2885
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
2770
2886
  }
2771
2887
  });
@@ -2805,14 +2921,14 @@ var init_memory = __esm({
2805
2921
 
2806
2922
  // src/lib/keychain.ts
2807
2923
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
2808
- import { existsSync as existsSync9 } from "fs";
2809
- import path12 from "path";
2810
- import os8 from "os";
2924
+ import { existsSync as existsSync11 } from "fs";
2925
+ import path13 from "path";
2926
+ import os9 from "os";
2811
2927
  function getKeyDir() {
2812
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path12.join(os8.homedir(), ".exe-os");
2928
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path13.join(os9.homedir(), ".exe-os");
2813
2929
  }
2814
2930
  function getKeyPath() {
2815
- return path12.join(getKeyDir(), "master.key");
2931
+ return path13.join(getKeyDir(), "master.key");
2816
2932
  }
2817
2933
  async function tryKeytar() {
2818
2934
  try {
@@ -2833,9 +2949,9 @@ async function getMasterKey() {
2833
2949
  }
2834
2950
  }
2835
2951
  const keyPath = getKeyPath();
2836
- if (!existsSync9(keyPath)) {
2952
+ if (!existsSync11(keyPath)) {
2837
2953
  process.stderr.write(
2838
- `[keychain] Key not found at ${keyPath} (HOME=${os8.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
2954
+ `[keychain] Key not found at ${keyPath} (HOME=${os9.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
2839
2955
  `
2840
2956
  );
2841
2957
  return null;
@@ -2920,6 +3036,7 @@ var shard_manager_exports = {};
2920
3036
  __export(shard_manager_exports, {
2921
3037
  disposeShards: () => disposeShards,
2922
3038
  ensureShardSchema: () => ensureShardSchema,
3039
+ getOpenShardCount: () => getOpenShardCount,
2923
3040
  getReadyShardClient: () => getReadyShardClient,
2924
3041
  getShardClient: () => getShardClient,
2925
3042
  getShardsDir: () => getShardsDir,
@@ -2928,15 +3045,18 @@ __export(shard_manager_exports, {
2928
3045
  listShards: () => listShards,
2929
3046
  shardExists: () => shardExists
2930
3047
  });
2931
- import path13 from "path";
2932
- import { existsSync as existsSync10, mkdirSync as mkdirSync6, readdirSync as readdirSync3 } from "fs";
3048
+ import path14 from "path";
3049
+ import { existsSync as existsSync12, mkdirSync as mkdirSync6, readdirSync as readdirSync3 } from "fs";
2933
3050
  import { createClient as createClient2 } from "@libsql/client";
2934
3051
  function initShardManager(encryptionKey) {
2935
3052
  _encryptionKey = encryptionKey;
2936
- if (!existsSync10(SHARDS_DIR)) {
3053
+ if (!existsSync12(SHARDS_DIR)) {
2937
3054
  mkdirSync6(SHARDS_DIR, { recursive: true });
2938
3055
  }
2939
3056
  _shardingEnabled = true;
3057
+ if (_evictionTimer) clearInterval(_evictionTimer);
3058
+ _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
3059
+ _evictionTimer.unref();
2940
3060
  }
2941
3061
  function isShardingEnabled() {
2942
3062
  return _shardingEnabled;
@@ -2953,21 +3073,28 @@ function getShardClient(projectName) {
2953
3073
  throw new Error(`Invalid project name for shard: "${projectName}"`);
2954
3074
  }
2955
3075
  const cached = _shards.get(safeName);
2956
- if (cached) return cached;
2957
- const dbPath = path13.join(SHARDS_DIR, `${safeName}.db`);
3076
+ if (cached) {
3077
+ _shardLastAccess.set(safeName, Date.now());
3078
+ return cached;
3079
+ }
3080
+ while (_shards.size >= MAX_OPEN_SHARDS) {
3081
+ evictLRU();
3082
+ }
3083
+ const dbPath = path14.join(SHARDS_DIR, `${safeName}.db`);
2958
3084
  const client = createClient2({
2959
3085
  url: `file:${dbPath}`,
2960
3086
  encryptionKey: _encryptionKey
2961
3087
  });
2962
3088
  _shards.set(safeName, client);
3089
+ _shardLastAccess.set(safeName, Date.now());
2963
3090
  return client;
2964
3091
  }
2965
3092
  function shardExists(projectName) {
2966
3093
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
2967
- return existsSync10(path13.join(SHARDS_DIR, `${safeName}.db`));
3094
+ return existsSync12(path14.join(SHARDS_DIR, `${safeName}.db`));
2968
3095
  }
2969
3096
  function listShards() {
2970
- if (!existsSync10(SHARDS_DIR)) return [];
3097
+ if (!existsSync12(SHARDS_DIR)) return [];
2971
3098
  return readdirSync3(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
2972
3099
  }
2973
3100
  async function ensureShardSchema(client) {
@@ -3019,6 +3146,8 @@ async function ensureShardSchema(client) {
3019
3146
  for (const col of [
3020
3147
  "ALTER TABLE memories ADD COLUMN task_id TEXT",
3021
3148
  "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
3149
+ "ALTER TABLE memories ADD COLUMN author_device_id TEXT",
3150
+ "ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
3022
3151
  "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
3023
3152
  "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
3024
3153
  "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
@@ -3156,21 +3285,69 @@ async function getReadyShardClient(projectName) {
3156
3285
  await ensureShardSchema(client);
3157
3286
  return client;
3158
3287
  }
3288
+ function evictLRU() {
3289
+ let oldest = null;
3290
+ let oldestTime = Infinity;
3291
+ for (const [name, time] of _shardLastAccess) {
3292
+ if (time < oldestTime) {
3293
+ oldestTime = time;
3294
+ oldest = name;
3295
+ }
3296
+ }
3297
+ if (oldest) {
3298
+ const client = _shards.get(oldest);
3299
+ if (client) {
3300
+ client.close();
3301
+ }
3302
+ _shards.delete(oldest);
3303
+ _shardLastAccess.delete(oldest);
3304
+ }
3305
+ }
3306
+ function evictIdleShards() {
3307
+ const now = Date.now();
3308
+ const toEvict = [];
3309
+ for (const [name, lastAccess] of _shardLastAccess) {
3310
+ if (now - lastAccess > SHARD_IDLE_MS) {
3311
+ toEvict.push(name);
3312
+ }
3313
+ }
3314
+ for (const name of toEvict) {
3315
+ const client = _shards.get(name);
3316
+ if (client) {
3317
+ client.close();
3318
+ }
3319
+ _shards.delete(name);
3320
+ _shardLastAccess.delete(name);
3321
+ }
3322
+ }
3323
+ function getOpenShardCount() {
3324
+ return _shards.size;
3325
+ }
3159
3326
  function disposeShards() {
3327
+ if (_evictionTimer) {
3328
+ clearInterval(_evictionTimer);
3329
+ _evictionTimer = null;
3330
+ }
3160
3331
  for (const [, client] of _shards) {
3161
3332
  client.close();
3162
3333
  }
3163
3334
  _shards.clear();
3335
+ _shardLastAccess.clear();
3164
3336
  _shardingEnabled = false;
3165
3337
  _encryptionKey = null;
3166
3338
  }
3167
- var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
3339
+ var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
3168
3340
  var init_shard_manager = __esm({
3169
3341
  "src/lib/shard-manager.ts"() {
3170
3342
  "use strict";
3171
3343
  init_config();
3172
- SHARDS_DIR = path13.join(EXE_AI_DIR, "shards");
3344
+ SHARDS_DIR = path14.join(EXE_AI_DIR, "shards");
3345
+ SHARD_IDLE_MS = 5 * 60 * 1e3;
3346
+ MAX_OPEN_SHARDS = 10;
3347
+ EVICTION_INTERVAL_MS = 60 * 1e3;
3173
3348
  _shards = /* @__PURE__ */ new Map();
3349
+ _shardLastAccess = /* @__PURE__ */ new Map();
3350
+ _evictionTimer = null;
3174
3351
  _encryptionKey = null;
3175
3352
  _shardingEnabled = false;
3176
3353
  }
@@ -3937,7 +4114,7 @@ var init_store = __esm({
3937
4114
  init_config();
3938
4115
  init_session_key();
3939
4116
  init_employees();
3940
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync, unlinkSync as unlinkSync2, readdirSync } from "fs";
4117
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2, readdirSync } from "fs";
3941
4118
  import { execSync as execSync3 } from "child_process";
3942
4119
  import path3 from "path";
3943
4120
  var CACHE_DIR = path3.join(EXE_AI_DIR, "session-cache");