@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
@@ -75,9 +75,34 @@ var init_db_retry = __esm({
75
75
  }
76
76
  });
77
77
 
78
+ // src/lib/secure-files.ts
79
+ import { chmodSync, existsSync, mkdirSync } from "fs";
80
+ import { chmod, mkdir } from "fs/promises";
81
+ async function ensurePrivateDir(dirPath) {
82
+ await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
83
+ try {
84
+ await chmod(dirPath, PRIVATE_DIR_MODE);
85
+ } catch {
86
+ }
87
+ }
88
+ async function enforcePrivateFile(filePath) {
89
+ try {
90
+ await chmod(filePath, PRIVATE_FILE_MODE);
91
+ } catch {
92
+ }
93
+ }
94
+ var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
95
+ var init_secure_files = __esm({
96
+ "src/lib/secure-files.ts"() {
97
+ "use strict";
98
+ PRIVATE_DIR_MODE = 448;
99
+ PRIVATE_FILE_MODE = 384;
100
+ }
101
+ });
102
+
78
103
  // src/lib/config.ts
79
- import { readFile, writeFile, mkdir, chmod } from "fs/promises";
80
- import { readFileSync, existsSync, renameSync } from "fs";
104
+ import { readFile, writeFile } from "fs/promises";
105
+ import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
81
106
  import path from "path";
82
107
  import os from "os";
83
108
  function resolveDataDir() {
@@ -85,7 +110,7 @@ function resolveDataDir() {
85
110
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
86
111
  const newDir = path.join(os.homedir(), ".exe-os");
87
112
  const legacyDir = path.join(os.homedir(), ".exe-mem");
88
- if (!existsSync(newDir) && existsSync(legacyDir)) {
113
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
89
114
  try {
90
115
  renameSync(legacyDir, newDir);
91
116
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -148,9 +173,9 @@ function normalizeAutoUpdate(raw) {
148
173
  }
149
174
  async function loadConfig() {
150
175
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
151
- await mkdir(dir, { recursive: true });
176
+ await ensurePrivateDir(dir);
152
177
  const configPath = path.join(dir, "config.json");
153
- if (!existsSync(configPath)) {
178
+ if (!existsSync2(configPath)) {
154
179
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
155
180
  }
156
181
  const raw = await readFile(configPath, "utf-8");
@@ -163,6 +188,7 @@ async function loadConfig() {
163
188
  `);
164
189
  try {
165
190
  await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
191
+ await enforcePrivateFile(configPath);
166
192
  } catch {
167
193
  }
168
194
  }
@@ -181,7 +207,7 @@ async function loadConfig() {
181
207
  function loadConfigSync() {
182
208
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
183
209
  const configPath = path.join(dir, "config.json");
184
- if (!existsSync(configPath)) {
210
+ if (!existsSync2(configPath)) {
185
211
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
186
212
  }
187
213
  try {
@@ -201,6 +227,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
201
227
  var init_config = __esm({
202
228
  "src/lib/config.ts"() {
203
229
  "use strict";
230
+ init_secure_files();
204
231
  EXE_AI_DIR = resolveDataDir();
205
232
  DB_PATH = path.join(EXE_AI_DIR, "memories.db");
206
233
  MODELS_DIR = path.join(EXE_AI_DIR, "models");
@@ -279,7 +306,7 @@ var init_config = __esm({
279
306
 
280
307
  // src/lib/employees.ts
281
308
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
282
- import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
309
+ import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
283
310
  import { execSync } from "child_process";
284
311
  import path2 from "path";
285
312
  import os2 from "os";
@@ -296,7 +323,7 @@ function getCoordinatorName(employees = loadEmployeesSync()) {
296
323
  return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
297
324
  }
298
325
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
299
- if (!existsSync2(employeesPath)) return [];
326
+ if (!existsSync3(employeesPath)) return [];
300
327
  try {
301
328
  return JSON.parse(readFileSync2(employeesPath, "utf-8"));
302
329
  } catch {
@@ -1241,6 +1268,7 @@ async function ensureSchema() {
1241
1268
  project TEXT NOT NULL,
1242
1269
  summary TEXT NOT NULL,
1243
1270
  task_file TEXT,
1271
+ session_scope TEXT,
1244
1272
  read INTEGER NOT NULL DEFAULT 0,
1245
1273
  created_at TEXT NOT NULL
1246
1274
  );
@@ -1249,7 +1277,7 @@ async function ensureSchema() {
1249
1277
  ON notifications(read);
1250
1278
 
1251
1279
  CREATE INDEX IF NOT EXISTS idx_notifications_agent
1252
- ON notifications(agent_id);
1280
+ ON notifications(agent_id, session_scope);
1253
1281
 
1254
1282
  CREATE INDEX IF NOT EXISTS idx_notifications_task_file
1255
1283
  ON notifications(task_file);
@@ -1287,6 +1315,7 @@ async function ensureSchema() {
1287
1315
  target_agent TEXT NOT NULL,
1288
1316
  target_project TEXT,
1289
1317
  target_device TEXT NOT NULL DEFAULT 'local',
1318
+ session_scope TEXT,
1290
1319
  content TEXT NOT NULL,
1291
1320
  priority TEXT DEFAULT 'normal',
1292
1321
  status TEXT DEFAULT 'pending',
@@ -1300,10 +1329,31 @@ async function ensureSchema() {
1300
1329
  );
1301
1330
 
1302
1331
  CREATE INDEX IF NOT EXISTS idx_messages_target
1303
- ON messages(target_agent, status);
1332
+ ON messages(target_agent, session_scope, status);
1304
1333
 
1305
1334
  CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
1306
- ON messages(target_agent, from_agent, server_seq);
1335
+ ON messages(target_agent, session_scope, from_agent, server_seq);
1336
+ `);
1337
+ try {
1338
+ await client.execute({
1339
+ sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
1340
+ args: []
1341
+ });
1342
+ } catch {
1343
+ }
1344
+ try {
1345
+ await client.execute({
1346
+ sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
1347
+ args: []
1348
+ });
1349
+ } catch {
1350
+ }
1351
+ await client.executeMultiple(`
1352
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
1353
+ ON notifications(agent_id, session_scope, read, created_at);
1354
+
1355
+ CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
1356
+ ON messages(target_agent, session_scope, status, created_at);
1307
1357
  `);
1308
1358
  try {
1309
1359
  await client.execute({
@@ -1887,6 +1937,13 @@ async function ensureSchema() {
1887
1937
  } catch {
1888
1938
  }
1889
1939
  }
1940
+ try {
1941
+ await client.execute({
1942
+ sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
1943
+ args: []
1944
+ });
1945
+ } catch {
1946
+ }
1890
1947
  }
1891
1948
  var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso;
1892
1949
  var init_database = __esm({
@@ -1964,6 +2021,7 @@ var shard_manager_exports = {};
1964
2021
  __export(shard_manager_exports, {
1965
2022
  disposeShards: () => disposeShards,
1966
2023
  ensureShardSchema: () => ensureShardSchema,
2024
+ getOpenShardCount: () => getOpenShardCount,
1967
2025
  getReadyShardClient: () => getReadyShardClient,
1968
2026
  getShardClient: () => getShardClient,
1969
2027
  getShardsDir: () => getShardsDir,
@@ -1973,14 +2031,17 @@ __export(shard_manager_exports, {
1973
2031
  shardExists: () => shardExists
1974
2032
  });
1975
2033
  import path5 from "path";
1976
- import { existsSync as existsSync4, mkdirSync, readdirSync } from "fs";
2034
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, readdirSync } from "fs";
1977
2035
  import { createClient as createClient2 } from "@libsql/client";
1978
2036
  function initShardManager(encryptionKey) {
1979
2037
  _encryptionKey = encryptionKey;
1980
- if (!existsSync4(SHARDS_DIR)) {
1981
- mkdirSync(SHARDS_DIR, { recursive: true });
2038
+ if (!existsSync5(SHARDS_DIR)) {
2039
+ mkdirSync2(SHARDS_DIR, { recursive: true });
1982
2040
  }
1983
2041
  _shardingEnabled = true;
2042
+ if (_evictionTimer) clearInterval(_evictionTimer);
2043
+ _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
2044
+ _evictionTimer.unref();
1984
2045
  }
1985
2046
  function isShardingEnabled() {
1986
2047
  return _shardingEnabled;
@@ -1997,21 +2058,28 @@ function getShardClient(projectName) {
1997
2058
  throw new Error(`Invalid project name for shard: "${projectName}"`);
1998
2059
  }
1999
2060
  const cached = _shards.get(safeName);
2000
- if (cached) return cached;
2061
+ if (cached) {
2062
+ _shardLastAccess.set(safeName, Date.now());
2063
+ return cached;
2064
+ }
2065
+ while (_shards.size >= MAX_OPEN_SHARDS) {
2066
+ evictLRU();
2067
+ }
2001
2068
  const dbPath = path5.join(SHARDS_DIR, `${safeName}.db`);
2002
2069
  const client = createClient2({
2003
2070
  url: `file:${dbPath}`,
2004
2071
  encryptionKey: _encryptionKey
2005
2072
  });
2006
2073
  _shards.set(safeName, client);
2074
+ _shardLastAccess.set(safeName, Date.now());
2007
2075
  return client;
2008
2076
  }
2009
2077
  function shardExists(projectName) {
2010
2078
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
2011
- return existsSync4(path5.join(SHARDS_DIR, `${safeName}.db`));
2079
+ return existsSync5(path5.join(SHARDS_DIR, `${safeName}.db`));
2012
2080
  }
2013
2081
  function listShards() {
2014
- if (!existsSync4(SHARDS_DIR)) return [];
2082
+ if (!existsSync5(SHARDS_DIR)) return [];
2015
2083
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
2016
2084
  }
2017
2085
  async function ensureShardSchema(client) {
@@ -2063,6 +2131,8 @@ async function ensureShardSchema(client) {
2063
2131
  for (const col of [
2064
2132
  "ALTER TABLE memories ADD COLUMN task_id TEXT",
2065
2133
  "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
2134
+ "ALTER TABLE memories ADD COLUMN author_device_id TEXT",
2135
+ "ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
2066
2136
  "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
2067
2137
  "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
2068
2138
  "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
@@ -2200,21 +2270,69 @@ async function getReadyShardClient(projectName) {
2200
2270
  await ensureShardSchema(client);
2201
2271
  return client;
2202
2272
  }
2273
+ function evictLRU() {
2274
+ let oldest = null;
2275
+ let oldestTime = Infinity;
2276
+ for (const [name, time] of _shardLastAccess) {
2277
+ if (time < oldestTime) {
2278
+ oldestTime = time;
2279
+ oldest = name;
2280
+ }
2281
+ }
2282
+ if (oldest) {
2283
+ const client = _shards.get(oldest);
2284
+ if (client) {
2285
+ client.close();
2286
+ }
2287
+ _shards.delete(oldest);
2288
+ _shardLastAccess.delete(oldest);
2289
+ }
2290
+ }
2291
+ function evictIdleShards() {
2292
+ const now = Date.now();
2293
+ const toEvict = [];
2294
+ for (const [name, lastAccess] of _shardLastAccess) {
2295
+ if (now - lastAccess > SHARD_IDLE_MS) {
2296
+ toEvict.push(name);
2297
+ }
2298
+ }
2299
+ for (const name of toEvict) {
2300
+ const client = _shards.get(name);
2301
+ if (client) {
2302
+ client.close();
2303
+ }
2304
+ _shards.delete(name);
2305
+ _shardLastAccess.delete(name);
2306
+ }
2307
+ }
2308
+ function getOpenShardCount() {
2309
+ return _shards.size;
2310
+ }
2203
2311
  function disposeShards() {
2312
+ if (_evictionTimer) {
2313
+ clearInterval(_evictionTimer);
2314
+ _evictionTimer = null;
2315
+ }
2204
2316
  for (const [, client] of _shards) {
2205
2317
  client.close();
2206
2318
  }
2207
2319
  _shards.clear();
2320
+ _shardLastAccess.clear();
2208
2321
  _shardingEnabled = false;
2209
2322
  _encryptionKey = null;
2210
2323
  }
2211
- var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
2324
+ var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
2212
2325
  var init_shard_manager = __esm({
2213
2326
  "src/lib/shard-manager.ts"() {
2214
2327
  "use strict";
2215
2328
  init_config();
2216
2329
  SHARDS_DIR = path5.join(EXE_AI_DIR, "shards");
2330
+ SHARD_IDLE_MS = 5 * 60 * 1e3;
2331
+ MAX_OPEN_SHARDS = 10;
2332
+ EVICTION_INTERVAL_MS = 60 * 1e3;
2217
2333
  _shards = /* @__PURE__ */ new Map();
2334
+ _shardLastAccess = /* @__PURE__ */ new Map();
2335
+ _evictionTimer = null;
2218
2336
  _encryptionKey = null;
2219
2337
  _shardingEnabled = false;
2220
2338
  }
@@ -2407,32 +2525,14 @@ ${p.content}`).join("\n\n");
2407
2525
  }
2408
2526
  });
2409
2527
 
2410
- // src/lib/notifications.ts
2411
- import crypto from "crypto";
2528
+ // src/lib/session-registry.ts
2412
2529
  import path6 from "path";
2413
2530
  import os5 from "os";
2414
- import {
2415
- readFileSync as readFileSync3,
2416
- readdirSync as readdirSync2,
2417
- unlinkSync as unlinkSync2,
2418
- existsSync as existsSync5,
2419
- rmdirSync
2420
- } from "fs";
2421
- var init_notifications = __esm({
2422
- "src/lib/notifications.ts"() {
2423
- "use strict";
2424
- init_database();
2425
- }
2426
- });
2427
-
2428
- // src/lib/session-registry.ts
2429
- import path7 from "path";
2430
- import os6 from "os";
2431
2531
  var REGISTRY_PATH;
2432
2532
  var init_session_registry = __esm({
2433
2533
  "src/lib/session-registry.ts"() {
2434
2534
  "use strict";
2435
- REGISTRY_PATH = path7.join(os6.homedir(), ".exe-os", "session-registry.json");
2535
+ REGISTRY_PATH = path6.join(os5.homedir(), ".exe-os", "session-registry.json");
2436
2536
  }
2437
2537
  });
2438
2538
 
@@ -2667,15 +2767,16 @@ var init_runtime_table = __esm({
2667
2767
  });
2668
2768
 
2669
2769
  // src/lib/agent-config.ts
2670
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync6, mkdirSync as mkdirSync2 } from "fs";
2671
- import path8 from "path";
2770
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync6 } from "fs";
2771
+ import path7 from "path";
2672
2772
  var AGENT_CONFIG_PATH, DEFAULT_MODELS;
2673
2773
  var init_agent_config = __esm({
2674
2774
  "src/lib/agent-config.ts"() {
2675
2775
  "use strict";
2676
2776
  init_config();
2677
2777
  init_runtime_table();
2678
- AGENT_CONFIG_PATH = path8.join(EXE_AI_DIR, "agent-config.json");
2778
+ init_secure_files();
2779
+ AGENT_CONFIG_PATH = path7.join(EXE_AI_DIR, "agent-config.json");
2679
2780
  DEFAULT_MODELS = {
2680
2781
  claude: "claude-opus-4",
2681
2782
  codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
@@ -2685,38 +2786,41 @@ var init_agent_config = __esm({
2685
2786
  });
2686
2787
 
2687
2788
  // src/lib/intercom-queue.ts
2688
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, renameSync as renameSync3, existsSync as existsSync7, mkdirSync as mkdirSync3 } from "fs";
2689
- import path9 from "path";
2690
- import os7 from "os";
2789
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync3, existsSync as existsSync7, mkdirSync as mkdirSync3 } from "fs";
2790
+ import path8 from "path";
2791
+ import os6 from "os";
2691
2792
  var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
2692
2793
  var init_intercom_queue = __esm({
2693
2794
  "src/lib/intercom-queue.ts"() {
2694
2795
  "use strict";
2695
- QUEUE_PATH = path9.join(os7.homedir(), ".exe-os", "intercom-queue.json");
2796
+ QUEUE_PATH = path8.join(os6.homedir(), ".exe-os", "intercom-queue.json");
2696
2797
  TTL_MS = 60 * 60 * 1e3;
2697
- INTERCOM_LOG = path9.join(os7.homedir(), ".exe-os", "intercom.log");
2798
+ INTERCOM_LOG = path8.join(os6.homedir(), ".exe-os", "intercom.log");
2698
2799
  }
2699
2800
  });
2700
2801
 
2701
2802
  // src/lib/license.ts
2702
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync8, mkdirSync as mkdirSync4 } from "fs";
2803
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync8, mkdirSync as mkdirSync4 } from "fs";
2703
2804
  import { randomUUID as randomUUID2 } from "crypto";
2704
- import path10 from "path";
2805
+ import { createRequire as createRequire2 } from "module";
2806
+ import { pathToFileURL as pathToFileURL2 } from "url";
2807
+ import os7 from "os";
2808
+ import path9 from "path";
2705
2809
  import { jwtVerify, importSPKI } from "jose";
2706
2810
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH;
2707
2811
  var init_license = __esm({
2708
2812
  "src/lib/license.ts"() {
2709
2813
  "use strict";
2710
2814
  init_config();
2711
- LICENSE_PATH = path10.join(EXE_AI_DIR, "license.key");
2712
- CACHE_PATH = path10.join(EXE_AI_DIR, "license-cache.json");
2713
- DEVICE_ID_PATH = path10.join(EXE_AI_DIR, "device-id");
2815
+ LICENSE_PATH = path9.join(EXE_AI_DIR, "license.key");
2816
+ CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
2817
+ DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
2714
2818
  }
2715
2819
  });
2716
2820
 
2717
2821
  // src/lib/plan-limits.ts
2718
- import { readFileSync as readFileSync7, existsSync as existsSync9 } from "fs";
2719
- import path11 from "path";
2822
+ import { readFileSync as readFileSync6, existsSync as existsSync9 } from "fs";
2823
+ import path10 from "path";
2720
2824
  var CACHE_PATH2;
2721
2825
  var init_plan_limits = __esm({
2722
2826
  "src/lib/plan-limits.ts"() {
@@ -2725,13 +2829,13 @@ var init_plan_limits = __esm({
2725
2829
  init_employees();
2726
2830
  init_license();
2727
2831
  init_config();
2728
- CACHE_PATH2 = path11.join(EXE_AI_DIR, "license-cache.json");
2832
+ CACHE_PATH2 = path10.join(EXE_AI_DIR, "license-cache.json");
2729
2833
  }
2730
2834
  });
2731
2835
 
2732
2836
  // src/lib/tmux-routing.ts
2733
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, existsSync as existsSync10, appendFileSync, readdirSync as readdirSync3 } from "fs";
2734
- import path12 from "path";
2837
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, existsSync as existsSync10, appendFileSync, readdirSync as readdirSync2 } from "fs";
2838
+ import path11 from "path";
2735
2839
  import os8 from "os";
2736
2840
  import { fileURLToPath as fileURLToPath2 } from "url";
2737
2841
  function getMySession() {
@@ -2745,7 +2849,7 @@ function extractRootExe(name) {
2745
2849
  }
2746
2850
  function getParentExe(sessionKey) {
2747
2851
  try {
2748
- const data = JSON.parse(readFileSync8(path12.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
2852
+ const data = JSON.parse(readFileSync7(path11.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
2749
2853
  return data.parentExe || null;
2750
2854
  } catch {
2751
2855
  return null;
@@ -2793,34 +2897,70 @@ var init_tmux_routing = __esm({
2793
2897
  init_intercom_queue();
2794
2898
  init_plan_limits();
2795
2899
  init_employees();
2796
- SPAWN_LOCK_DIR = path12.join(os8.homedir(), ".exe-os", "spawn-locks");
2797
- SESSION_CACHE = path12.join(os8.homedir(), ".exe-os", "session-cache");
2798
- INTERCOM_LOG2 = path12.join(os8.homedir(), ".exe-os", "intercom.log");
2799
- DEBOUNCE_FILE = path12.join(SESSION_CACHE, "intercom-debounce.json");
2900
+ SPAWN_LOCK_DIR = path11.join(os8.homedir(), ".exe-os", "spawn-locks");
2901
+ SESSION_CACHE = path11.join(os8.homedir(), ".exe-os", "session-cache");
2902
+ INTERCOM_LOG2 = path11.join(os8.homedir(), ".exe-os", "intercom.log");
2903
+ DEBOUNCE_FILE = path11.join(SESSION_CACHE, "intercom-debounce.json");
2800
2904
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
2801
2905
  }
2802
2906
  });
2803
2907
 
2908
+ // src/lib/task-scope.ts
2909
+ function getCurrentSessionScope() {
2910
+ try {
2911
+ return resolveExeSession();
2912
+ } catch {
2913
+ return null;
2914
+ }
2915
+ }
2916
+ function strictSessionScopeFilter(sessionScope, tableAlias) {
2917
+ const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
2918
+ if (!scope) return { sql: "", args: [] };
2919
+ const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
2920
+ return {
2921
+ sql: ` AND ${col} = ?`,
2922
+ args: [scope]
2923
+ };
2924
+ }
2925
+ var init_task_scope = __esm({
2926
+ "src/lib/task-scope.ts"() {
2927
+ "use strict";
2928
+ init_tmux_routing();
2929
+ }
2930
+ });
2931
+
2932
+ // src/lib/notifications.ts
2933
+ import crypto from "crypto";
2934
+ import path12 from "path";
2935
+ import os9 from "os";
2936
+ import {
2937
+ readFileSync as readFileSync8,
2938
+ readdirSync as readdirSync3,
2939
+ unlinkSync as unlinkSync2,
2940
+ existsSync as existsSync11,
2941
+ rmdirSync
2942
+ } from "fs";
2943
+ var init_notifications = __esm({
2944
+ "src/lib/notifications.ts"() {
2945
+ "use strict";
2946
+ init_database();
2947
+ init_task_scope();
2948
+ }
2949
+ });
2950
+
2804
2951
  // src/lib/tasks-review.ts
2805
2952
  import path13 from "path";
2806
- import { existsSync as existsSync11, readdirSync as readdirSync4, unlinkSync as unlinkSync3 } from "fs";
2953
+ import { existsSync as existsSync12, readdirSync as readdirSync4, unlinkSync as unlinkSync3 } from "fs";
2807
2954
  async function listPendingReviews(limit, sessionScope) {
2808
2955
  const client = getClient();
2809
- if (sessionScope) {
2810
- const result2 = await client.execute({
2811
- sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
2812
- WHERE status = 'needs_review'
2813
- AND session_scope = ?
2814
- ORDER BY updated_at ASC LIMIT ?`,
2815
- args: [sessionScope, limit]
2816
- });
2817
- return result2.rows;
2818
- }
2956
+ const scope = strictSessionScopeFilter(
2957
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
2958
+ );
2819
2959
  const result = await client.execute({
2820
2960
  sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
2821
- WHERE status = 'needs_review'
2961
+ WHERE status = 'needs_review'${scope.sql}
2822
2962
  ORDER BY updated_at ASC LIMIT ?`,
2823
- args: [limit]
2963
+ args: [...scope.args, limit]
2824
2964
  });
2825
2965
  return result.rows;
2826
2966
  }
@@ -2834,37 +2974,14 @@ var init_tasks_review = __esm({
2834
2974
  init_tmux_routing();
2835
2975
  init_session_key();
2836
2976
  init_state_bus();
2837
- }
2838
- });
2839
-
2840
- // src/lib/task-scope.ts
2841
- function getCurrentSessionScope() {
2842
- try {
2843
- return resolveExeSession();
2844
- } catch {
2845
- return null;
2846
- }
2847
- }
2848
- function sessionScopeFilter(sessionScope, tableAlias) {
2849
- const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
2850
- if (!scope) return { sql: "", args: [] };
2851
- const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
2852
- return {
2853
- sql: ` AND (${col} IS NULL OR ${col} = ?)`,
2854
- args: [scope]
2855
- };
2856
- }
2857
- var init_task_scope = __esm({
2858
- "src/lib/task-scope.ts"() {
2859
- "use strict";
2860
- init_tmux_routing();
2977
+ init_task_scope();
2861
2978
  }
2862
2979
  });
2863
2980
 
2864
2981
  // src/bin/exe-heartbeat.ts
2865
2982
  import { createHash as createHash2 } from "crypto";
2866
2983
  import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, mkdirSync as mkdirSync6 } from "fs";
2867
- import os9 from "os";
2984
+ import os10 from "os";
2868
2985
  import path14 from "path";
2869
2986
 
2870
2987
  // src/lib/store.ts
@@ -2873,7 +2990,7 @@ init_database();
2873
2990
 
2874
2991
  // src/lib/keychain.ts
2875
2992
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
2876
- import { existsSync as existsSync3 } from "fs";
2993
+ import { existsSync as existsSync4 } from "fs";
2877
2994
  import path4 from "path";
2878
2995
  import os4 from "os";
2879
2996
  var SERVICE = "exe-mem";
@@ -2903,7 +3020,7 @@ async function getMasterKey() {
2903
3020
  }
2904
3021
  }
2905
3022
  const keyPath = getKeyPath();
2906
- if (!existsSync3(keyPath)) {
3023
+ if (!existsSync4(keyPath)) {
2907
3024
  process.stderr.write(
2908
3025
  `[keychain] Key not found at ${keyPath} (HOME=${os4.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
2909
3026
  `
@@ -3038,7 +3155,7 @@ var MESSAGE_PREVIEW_CHARS = 80;
3038
3155
  var MARKER_FILENAME = "exe-heartbeat-marker.json";
3039
3156
  var SESSION_CACHE_SUBDIR = "session-cache";
3040
3157
  function resolveExeOsDir() {
3041
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path14.join(os9.homedir(), ".exe-os");
3158
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path14.join(os10.homedir(), ".exe-os");
3042
3159
  }
3043
3160
  function getMarkerDir() {
3044
3161
  return path14.join(resolveExeOsDir(), SESSION_CACHE_SUBDIR);
@@ -3087,12 +3204,13 @@ async function queryPendingReviews() {
3087
3204
  }
3088
3205
  async function queryUnreadMessages() {
3089
3206
  const client = getClient();
3207
+ const hbScope = strictSessionScopeFilter();
3090
3208
  const placeholders = UNREAD_MESSAGE_STATUSES.map(() => "?").join(", ");
3091
3209
  const result = await client.execute({
3092
3210
  sql: `SELECT from_agent, content FROM messages
3093
- WHERE target_agent = 'exe' AND status IN (${placeholders})
3211
+ WHERE target_agent = 'exe' AND status IN (${placeholders})${hbScope.sql}
3094
3212
  ORDER BY created_at ASC LIMIT ?`,
3095
- args: [...UNREAD_MESSAGE_STATUSES, UNREAD_MESSAGE_LIMIT]
3213
+ args: [...UNREAD_MESSAGE_STATUSES, ...hbScope.args, UNREAD_MESSAGE_LIMIT]
3096
3214
  });
3097
3215
  if (result.rows.length === 0) return "";
3098
3216
  const lines = [`\u{1F4E8} ${result.rows.length} unread message(s):`];
@@ -3116,7 +3234,7 @@ function formatRelative(updatedAtIso) {
3116
3234
  }
3117
3235
  async function queryStaleInProgress(thresholdHours) {
3118
3236
  const client = getClient();
3119
- const hbScope = sessionScopeFilter();
3237
+ const hbScope = strictSessionScopeFilter();
3120
3238
  const result = await client.execute({
3121
3239
  sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
3122
3240
  WHERE status = 'in_progress'
@@ -3133,7 +3251,7 @@ async function queryStaleInProgress(thresholdHours) {
3133
3251
  }
3134
3252
  async function queryNewErrors(sinceIso) {
3135
3253
  const client = getClient();
3136
- const erScope = sessionScopeFilter();
3254
+ const erScope = strictSessionScopeFilter();
3137
3255
  const result = await client.execute({
3138
3256
  sql: erScope.args.length > 0 ? `SELECT COUNT(*) as cnt FROM memories
3139
3257
  WHERE has_error = 1 AND timestamp > ?
@@ -3178,10 +3296,11 @@ async function runHeartbeat() {
3178
3296
  writeMarker({ lastFiredAt: new Date(now).toISOString(), lastSurfaceHash: hash });
3179
3297
  try {
3180
3298
  const client = getClient();
3299
+ const ackScope = strictSessionScopeFilter();
3181
3300
  await client.execute({
3182
3301
  sql: `UPDATE messages SET status = 'acknowledged', processed_at = datetime('now')
3183
- WHERE target_agent = 'exe' AND status IN ('pending', 'delivered')`,
3184
- args: []
3302
+ WHERE target_agent = 'exe' AND status IN ('pending', 'delivered')${ackScope.sql}`,
3303
+ args: [...ackScope.args]
3185
3304
  });
3186
3305
  } catch {
3187
3306
  }