@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
@@ -8,6 +8,44 @@ var __export = (target, all) => {
8
8
  __defProp(target, name, { get: all[name], enumerable: true });
9
9
  };
10
10
 
11
+ // src/lib/secure-files.ts
12
+ import { chmodSync, existsSync, mkdirSync } from "fs";
13
+ import { chmod, mkdir } from "fs/promises";
14
+ async function ensurePrivateDir(dirPath) {
15
+ await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
16
+ try {
17
+ await chmod(dirPath, PRIVATE_DIR_MODE);
18
+ } catch {
19
+ }
20
+ }
21
+ function ensurePrivateDirSync(dirPath) {
22
+ mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
23
+ try {
24
+ chmodSync(dirPath, PRIVATE_DIR_MODE);
25
+ } catch {
26
+ }
27
+ }
28
+ async function enforcePrivateFile(filePath) {
29
+ try {
30
+ await chmod(filePath, PRIVATE_FILE_MODE);
31
+ } catch {
32
+ }
33
+ }
34
+ function enforcePrivateFileSync(filePath) {
35
+ try {
36
+ if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
37
+ } catch {
38
+ }
39
+ }
40
+ var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
41
+ var init_secure_files = __esm({
42
+ "src/lib/secure-files.ts"() {
43
+ "use strict";
44
+ PRIVATE_DIR_MODE = 448;
45
+ PRIVATE_FILE_MODE = 384;
46
+ }
47
+ });
48
+
11
49
  // src/lib/config.ts
12
50
  var config_exports = {};
13
51
  __export(config_exports, {
@@ -24,8 +62,8 @@ __export(config_exports, {
24
62
  migrateConfig: () => migrateConfig,
25
63
  saveConfig: () => saveConfig
26
64
  });
27
- import { readFile, writeFile, mkdir, chmod } from "fs/promises";
28
- import { readFileSync, existsSync, renameSync } from "fs";
65
+ import { readFile, writeFile } from "fs/promises";
66
+ import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
29
67
  import path from "path";
30
68
  import os from "os";
31
69
  function resolveDataDir() {
@@ -33,7 +71,7 @@ function resolveDataDir() {
33
71
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
34
72
  const newDir = path.join(os.homedir(), ".exe-os");
35
73
  const legacyDir = path.join(os.homedir(), ".exe-mem");
36
- if (!existsSync(newDir) && existsSync(legacyDir)) {
74
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
37
75
  try {
38
76
  renameSync(legacyDir, newDir);
39
77
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -96,9 +134,9 @@ function normalizeAutoUpdate(raw) {
96
134
  }
97
135
  async function loadConfig() {
98
136
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
99
- await mkdir(dir, { recursive: true });
137
+ await ensurePrivateDir(dir);
100
138
  const configPath = path.join(dir, "config.json");
101
- if (!existsSync(configPath)) {
139
+ if (!existsSync2(configPath)) {
102
140
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
103
141
  }
104
142
  const raw = await readFile(configPath, "utf-8");
@@ -111,6 +149,7 @@ async function loadConfig() {
111
149
  `);
112
150
  try {
113
151
  await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
152
+ await enforcePrivateFile(configPath);
114
153
  } catch {
115
154
  }
116
155
  }
@@ -129,7 +168,7 @@ async function loadConfig() {
129
168
  function loadConfigSync() {
130
169
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
131
170
  const configPath = path.join(dir, "config.json");
132
- if (!existsSync(configPath)) {
171
+ if (!existsSync2(configPath)) {
133
172
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
134
173
  }
135
174
  try {
@@ -147,12 +186,10 @@ function loadConfigSync() {
147
186
  }
148
187
  async function saveConfig(config) {
149
188
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
150
- await mkdir(dir, { recursive: true });
189
+ await ensurePrivateDir(dir);
151
190
  const configPath = path.join(dir, "config.json");
152
191
  await writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
153
- if (config.cloud?.apiKey) {
154
- await chmod(configPath, 384);
155
- }
192
+ await enforcePrivateFile(configPath);
156
193
  }
157
194
  async function loadConfigFrom(configPath) {
158
195
  const raw = await readFile(configPath, "utf-8");
@@ -172,6 +209,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
172
209
  var init_config = __esm({
173
210
  "src/lib/config.ts"() {
174
211
  "use strict";
212
+ init_secure_files();
175
213
  EXE_AI_DIR = resolveDataDir();
176
214
  DB_PATH = path.join(EXE_AI_DIR, "memories.db");
177
215
  MODELS_DIR = path.join(EXE_AI_DIR, "models");
@@ -314,7 +352,7 @@ var init_db_retry = __esm({
314
352
 
315
353
  // src/lib/employees.ts
316
354
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
317
- import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
355
+ import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
318
356
  import { execSync } from "child_process";
319
357
  import path2 from "path";
320
358
  import os2 from "os";
@@ -331,7 +369,7 @@ function getCoordinatorName(employees = loadEmployeesSync()) {
331
369
  return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
332
370
  }
333
371
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
334
- if (!existsSync2(employeesPath)) return [];
372
+ if (!existsSync3(employeesPath)) return [];
335
373
  try {
336
374
  return JSON.parse(readFileSync2(employeesPath, "utf-8"));
337
375
  } catch {
@@ -1279,6 +1317,7 @@ async function ensureSchema() {
1279
1317
  project TEXT NOT NULL,
1280
1318
  summary TEXT NOT NULL,
1281
1319
  task_file TEXT,
1320
+ session_scope TEXT,
1282
1321
  read INTEGER NOT NULL DEFAULT 0,
1283
1322
  created_at TEXT NOT NULL
1284
1323
  );
@@ -1287,7 +1326,7 @@ async function ensureSchema() {
1287
1326
  ON notifications(read);
1288
1327
 
1289
1328
  CREATE INDEX IF NOT EXISTS idx_notifications_agent
1290
- ON notifications(agent_id);
1329
+ ON notifications(agent_id, session_scope);
1291
1330
 
1292
1331
  CREATE INDEX IF NOT EXISTS idx_notifications_task_file
1293
1332
  ON notifications(task_file);
@@ -1325,6 +1364,7 @@ async function ensureSchema() {
1325
1364
  target_agent TEXT NOT NULL,
1326
1365
  target_project TEXT,
1327
1366
  target_device TEXT NOT NULL DEFAULT 'local',
1367
+ session_scope TEXT,
1328
1368
  content TEXT NOT NULL,
1329
1369
  priority TEXT DEFAULT 'normal',
1330
1370
  status TEXT DEFAULT 'pending',
@@ -1338,10 +1378,31 @@ async function ensureSchema() {
1338
1378
  );
1339
1379
 
1340
1380
  CREATE INDEX IF NOT EXISTS idx_messages_target
1341
- ON messages(target_agent, status);
1381
+ ON messages(target_agent, session_scope, status);
1342
1382
 
1343
1383
  CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
1344
- ON messages(target_agent, from_agent, server_seq);
1384
+ ON messages(target_agent, session_scope, from_agent, server_seq);
1385
+ `);
1386
+ try {
1387
+ await client.execute({
1388
+ sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
1389
+ args: []
1390
+ });
1391
+ } catch {
1392
+ }
1393
+ try {
1394
+ await client.execute({
1395
+ sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
1396
+ args: []
1397
+ });
1398
+ } catch {
1399
+ }
1400
+ await client.executeMultiple(`
1401
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
1402
+ ON notifications(agent_id, session_scope, read, created_at);
1403
+
1404
+ CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
1405
+ ON messages(target_agent, session_scope, status, created_at);
1345
1406
  `);
1346
1407
  try {
1347
1408
  await client.execute({
@@ -1925,6 +1986,13 @@ async function ensureSchema() {
1925
1986
  } catch {
1926
1987
  }
1927
1988
  }
1989
+ try {
1990
+ await client.execute({
1991
+ sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
1992
+ args: []
1993
+ });
1994
+ } catch {
1995
+ }
1928
1996
  }
1929
1997
  async function disposeDatabase() {
1930
1998
  if (_walCheckpointTimer) {
@@ -1964,7 +2032,7 @@ var init_database = __esm({
1964
2032
 
1965
2033
  // src/lib/keychain.ts
1966
2034
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
1967
- import { existsSync as existsSync3 } from "fs";
2035
+ import { existsSync as existsSync4 } from "fs";
1968
2036
  import path4 from "path";
1969
2037
  import os4 from "os";
1970
2038
  function getKeyDir() {
@@ -1992,7 +2060,7 @@ async function getMasterKey() {
1992
2060
  }
1993
2061
  }
1994
2062
  const keyPath = getKeyPath();
1995
- if (!existsSync3(keyPath)) {
2063
+ if (!existsSync4(keyPath)) {
1996
2064
  process.stderr.write(
1997
2065
  `[keychain] Key not found at ${keyPath} (HOME=${os4.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
1998
2066
  `
@@ -2079,6 +2147,7 @@ var shard_manager_exports = {};
2079
2147
  __export(shard_manager_exports, {
2080
2148
  disposeShards: () => disposeShards,
2081
2149
  ensureShardSchema: () => ensureShardSchema,
2150
+ getOpenShardCount: () => getOpenShardCount,
2082
2151
  getReadyShardClient: () => getReadyShardClient,
2083
2152
  getShardClient: () => getShardClient,
2084
2153
  getShardsDir: () => getShardsDir,
@@ -2088,14 +2157,17 @@ __export(shard_manager_exports, {
2088
2157
  shardExists: () => shardExists
2089
2158
  });
2090
2159
  import path5 from "path";
2091
- import { existsSync as existsSync4, mkdirSync, readdirSync } from "fs";
2160
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, readdirSync } from "fs";
2092
2161
  import { createClient as createClient2 } from "@libsql/client";
2093
2162
  function initShardManager(encryptionKey) {
2094
2163
  _encryptionKey = encryptionKey;
2095
- if (!existsSync4(SHARDS_DIR)) {
2096
- mkdirSync(SHARDS_DIR, { recursive: true });
2164
+ if (!existsSync5(SHARDS_DIR)) {
2165
+ mkdirSync2(SHARDS_DIR, { recursive: true });
2097
2166
  }
2098
2167
  _shardingEnabled = true;
2168
+ if (_evictionTimer) clearInterval(_evictionTimer);
2169
+ _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
2170
+ _evictionTimer.unref();
2099
2171
  }
2100
2172
  function isShardingEnabled() {
2101
2173
  return _shardingEnabled;
@@ -2112,21 +2184,28 @@ function getShardClient(projectName) {
2112
2184
  throw new Error(`Invalid project name for shard: "${projectName}"`);
2113
2185
  }
2114
2186
  const cached = _shards.get(safeName);
2115
- if (cached) return cached;
2187
+ if (cached) {
2188
+ _shardLastAccess.set(safeName, Date.now());
2189
+ return cached;
2190
+ }
2191
+ while (_shards.size >= MAX_OPEN_SHARDS) {
2192
+ evictLRU();
2193
+ }
2116
2194
  const dbPath = path5.join(SHARDS_DIR, `${safeName}.db`);
2117
2195
  const client = createClient2({
2118
2196
  url: `file:${dbPath}`,
2119
2197
  encryptionKey: _encryptionKey
2120
2198
  });
2121
2199
  _shards.set(safeName, client);
2200
+ _shardLastAccess.set(safeName, Date.now());
2122
2201
  return client;
2123
2202
  }
2124
2203
  function shardExists(projectName) {
2125
2204
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
2126
- return existsSync4(path5.join(SHARDS_DIR, `${safeName}.db`));
2205
+ return existsSync5(path5.join(SHARDS_DIR, `${safeName}.db`));
2127
2206
  }
2128
2207
  function listShards() {
2129
- if (!existsSync4(SHARDS_DIR)) return [];
2208
+ if (!existsSync5(SHARDS_DIR)) return [];
2130
2209
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
2131
2210
  }
2132
2211
  async function ensureShardSchema(client) {
@@ -2178,6 +2257,8 @@ async function ensureShardSchema(client) {
2178
2257
  for (const col of [
2179
2258
  "ALTER TABLE memories ADD COLUMN task_id TEXT",
2180
2259
  "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
2260
+ "ALTER TABLE memories ADD COLUMN author_device_id TEXT",
2261
+ "ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
2181
2262
  "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
2182
2263
  "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
2183
2264
  "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
@@ -2315,21 +2396,69 @@ async function getReadyShardClient(projectName) {
2315
2396
  await ensureShardSchema(client);
2316
2397
  return client;
2317
2398
  }
2399
+ function evictLRU() {
2400
+ let oldest = null;
2401
+ let oldestTime = Infinity;
2402
+ for (const [name, time] of _shardLastAccess) {
2403
+ if (time < oldestTime) {
2404
+ oldestTime = time;
2405
+ oldest = name;
2406
+ }
2407
+ }
2408
+ if (oldest) {
2409
+ const client = _shards.get(oldest);
2410
+ if (client) {
2411
+ client.close();
2412
+ }
2413
+ _shards.delete(oldest);
2414
+ _shardLastAccess.delete(oldest);
2415
+ }
2416
+ }
2417
+ function evictIdleShards() {
2418
+ const now = Date.now();
2419
+ const toEvict = [];
2420
+ for (const [name, lastAccess] of _shardLastAccess) {
2421
+ if (now - lastAccess > SHARD_IDLE_MS) {
2422
+ toEvict.push(name);
2423
+ }
2424
+ }
2425
+ for (const name of toEvict) {
2426
+ const client = _shards.get(name);
2427
+ if (client) {
2428
+ client.close();
2429
+ }
2430
+ _shards.delete(name);
2431
+ _shardLastAccess.delete(name);
2432
+ }
2433
+ }
2434
+ function getOpenShardCount() {
2435
+ return _shards.size;
2436
+ }
2318
2437
  function disposeShards() {
2438
+ if (_evictionTimer) {
2439
+ clearInterval(_evictionTimer);
2440
+ _evictionTimer = null;
2441
+ }
2319
2442
  for (const [, client] of _shards) {
2320
2443
  client.close();
2321
2444
  }
2322
2445
  _shards.clear();
2446
+ _shardLastAccess.clear();
2323
2447
  _shardingEnabled = false;
2324
2448
  _encryptionKey = null;
2325
2449
  }
2326
- var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
2450
+ var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
2327
2451
  var init_shard_manager = __esm({
2328
2452
  "src/lib/shard-manager.ts"() {
2329
2453
  "use strict";
2330
2454
  init_config();
2331
2455
  SHARDS_DIR = path5.join(EXE_AI_DIR, "shards");
2456
+ SHARD_IDLE_MS = 5 * 60 * 1e3;
2457
+ MAX_OPEN_SHARDS = 10;
2458
+ EVICTION_INTERVAL_MS = 60 * 1e3;
2332
2459
  _shards = /* @__PURE__ */ new Map();
2460
+ _shardLastAccess = /* @__PURE__ */ new Map();
2461
+ _evictionTimer = null;
2333
2462
  _encryptionKey = null;
2334
2463
  _shardingEnabled = false;
2335
2464
  }
@@ -3192,7 +3321,7 @@ __export(reranker_exports, {
3192
3321
  rerankWithScores: () => rerankWithScores
3193
3322
  });
3194
3323
  import path6 from "path";
3195
- import { existsSync as existsSync5 } from "fs";
3324
+ import { existsSync as existsSync6 } from "fs";
3196
3325
  function resetIdleTimer() {
3197
3326
  if (_idleTimer) clearTimeout(_idleTimer);
3198
3327
  _idleTimer = setTimeout(() => {
@@ -3203,7 +3332,7 @@ function resetIdleTimer() {
3203
3332
  }
3204
3333
  }
3205
3334
  function isRerankerAvailable() {
3206
- return existsSync5(path6.join(MODELS_DIR, RERANKER_MODEL_FILE));
3335
+ return existsSync6(path6.join(MODELS_DIR, RERANKER_MODEL_FILE));
3207
3336
  }
3208
3337
  function getRerankerModelPath() {
3209
3338
  return path6.join(MODELS_DIR, RERANKER_MODEL_FILE);
@@ -3214,7 +3343,7 @@ async function ensureLoaded() {
3214
3343
  return;
3215
3344
  }
3216
3345
  const modelPath = path6.join(MODELS_DIR, RERANKER_MODEL_FILE);
3217
- if (!existsSync5(modelPath)) {
3346
+ if (!existsSync6(modelPath)) {
3218
3347
  throw new Error(
3219
3348
  `Reranker model not found at ${modelPath}. Run /exe-setup to download it.`
3220
3349
  );
@@ -3310,13 +3439,50 @@ var init_reranker = __esm({
3310
3439
  }
3311
3440
  });
3312
3441
 
3442
+ // src/lib/daemon-auth.ts
3443
+ import crypto2 from "crypto";
3444
+ import path7 from "path";
3445
+ import { existsSync as existsSync7, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
3446
+ function normalizeToken(token) {
3447
+ if (!token) return null;
3448
+ const trimmed = token.trim();
3449
+ return trimmed.length > 0 ? trimmed : null;
3450
+ }
3451
+ function readDaemonToken() {
3452
+ try {
3453
+ if (!existsSync7(DAEMON_TOKEN_PATH)) return null;
3454
+ return normalizeToken(readFileSync3(DAEMON_TOKEN_PATH, "utf8"));
3455
+ } catch {
3456
+ return null;
3457
+ }
3458
+ }
3459
+ function ensureDaemonToken(seed) {
3460
+ const existing = readDaemonToken();
3461
+ if (existing) return existing;
3462
+ const token = normalizeToken(seed) ?? crypto2.randomBytes(32).toString("hex");
3463
+ ensurePrivateDirSync(EXE_AI_DIR);
3464
+ writeFileSync2(DAEMON_TOKEN_PATH, `${token}
3465
+ `, "utf8");
3466
+ enforcePrivateFileSync(DAEMON_TOKEN_PATH);
3467
+ return token;
3468
+ }
3469
+ var DAEMON_TOKEN_PATH;
3470
+ var init_daemon_auth = __esm({
3471
+ "src/lib/daemon-auth.ts"() {
3472
+ "use strict";
3473
+ init_config();
3474
+ init_secure_files();
3475
+ DAEMON_TOKEN_PATH = path7.join(EXE_AI_DIR, "exed.token");
3476
+ }
3477
+ });
3478
+
3313
3479
  // src/lib/exe-daemon-client.ts
3314
3480
  import net from "net";
3315
3481
  import os5 from "os";
3316
3482
  import { spawn } from "child_process";
3317
3483
  import { randomUUID as randomUUID2 } from "crypto";
3318
- import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
3319
- import path7 from "path";
3484
+ import { existsSync as existsSync8, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
3485
+ import path8 from "path";
3320
3486
  import { fileURLToPath } from "url";
3321
3487
  function handleData(chunk) {
3322
3488
  _buffer += chunk.toString();
@@ -3344,9 +3510,9 @@ function handleData(chunk) {
3344
3510
  }
3345
3511
  }
3346
3512
  function cleanupStaleFiles() {
3347
- if (existsSync6(PID_PATH)) {
3513
+ if (existsSync8(PID_PATH)) {
3348
3514
  try {
3349
- const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
3515
+ const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
3350
3516
  if (pid > 0) {
3351
3517
  try {
3352
3518
  process.kill(pid, 0);
@@ -3367,11 +3533,11 @@ function cleanupStaleFiles() {
3367
3533
  }
3368
3534
  }
3369
3535
  function findPackageRoot() {
3370
- let dir = path7.dirname(fileURLToPath(import.meta.url));
3371
- const { root } = path7.parse(dir);
3536
+ let dir = path8.dirname(fileURLToPath(import.meta.url));
3537
+ const { root } = path8.parse(dir);
3372
3538
  while (dir !== root) {
3373
- if (existsSync6(path7.join(dir, "package.json"))) return dir;
3374
- dir = path7.dirname(dir);
3539
+ if (existsSync8(path8.join(dir, "package.json"))) return dir;
3540
+ dir = path8.dirname(dir);
3375
3541
  }
3376
3542
  return null;
3377
3543
  }
@@ -3397,16 +3563,17 @@ function spawnDaemon() {
3397
3563
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
3398
3564
  return;
3399
3565
  }
3400
- const daemonPath = path7.join(pkgRoot, "dist", "lib", "exe-daemon.js");
3401
- if (!existsSync6(daemonPath)) {
3566
+ const daemonPath = path8.join(pkgRoot, "dist", "lib", "exe-daemon.js");
3567
+ if (!existsSync8(daemonPath)) {
3402
3568
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
3403
3569
  `);
3404
3570
  return;
3405
3571
  }
3406
3572
  const resolvedPath = daemonPath;
3573
+ const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
3407
3574
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
3408
3575
  `);
3409
- const logPath = path7.join(path7.dirname(SOCKET_PATH), "exed.log");
3576
+ const logPath = path8.join(path8.dirname(SOCKET_PATH), "exed.log");
3410
3577
  let stderrFd = "ignore";
3411
3578
  try {
3412
3579
  stderrFd = openSync(logPath, "a");
@@ -3424,7 +3591,8 @@ function spawnDaemon() {
3424
3591
  TMUX_PANE: void 0,
3425
3592
  // Prevents resolveExeSession() from scoping to one session
3426
3593
  EXE_DAEMON_SOCK: SOCKET_PATH,
3427
- EXE_DAEMON_PID: PID_PATH
3594
+ EXE_DAEMON_PID: PID_PATH,
3595
+ [DAEMON_TOKEN_ENV]: daemonToken
3428
3596
  }
3429
3597
  });
3430
3598
  child.unref();
@@ -3534,13 +3702,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
3534
3702
  return;
3535
3703
  }
3536
3704
  const id = randomUUID2();
3705
+ const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
3537
3706
  const timer = setTimeout(() => {
3538
3707
  _pending.delete(id);
3539
3708
  resolve({ error: "Request timeout" });
3540
3709
  }, timeoutMs);
3541
3710
  _pending.set(id, { resolve, timer });
3542
3711
  try {
3543
- _socket.write(JSON.stringify({ id, ...payload }) + "\n");
3712
+ _socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
3544
3713
  } catch {
3545
3714
  clearTimeout(timer);
3546
3715
  _pending.delete(id);
@@ -3569,9 +3738,9 @@ function killAndRespawnDaemon() {
3569
3738
  }
3570
3739
  try {
3571
3740
  process.stderr.write("[exed-client] Killing daemon for restart...\n");
3572
- if (existsSync6(PID_PATH)) {
3741
+ if (existsSync8(PID_PATH)) {
3573
3742
  try {
3574
- const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
3743
+ const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
3575
3744
  if (pid > 0) {
3576
3745
  try {
3577
3746
  process.kill(pid, "SIGKILL");
@@ -3688,17 +3857,19 @@ function disconnectClient() {
3688
3857
  entry.resolve({ error: "Client disconnected" });
3689
3858
  }
3690
3859
  }
3691
- var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _requestCount, _consecutiveFailures, HEALTH_CHECK_INTERVAL, MAX_RETRIES_BEFORE_RESTART, RETRY_DELAYS_MS, MIN_DAEMON_AGE_MS, _pending, MAX_BUFFER;
3860
+ var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, DAEMON_TOKEN_ENV, _socket, _connected, _buffer, _requestCount, _consecutiveFailures, HEALTH_CHECK_INTERVAL, MAX_RETRIES_BEFORE_RESTART, RETRY_DELAYS_MS, MIN_DAEMON_AGE_MS, _pending, MAX_BUFFER;
3692
3861
  var init_exe_daemon_client = __esm({
3693
3862
  "src/lib/exe-daemon-client.ts"() {
3694
3863
  "use strict";
3695
3864
  init_config();
3696
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path7.join(EXE_AI_DIR, "exed.sock");
3697
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path7.join(EXE_AI_DIR, "exed.pid");
3698
- SPAWN_LOCK_PATH = path7.join(EXE_AI_DIR, "exed-spawn.lock");
3865
+ init_daemon_auth();
3866
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path8.join(EXE_AI_DIR, "exed.sock");
3867
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path8.join(EXE_AI_DIR, "exed.pid");
3868
+ SPAWN_LOCK_PATH = path8.join(EXE_AI_DIR, "exed-spawn.lock");
3699
3869
  SPAWN_LOCK_STALE_MS = 3e4;
3700
3870
  CONNECT_TIMEOUT_MS = 15e3;
3701
3871
  REQUEST_TIMEOUT_MS = 3e4;
3872
+ DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
3702
3873
  _socket = null;
3703
3874
  _connected = false;
3704
3875
  _buffer = "";
@@ -3750,10 +3921,10 @@ async function disposeEmbedder() {
3750
3921
  async function embedDirect(text) {
3751
3922
  const llamaCpp = await import("node-llama-cpp");
3752
3923
  const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
3753
- const { existsSync: existsSync8 } = await import("fs");
3754
- const path11 = await import("path");
3755
- const modelPath = path11.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
3756
- if (!existsSync8(modelPath)) {
3924
+ const { existsSync: existsSync10 } = await import("fs");
3925
+ const path12 = await import("path");
3926
+ const modelPath = path12.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
3927
+ if (!existsSync10(modelPath)) {
3757
3928
  throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
3758
3929
  }
3759
3930
  const llama = await llamaCpp.getLlama();
@@ -3788,7 +3959,7 @@ __export(project_name_exports, {
3788
3959
  getProjectName: () => getProjectName
3789
3960
  });
3790
3961
  import { execSync as execSync2 } from "child_process";
3791
- import path8 from "path";
3962
+ import path9 from "path";
3792
3963
  function getProjectName(cwd) {
3793
3964
  const dir = cwd ?? process.cwd();
3794
3965
  if (_cached && _cachedCwd === dir) return _cached;
@@ -3801,7 +3972,7 @@ function getProjectName(cwd) {
3801
3972
  timeout: 2e3,
3802
3973
  stdio: ["pipe", "pipe", "pipe"]
3803
3974
  }).trim();
3804
- repoRoot = path8.dirname(gitCommonDir);
3975
+ repoRoot = path9.dirname(gitCommonDir);
3805
3976
  } catch {
3806
3977
  repoRoot = execSync2("git rev-parse --show-toplevel", {
3807
3978
  cwd: dir,
@@ -3810,11 +3981,11 @@ function getProjectName(cwd) {
3810
3981
  stdio: ["pipe", "pipe", "pipe"]
3811
3982
  }).trim();
3812
3983
  }
3813
- _cached = path8.basename(repoRoot);
3984
+ _cached = path9.basename(repoRoot);
3814
3985
  _cachedCwd = dir;
3815
3986
  return _cached;
3816
3987
  } catch {
3817
- _cached = path8.basename(dir);
3988
+ _cached = path9.basename(dir);
3818
3989
  _cachedCwd = dir;
3819
3990
  return _cached;
3820
3991
  }
@@ -3838,9 +4009,9 @@ __export(file_grep_exports, {
3838
4009
  grepProjectFiles: () => grepProjectFiles
3839
4010
  });
3840
4011
  import { execSync as execSync3 } from "child_process";
3841
- import { readFileSync as readFileSync4, readdirSync as readdirSync2, statSync as statSync2, existsSync as existsSync7 } from "fs";
3842
- import path9 from "path";
3843
- import crypto2 from "crypto";
4012
+ import { readFileSync as readFileSync5, readdirSync as readdirSync2, statSync as statSync2, existsSync as existsSync9 } from "fs";
4013
+ import path10 from "path";
4014
+ import crypto3 from "crypto";
3844
4015
  function hasRipgrep() {
3845
4016
  if (_hasRg === null) {
3846
4017
  try {
@@ -3873,13 +4044,13 @@ async function grepProjectFiles(query, projectRoot, options) {
3873
4044
  const chunkCtx = getChunkContext(hit.filePath, hit.lineNumber);
3874
4045
  const prefix = chunkCtx ? `[file: ${hit.filePath}:${hit.lineNumber} in ${chunkCtx}]` : `[file: ${hit.filePath}:${hit.lineNumber}]`;
3875
4046
  return {
3876
- id: crypto2.createHash("sha256").update(`${hit.filePath}:${hit.lineNumber}`).digest("hex").slice(0, 36),
4047
+ id: crypto3.createHash("sha256").update(`${hit.filePath}:${hit.lineNumber}`).digest("hex").slice(0, 36),
3877
4048
  agent_id: "project",
3878
4049
  agent_role: "file",
3879
4050
  session_id: "file-grep",
3880
4051
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3881
4052
  tool_name: "file_grep",
3882
- project_name: path9.basename(projectRoot),
4053
+ project_name: path10.basename(projectRoot),
3883
4054
  has_error: false,
3884
4055
  raw_text: `${prefix} ${buildSnippet(hit, projectRoot)}`,
3885
4056
  vector: null,
@@ -3891,7 +4062,7 @@ function getChunkContext(filePath, lineNumber) {
3891
4062
  try {
3892
4063
  const ext = filePath.split(".").pop()?.toLowerCase();
3893
4064
  if (ext !== "ts" && ext !== "tsx" && ext !== "js" && ext !== "jsx") return "";
3894
- const source = readFileSync4(filePath, "utf8");
4065
+ const source = readFileSync5(filePath, "utf8");
3895
4066
  const lines = source.split("\n");
3896
4067
  for (let i = Math.min(lineNumber - 1, lines.length - 1); i >= 0; i--) {
3897
4068
  const line = lines[i];
@@ -3953,11 +4124,11 @@ function grepWithNodeFs(pattern, projectRoot, patterns) {
3953
4124
  const files = collectFiles(projectRoot, patterns ?? DEFAULT_PATTERNS);
3954
4125
  const hits = [];
3955
4126
  for (const filePath of files.slice(0, MAX_FILES)) {
3956
- const absPath = path9.join(projectRoot, filePath);
4127
+ const absPath = path10.join(projectRoot, filePath);
3957
4128
  try {
3958
4129
  const stat = statSync2(absPath);
3959
4130
  if (stat.size > MAX_FILE_SIZE) continue;
3960
- const content = readFileSync4(absPath, "utf8");
4131
+ const content = readFileSync5(absPath, "utf8");
3961
4132
  const lines = content.split("\n");
3962
4133
  const matches = content.match(regex);
3963
4134
  if (!matches || matches.length === 0) continue;
@@ -3980,15 +4151,15 @@ function collectFiles(root, patterns) {
3980
4151
  const files = [];
3981
4152
  function walk(dir, relative) {
3982
4153
  if (files.length >= MAX_FILES) return;
3983
- const basename = path9.basename(dir);
4154
+ const basename = path10.basename(dir);
3984
4155
  if (EXCLUDE_DIRS.includes(basename)) return;
3985
4156
  try {
3986
4157
  const entries = readdirSync2(dir, { withFileTypes: true });
3987
4158
  for (const entry of entries) {
3988
4159
  if (files.length >= MAX_FILES) return;
3989
- const rel = path9.join(relative, entry.name);
4160
+ const rel = path10.join(relative, entry.name);
3990
4161
  if (entry.isDirectory()) {
3991
- walk(path9.join(dir, entry.name), rel);
4162
+ walk(path10.join(dir, entry.name), rel);
3992
4163
  } else if (entry.isFile()) {
3993
4164
  for (const pat of patterns) {
3994
4165
  if (matchGlob(rel, pat)) {
@@ -4020,7 +4191,7 @@ function matchGlob(filePath, pattern) {
4020
4191
  if (slashIdx !== -1) {
4021
4192
  const dir = pattern.slice(0, slashIdx);
4022
4193
  const ext2 = pattern.slice(slashIdx + 1).replace("*", "");
4023
- const fileDir = path9.dirname(filePath);
4194
+ const fileDir = path10.dirname(filePath);
4024
4195
  return fileDir === dir && filePath.endsWith(ext2);
4025
4196
  }
4026
4197
  const ext = pattern.replace("*", "");
@@ -4028,9 +4199,9 @@ function matchGlob(filePath, pattern) {
4028
4199
  }
4029
4200
  function buildSnippet(hit, projectRoot) {
4030
4201
  try {
4031
- const absPath = path9.join(projectRoot, hit.filePath);
4032
- if (!existsSync7(absPath)) return hit.matchLine;
4033
- const lines = readFileSync4(absPath, "utf8").split("\n");
4202
+ const absPath = path10.join(projectRoot, hit.filePath);
4203
+ if (!existsSync9(absPath)) return hit.matchLine;
4204
+ const lines = readFileSync5(absPath, "utf8").split("\n");
4034
4205
  const start = Math.max(0, hit.lineNumber - 3);
4035
4206
  const end = Math.min(lines.length, hit.lineNumber + 2);
4036
4207
  return lines.slice(start, end).join("\n").slice(0, 500);
@@ -5274,9 +5445,9 @@ init_database();
5274
5445
 
5275
5446
  // src/lib/active-agent.ts
5276
5447
  init_config();
5277
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, unlinkSync as unlinkSync3, readdirSync as readdirSync3 } from "fs";
5448
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, unlinkSync as unlinkSync3, readdirSync as readdirSync3 } from "fs";
5278
5449
  import { execSync as execSync5 } from "child_process";
5279
- import path10 from "path";
5450
+ import path11 from "path";
5280
5451
 
5281
5452
  // src/lib/session-key.ts
5282
5453
  import { execSync as execSync4 } from "child_process";
@@ -5342,7 +5513,7 @@ function getSessionKey() {
5342
5513
 
5343
5514
  // src/lib/active-agent.ts
5344
5515
  init_employees();
5345
- var CACHE_DIR = path10.join(EXE_AI_DIR, "session-cache");
5516
+ var CACHE_DIR = path11.join(EXE_AI_DIR, "session-cache");
5346
5517
  var STALE_MS = 24 * 60 * 60 * 1e3;
5347
5518
  function isNameWithOptionalInstance(candidate, baseName) {
5348
5519
  if (candidate === baseName) return true;
@@ -5387,12 +5558,12 @@ function resolveActiveAgentFromTmuxSession(sessionName) {
5387
5558
  return null;
5388
5559
  }
5389
5560
  function getMarkerPath() {
5390
- return path10.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
5561
+ return path11.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
5391
5562
  }
5392
5563
  function getActiveAgent() {
5393
5564
  try {
5394
5565
  const markerPath = getMarkerPath();
5395
- const raw = readFileSync5(markerPath, "utf8");
5566
+ const raw = readFileSync6(markerPath, "utf8");
5396
5567
  const data = JSON.parse(raw);
5397
5568
  if (data.agentId) {
5398
5569
  if (data.startedAt) {