@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
@@ -73,6 +73,44 @@ var init_db_retry = __esm({
73
73
  }
74
74
  });
75
75
 
76
+ // src/lib/secure-files.ts
77
+ import { chmodSync, existsSync, mkdirSync } from "fs";
78
+ import { chmod, mkdir } from "fs/promises";
79
+ async function ensurePrivateDir(dirPath) {
80
+ await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
81
+ try {
82
+ await chmod(dirPath, PRIVATE_DIR_MODE);
83
+ } catch {
84
+ }
85
+ }
86
+ function ensurePrivateDirSync(dirPath) {
87
+ mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
88
+ try {
89
+ chmodSync(dirPath, PRIVATE_DIR_MODE);
90
+ } catch {
91
+ }
92
+ }
93
+ async function enforcePrivateFile(filePath) {
94
+ try {
95
+ await chmod(filePath, PRIVATE_FILE_MODE);
96
+ } catch {
97
+ }
98
+ }
99
+ function enforcePrivateFileSync(filePath) {
100
+ try {
101
+ if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
102
+ } catch {
103
+ }
104
+ }
105
+ var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
106
+ var init_secure_files = __esm({
107
+ "src/lib/secure-files.ts"() {
108
+ "use strict";
109
+ PRIVATE_DIR_MODE = 448;
110
+ PRIVATE_FILE_MODE = 384;
111
+ }
112
+ });
113
+
76
114
  // src/lib/config.ts
77
115
  var config_exports = {};
78
116
  __export(config_exports, {
@@ -89,8 +127,8 @@ __export(config_exports, {
89
127
  migrateConfig: () => migrateConfig,
90
128
  saveConfig: () => saveConfig
91
129
  });
92
- import { readFile, writeFile, mkdir, chmod } from "fs/promises";
93
- import { readFileSync, existsSync, renameSync } from "fs";
130
+ import { readFile, writeFile } from "fs/promises";
131
+ import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
94
132
  import path from "path";
95
133
  import os from "os";
96
134
  function resolveDataDir() {
@@ -98,7 +136,7 @@ function resolveDataDir() {
98
136
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
99
137
  const newDir = path.join(os.homedir(), ".exe-os");
100
138
  const legacyDir = path.join(os.homedir(), ".exe-mem");
101
- if (!existsSync(newDir) && existsSync(legacyDir)) {
139
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
102
140
  try {
103
141
  renameSync(legacyDir, newDir);
104
142
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -161,9 +199,9 @@ function normalizeAutoUpdate(raw) {
161
199
  }
162
200
  async function loadConfig() {
163
201
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
164
- await mkdir(dir, { recursive: true });
202
+ await ensurePrivateDir(dir);
165
203
  const configPath = path.join(dir, "config.json");
166
- if (!existsSync(configPath)) {
204
+ if (!existsSync2(configPath)) {
167
205
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
168
206
  }
169
207
  const raw = await readFile(configPath, "utf-8");
@@ -176,6 +214,7 @@ async function loadConfig() {
176
214
  `);
177
215
  try {
178
216
  await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
217
+ await enforcePrivateFile(configPath);
179
218
  } catch {
180
219
  }
181
220
  }
@@ -194,7 +233,7 @@ async function loadConfig() {
194
233
  function loadConfigSync() {
195
234
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
196
235
  const configPath = path.join(dir, "config.json");
197
- if (!existsSync(configPath)) {
236
+ if (!existsSync2(configPath)) {
198
237
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
199
238
  }
200
239
  try {
@@ -212,12 +251,10 @@ function loadConfigSync() {
212
251
  }
213
252
  async function saveConfig(config) {
214
253
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
215
- await mkdir(dir, { recursive: true });
254
+ await ensurePrivateDir(dir);
216
255
  const configPath = path.join(dir, "config.json");
217
256
  await writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
218
- if (config.cloud?.apiKey) {
219
- await chmod(configPath, 384);
220
- }
257
+ await enforcePrivateFile(configPath);
221
258
  }
222
259
  async function loadConfigFrom(configPath) {
223
260
  const raw = await readFile(configPath, "utf-8");
@@ -237,6 +274,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
237
274
  var init_config = __esm({
238
275
  "src/lib/config.ts"() {
239
276
  "use strict";
277
+ init_secure_files();
240
278
  EXE_AI_DIR = resolveDataDir();
241
279
  DB_PATH = path.join(EXE_AI_DIR, "memories.db");
242
280
  MODELS_DIR = path.join(EXE_AI_DIR, "models");
@@ -315,7 +353,7 @@ var init_config = __esm({
315
353
 
316
354
  // src/lib/employees.ts
317
355
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
318
- import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
356
+ import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
319
357
  import { execSync } from "child_process";
320
358
  import path2 from "path";
321
359
  import os2 from "os";
@@ -332,7 +370,7 @@ function getCoordinatorName(employees = loadEmployeesSync()) {
332
370
  return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
333
371
  }
334
372
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
335
- if (!existsSync2(employeesPath)) return [];
373
+ if (!existsSync3(employeesPath)) return [];
336
374
  try {
337
375
  return JSON.parse(readFileSync2(employeesPath, "utf-8"));
338
376
  } catch {
@@ -1277,6 +1315,7 @@ async function ensureSchema() {
1277
1315
  project TEXT NOT NULL,
1278
1316
  summary TEXT NOT NULL,
1279
1317
  task_file TEXT,
1318
+ session_scope TEXT,
1280
1319
  read INTEGER NOT NULL DEFAULT 0,
1281
1320
  created_at TEXT NOT NULL
1282
1321
  );
@@ -1285,7 +1324,7 @@ async function ensureSchema() {
1285
1324
  ON notifications(read);
1286
1325
 
1287
1326
  CREATE INDEX IF NOT EXISTS idx_notifications_agent
1288
- ON notifications(agent_id);
1327
+ ON notifications(agent_id, session_scope);
1289
1328
 
1290
1329
  CREATE INDEX IF NOT EXISTS idx_notifications_task_file
1291
1330
  ON notifications(task_file);
@@ -1323,6 +1362,7 @@ async function ensureSchema() {
1323
1362
  target_agent TEXT NOT NULL,
1324
1363
  target_project TEXT,
1325
1364
  target_device TEXT NOT NULL DEFAULT 'local',
1365
+ session_scope TEXT,
1326
1366
  content TEXT NOT NULL,
1327
1367
  priority TEXT DEFAULT 'normal',
1328
1368
  status TEXT DEFAULT 'pending',
@@ -1336,10 +1376,31 @@ async function ensureSchema() {
1336
1376
  );
1337
1377
 
1338
1378
  CREATE INDEX IF NOT EXISTS idx_messages_target
1339
- ON messages(target_agent, status);
1379
+ ON messages(target_agent, session_scope, status);
1340
1380
 
1341
1381
  CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
1342
- ON messages(target_agent, from_agent, server_seq);
1382
+ ON messages(target_agent, session_scope, from_agent, server_seq);
1383
+ `);
1384
+ try {
1385
+ await client.execute({
1386
+ sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
1387
+ args: []
1388
+ });
1389
+ } catch {
1390
+ }
1391
+ try {
1392
+ await client.execute({
1393
+ sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
1394
+ args: []
1395
+ });
1396
+ } catch {
1397
+ }
1398
+ await client.executeMultiple(`
1399
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
1400
+ ON notifications(agent_id, session_scope, read, created_at);
1401
+
1402
+ CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
1403
+ ON messages(target_agent, session_scope, status, created_at);
1343
1404
  `);
1344
1405
  try {
1345
1406
  await client.execute({
@@ -1923,6 +1984,13 @@ async function ensureSchema() {
1923
1984
  } catch {
1924
1985
  }
1925
1986
  }
1987
+ try {
1988
+ await client.execute({
1989
+ sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
1990
+ args: []
1991
+ });
1992
+ } catch {
1993
+ }
1926
1994
  }
1927
1995
  async function disposeDatabase() {
1928
1996
  if (_walCheckpointTimer) {
@@ -1962,7 +2030,7 @@ var init_database = __esm({
1962
2030
 
1963
2031
  // src/lib/keychain.ts
1964
2032
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
1965
- import { existsSync as existsSync3 } from "fs";
2033
+ import { existsSync as existsSync4 } from "fs";
1966
2034
  import path4 from "path";
1967
2035
  import os4 from "os";
1968
2036
  function getKeyDir() {
@@ -1990,7 +2058,7 @@ async function getMasterKey() {
1990
2058
  }
1991
2059
  }
1992
2060
  const keyPath = getKeyPath();
1993
- if (!existsSync3(keyPath)) {
2061
+ if (!existsSync4(keyPath)) {
1994
2062
  process.stderr.write(
1995
2063
  `[keychain] Key not found at ${keyPath} (HOME=${os4.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
1996
2064
  `
@@ -2077,6 +2145,7 @@ var shard_manager_exports = {};
2077
2145
  __export(shard_manager_exports, {
2078
2146
  disposeShards: () => disposeShards,
2079
2147
  ensureShardSchema: () => ensureShardSchema,
2148
+ getOpenShardCount: () => getOpenShardCount,
2080
2149
  getReadyShardClient: () => getReadyShardClient,
2081
2150
  getShardClient: () => getShardClient,
2082
2151
  getShardsDir: () => getShardsDir,
@@ -2086,14 +2155,17 @@ __export(shard_manager_exports, {
2086
2155
  shardExists: () => shardExists
2087
2156
  });
2088
2157
  import path5 from "path";
2089
- import { existsSync as existsSync4, mkdirSync, readdirSync } from "fs";
2158
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, readdirSync } from "fs";
2090
2159
  import { createClient as createClient2 } from "@libsql/client";
2091
2160
  function initShardManager(encryptionKey) {
2092
2161
  _encryptionKey = encryptionKey;
2093
- if (!existsSync4(SHARDS_DIR)) {
2094
- mkdirSync(SHARDS_DIR, { recursive: true });
2162
+ if (!existsSync5(SHARDS_DIR)) {
2163
+ mkdirSync2(SHARDS_DIR, { recursive: true });
2095
2164
  }
2096
2165
  _shardingEnabled = true;
2166
+ if (_evictionTimer) clearInterval(_evictionTimer);
2167
+ _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
2168
+ _evictionTimer.unref();
2097
2169
  }
2098
2170
  function isShardingEnabled() {
2099
2171
  return _shardingEnabled;
@@ -2110,21 +2182,28 @@ function getShardClient(projectName) {
2110
2182
  throw new Error(`Invalid project name for shard: "${projectName}"`);
2111
2183
  }
2112
2184
  const cached = _shards.get(safeName);
2113
- if (cached) return cached;
2185
+ if (cached) {
2186
+ _shardLastAccess.set(safeName, Date.now());
2187
+ return cached;
2188
+ }
2189
+ while (_shards.size >= MAX_OPEN_SHARDS) {
2190
+ evictLRU();
2191
+ }
2114
2192
  const dbPath = path5.join(SHARDS_DIR, `${safeName}.db`);
2115
2193
  const client = createClient2({
2116
2194
  url: `file:${dbPath}`,
2117
2195
  encryptionKey: _encryptionKey
2118
2196
  });
2119
2197
  _shards.set(safeName, client);
2198
+ _shardLastAccess.set(safeName, Date.now());
2120
2199
  return client;
2121
2200
  }
2122
2201
  function shardExists(projectName) {
2123
2202
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
2124
- return existsSync4(path5.join(SHARDS_DIR, `${safeName}.db`));
2203
+ return existsSync5(path5.join(SHARDS_DIR, `${safeName}.db`));
2125
2204
  }
2126
2205
  function listShards() {
2127
- if (!existsSync4(SHARDS_DIR)) return [];
2206
+ if (!existsSync5(SHARDS_DIR)) return [];
2128
2207
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
2129
2208
  }
2130
2209
  async function ensureShardSchema(client) {
@@ -2176,6 +2255,8 @@ async function ensureShardSchema(client) {
2176
2255
  for (const col of [
2177
2256
  "ALTER TABLE memories ADD COLUMN task_id TEXT",
2178
2257
  "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
2258
+ "ALTER TABLE memories ADD COLUMN author_device_id TEXT",
2259
+ "ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
2179
2260
  "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
2180
2261
  "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
2181
2262
  "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
@@ -2313,21 +2394,69 @@ async function getReadyShardClient(projectName) {
2313
2394
  await ensureShardSchema(client);
2314
2395
  return client;
2315
2396
  }
2397
+ function evictLRU() {
2398
+ let oldest = null;
2399
+ let oldestTime = Infinity;
2400
+ for (const [name, time] of _shardLastAccess) {
2401
+ if (time < oldestTime) {
2402
+ oldestTime = time;
2403
+ oldest = name;
2404
+ }
2405
+ }
2406
+ if (oldest) {
2407
+ const client = _shards.get(oldest);
2408
+ if (client) {
2409
+ client.close();
2410
+ }
2411
+ _shards.delete(oldest);
2412
+ _shardLastAccess.delete(oldest);
2413
+ }
2414
+ }
2415
+ function evictIdleShards() {
2416
+ const now = Date.now();
2417
+ const toEvict = [];
2418
+ for (const [name, lastAccess] of _shardLastAccess) {
2419
+ if (now - lastAccess > SHARD_IDLE_MS) {
2420
+ toEvict.push(name);
2421
+ }
2422
+ }
2423
+ for (const name of toEvict) {
2424
+ const client = _shards.get(name);
2425
+ if (client) {
2426
+ client.close();
2427
+ }
2428
+ _shards.delete(name);
2429
+ _shardLastAccess.delete(name);
2430
+ }
2431
+ }
2432
+ function getOpenShardCount() {
2433
+ return _shards.size;
2434
+ }
2316
2435
  function disposeShards() {
2436
+ if (_evictionTimer) {
2437
+ clearInterval(_evictionTimer);
2438
+ _evictionTimer = null;
2439
+ }
2317
2440
  for (const [, client] of _shards) {
2318
2441
  client.close();
2319
2442
  }
2320
2443
  _shards.clear();
2444
+ _shardLastAccess.clear();
2321
2445
  _shardingEnabled = false;
2322
2446
  _encryptionKey = null;
2323
2447
  }
2324
- var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
2448
+ var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
2325
2449
  var init_shard_manager = __esm({
2326
2450
  "src/lib/shard-manager.ts"() {
2327
2451
  "use strict";
2328
2452
  init_config();
2329
2453
  SHARDS_DIR = path5.join(EXE_AI_DIR, "shards");
2454
+ SHARD_IDLE_MS = 5 * 60 * 1e3;
2455
+ MAX_OPEN_SHARDS = 10;
2456
+ EVICTION_INTERVAL_MS = 60 * 1e3;
2330
2457
  _shards = /* @__PURE__ */ new Map();
2458
+ _shardLastAccess = /* @__PURE__ */ new Map();
2459
+ _evictionTimer = null;
2331
2460
  _encryptionKey = null;
2332
2461
  _shardingEnabled = false;
2333
2462
  }
@@ -3190,7 +3319,7 @@ __export(reranker_exports, {
3190
3319
  rerankWithScores: () => rerankWithScores
3191
3320
  });
3192
3321
  import path6 from "path";
3193
- import { existsSync as existsSync5 } from "fs";
3322
+ import { existsSync as existsSync6 } from "fs";
3194
3323
  function resetIdleTimer() {
3195
3324
  if (_idleTimer) clearTimeout(_idleTimer);
3196
3325
  _idleTimer = setTimeout(() => {
@@ -3201,7 +3330,7 @@ function resetIdleTimer() {
3201
3330
  }
3202
3331
  }
3203
3332
  function isRerankerAvailable() {
3204
- return existsSync5(path6.join(MODELS_DIR, RERANKER_MODEL_FILE));
3333
+ return existsSync6(path6.join(MODELS_DIR, RERANKER_MODEL_FILE));
3205
3334
  }
3206
3335
  function getRerankerModelPath() {
3207
3336
  return path6.join(MODELS_DIR, RERANKER_MODEL_FILE);
@@ -3212,7 +3341,7 @@ async function ensureLoaded() {
3212
3341
  return;
3213
3342
  }
3214
3343
  const modelPath = path6.join(MODELS_DIR, RERANKER_MODEL_FILE);
3215
- if (!existsSync5(modelPath)) {
3344
+ if (!existsSync6(modelPath)) {
3216
3345
  throw new Error(
3217
3346
  `Reranker model not found at ${modelPath}. Run /exe-setup to download it.`
3218
3347
  );
@@ -3308,13 +3437,50 @@ var init_reranker = __esm({
3308
3437
  }
3309
3438
  });
3310
3439
 
3440
+ // src/lib/daemon-auth.ts
3441
+ import crypto from "crypto";
3442
+ import path7 from "path";
3443
+ import { existsSync as existsSync7, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
3444
+ function normalizeToken(token) {
3445
+ if (!token) return null;
3446
+ const trimmed = token.trim();
3447
+ return trimmed.length > 0 ? trimmed : null;
3448
+ }
3449
+ function readDaemonToken() {
3450
+ try {
3451
+ if (!existsSync7(DAEMON_TOKEN_PATH)) return null;
3452
+ return normalizeToken(readFileSync3(DAEMON_TOKEN_PATH, "utf8"));
3453
+ } catch {
3454
+ return null;
3455
+ }
3456
+ }
3457
+ function ensureDaemonToken(seed) {
3458
+ const existing = readDaemonToken();
3459
+ if (existing) return existing;
3460
+ const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
3461
+ ensurePrivateDirSync(EXE_AI_DIR);
3462
+ writeFileSync2(DAEMON_TOKEN_PATH, `${token}
3463
+ `, "utf8");
3464
+ enforcePrivateFileSync(DAEMON_TOKEN_PATH);
3465
+ return token;
3466
+ }
3467
+ var DAEMON_TOKEN_PATH;
3468
+ var init_daemon_auth = __esm({
3469
+ "src/lib/daemon-auth.ts"() {
3470
+ "use strict";
3471
+ init_config();
3472
+ init_secure_files();
3473
+ DAEMON_TOKEN_PATH = path7.join(EXE_AI_DIR, "exed.token");
3474
+ }
3475
+ });
3476
+
3311
3477
  // src/lib/exe-daemon-client.ts
3312
3478
  import net from "net";
3313
3479
  import os5 from "os";
3314
3480
  import { spawn } from "child_process";
3315
3481
  import { randomUUID as randomUUID2 } from "crypto";
3316
- import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
3317
- import path7 from "path";
3482
+ import { existsSync as existsSync8, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
3483
+ import path8 from "path";
3318
3484
  import { fileURLToPath } from "url";
3319
3485
  function handleData(chunk) {
3320
3486
  _buffer += chunk.toString();
@@ -3342,9 +3508,9 @@ function handleData(chunk) {
3342
3508
  }
3343
3509
  }
3344
3510
  function cleanupStaleFiles() {
3345
- if (existsSync6(PID_PATH)) {
3511
+ if (existsSync8(PID_PATH)) {
3346
3512
  try {
3347
- const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
3513
+ const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
3348
3514
  if (pid > 0) {
3349
3515
  try {
3350
3516
  process.kill(pid, 0);
@@ -3365,11 +3531,11 @@ function cleanupStaleFiles() {
3365
3531
  }
3366
3532
  }
3367
3533
  function findPackageRoot() {
3368
- let dir = path7.dirname(fileURLToPath(import.meta.url));
3369
- const { root } = path7.parse(dir);
3534
+ let dir = path8.dirname(fileURLToPath(import.meta.url));
3535
+ const { root } = path8.parse(dir);
3370
3536
  while (dir !== root) {
3371
- if (existsSync6(path7.join(dir, "package.json"))) return dir;
3372
- dir = path7.dirname(dir);
3537
+ if (existsSync8(path8.join(dir, "package.json"))) return dir;
3538
+ dir = path8.dirname(dir);
3373
3539
  }
3374
3540
  return null;
3375
3541
  }
@@ -3395,16 +3561,17 @@ function spawnDaemon() {
3395
3561
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
3396
3562
  return;
3397
3563
  }
3398
- const daemonPath = path7.join(pkgRoot, "dist", "lib", "exe-daemon.js");
3399
- if (!existsSync6(daemonPath)) {
3564
+ const daemonPath = path8.join(pkgRoot, "dist", "lib", "exe-daemon.js");
3565
+ if (!existsSync8(daemonPath)) {
3400
3566
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
3401
3567
  `);
3402
3568
  return;
3403
3569
  }
3404
3570
  const resolvedPath = daemonPath;
3571
+ const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
3405
3572
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
3406
3573
  `);
3407
- const logPath = path7.join(path7.dirname(SOCKET_PATH), "exed.log");
3574
+ const logPath = path8.join(path8.dirname(SOCKET_PATH), "exed.log");
3408
3575
  let stderrFd = "ignore";
3409
3576
  try {
3410
3577
  stderrFd = openSync(logPath, "a");
@@ -3422,7 +3589,8 @@ function spawnDaemon() {
3422
3589
  TMUX_PANE: void 0,
3423
3590
  // Prevents resolveExeSession() from scoping to one session
3424
3591
  EXE_DAEMON_SOCK: SOCKET_PATH,
3425
- EXE_DAEMON_PID: PID_PATH
3592
+ EXE_DAEMON_PID: PID_PATH,
3593
+ [DAEMON_TOKEN_ENV]: daemonToken
3426
3594
  }
3427
3595
  });
3428
3596
  child.unref();
@@ -3532,13 +3700,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
3532
3700
  return;
3533
3701
  }
3534
3702
  const id = randomUUID2();
3703
+ const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
3535
3704
  const timer = setTimeout(() => {
3536
3705
  _pending.delete(id);
3537
3706
  resolve({ error: "Request timeout" });
3538
3707
  }, timeoutMs);
3539
3708
  _pending.set(id, { resolve, timer });
3540
3709
  try {
3541
- _socket.write(JSON.stringify({ id, ...payload }) + "\n");
3710
+ _socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
3542
3711
  } catch {
3543
3712
  clearTimeout(timer);
3544
3713
  _pending.delete(id);
@@ -3567,9 +3736,9 @@ function killAndRespawnDaemon() {
3567
3736
  }
3568
3737
  try {
3569
3738
  process.stderr.write("[exed-client] Killing daemon for restart...\n");
3570
- if (existsSync6(PID_PATH)) {
3739
+ if (existsSync8(PID_PATH)) {
3571
3740
  try {
3572
- const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
3741
+ const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
3573
3742
  if (pid > 0) {
3574
3743
  try {
3575
3744
  process.kill(pid, "SIGKILL");
@@ -3686,17 +3855,19 @@ function disconnectClient() {
3686
3855
  entry.resolve({ error: "Client disconnected" });
3687
3856
  }
3688
3857
  }
3689
- 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;
3858
+ 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;
3690
3859
  var init_exe_daemon_client = __esm({
3691
3860
  "src/lib/exe-daemon-client.ts"() {
3692
3861
  "use strict";
3693
3862
  init_config();
3694
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path7.join(EXE_AI_DIR, "exed.sock");
3695
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path7.join(EXE_AI_DIR, "exed.pid");
3696
- SPAWN_LOCK_PATH = path7.join(EXE_AI_DIR, "exed-spawn.lock");
3863
+ init_daemon_auth();
3864
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path8.join(EXE_AI_DIR, "exed.sock");
3865
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path8.join(EXE_AI_DIR, "exed.pid");
3866
+ SPAWN_LOCK_PATH = path8.join(EXE_AI_DIR, "exed-spawn.lock");
3697
3867
  SPAWN_LOCK_STALE_MS = 3e4;
3698
3868
  CONNECT_TIMEOUT_MS = 15e3;
3699
3869
  REQUEST_TIMEOUT_MS = 3e4;
3870
+ DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
3700
3871
  _socket = null;
3701
3872
  _connected = false;
3702
3873
  _buffer = "";
@@ -3748,10 +3919,10 @@ async function disposeEmbedder() {
3748
3919
  async function embedDirect(text) {
3749
3920
  const llamaCpp = await import("node-llama-cpp");
3750
3921
  const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
3751
- const { existsSync: existsSync8 } = await import("fs");
3752
- const path10 = await import("path");
3753
- const modelPath = path10.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
3754
- if (!existsSync8(modelPath)) {
3922
+ const { existsSync: existsSync10 } = await import("fs");
3923
+ const path11 = await import("path");
3924
+ const modelPath = path11.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
3925
+ if (!existsSync10(modelPath)) {
3755
3926
  throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
3756
3927
  }
3757
3928
  const llama = await llamaCpp.getLlama();
@@ -3786,7 +3957,7 @@ __export(project_name_exports, {
3786
3957
  getProjectName: () => getProjectName
3787
3958
  });
3788
3959
  import { execSync as execSync2 } from "child_process";
3789
- import path8 from "path";
3960
+ import path9 from "path";
3790
3961
  function getProjectName(cwd) {
3791
3962
  const dir = cwd ?? process.cwd();
3792
3963
  if (_cached && _cachedCwd === dir) return _cached;
@@ -3799,7 +3970,7 @@ function getProjectName(cwd) {
3799
3970
  timeout: 2e3,
3800
3971
  stdio: ["pipe", "pipe", "pipe"]
3801
3972
  }).trim();
3802
- repoRoot = path8.dirname(gitCommonDir);
3973
+ repoRoot = path9.dirname(gitCommonDir);
3803
3974
  } catch {
3804
3975
  repoRoot = execSync2("git rev-parse --show-toplevel", {
3805
3976
  cwd: dir,
@@ -3808,11 +3979,11 @@ function getProjectName(cwd) {
3808
3979
  stdio: ["pipe", "pipe", "pipe"]
3809
3980
  }).trim();
3810
3981
  }
3811
- _cached = path8.basename(repoRoot);
3982
+ _cached = path9.basename(repoRoot);
3812
3983
  _cachedCwd = dir;
3813
3984
  return _cached;
3814
3985
  } catch {
3815
- _cached = path8.basename(dir);
3986
+ _cached = path9.basename(dir);
3816
3987
  _cachedCwd = dir;
3817
3988
  return _cached;
3818
3989
  }
@@ -3836,9 +4007,9 @@ __export(file_grep_exports, {
3836
4007
  grepProjectFiles: () => grepProjectFiles
3837
4008
  });
3838
4009
  import { execSync as execSync3 } from "child_process";
3839
- import { readFileSync as readFileSync4, readdirSync as readdirSync2, statSync as statSync2, existsSync as existsSync7 } from "fs";
3840
- import path9 from "path";
3841
- import crypto from "crypto";
4010
+ import { readFileSync as readFileSync5, readdirSync as readdirSync2, statSync as statSync2, existsSync as existsSync9 } from "fs";
4011
+ import path10 from "path";
4012
+ import crypto2 from "crypto";
3842
4013
  function hasRipgrep() {
3843
4014
  if (_hasRg === null) {
3844
4015
  try {
@@ -3871,13 +4042,13 @@ async function grepProjectFiles(query, projectRoot, options) {
3871
4042
  const chunkCtx = getChunkContext(hit.filePath, hit.lineNumber);
3872
4043
  const prefix = chunkCtx ? `[file: ${hit.filePath}:${hit.lineNumber} in ${chunkCtx}]` : `[file: ${hit.filePath}:${hit.lineNumber}]`;
3873
4044
  return {
3874
- id: crypto.createHash("sha256").update(`${hit.filePath}:${hit.lineNumber}`).digest("hex").slice(0, 36),
4045
+ id: crypto2.createHash("sha256").update(`${hit.filePath}:${hit.lineNumber}`).digest("hex").slice(0, 36),
3875
4046
  agent_id: "project",
3876
4047
  agent_role: "file",
3877
4048
  session_id: "file-grep",
3878
4049
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3879
4050
  tool_name: "file_grep",
3880
- project_name: path9.basename(projectRoot),
4051
+ project_name: path10.basename(projectRoot),
3881
4052
  has_error: false,
3882
4053
  raw_text: `${prefix} ${buildSnippet(hit, projectRoot)}`,
3883
4054
  vector: null,
@@ -3889,7 +4060,7 @@ function getChunkContext(filePath, lineNumber) {
3889
4060
  try {
3890
4061
  const ext = filePath.split(".").pop()?.toLowerCase();
3891
4062
  if (ext !== "ts" && ext !== "tsx" && ext !== "js" && ext !== "jsx") return "";
3892
- const source = readFileSync4(filePath, "utf8");
4063
+ const source = readFileSync5(filePath, "utf8");
3893
4064
  const lines = source.split("\n");
3894
4065
  for (let i = Math.min(lineNumber - 1, lines.length - 1); i >= 0; i--) {
3895
4066
  const line = lines[i];
@@ -3951,11 +4122,11 @@ function grepWithNodeFs(pattern, projectRoot, patterns) {
3951
4122
  const files = collectFiles(projectRoot, patterns ?? DEFAULT_PATTERNS);
3952
4123
  const hits = [];
3953
4124
  for (const filePath of files.slice(0, MAX_FILES)) {
3954
- const absPath = path9.join(projectRoot, filePath);
4125
+ const absPath = path10.join(projectRoot, filePath);
3955
4126
  try {
3956
4127
  const stat = statSync2(absPath);
3957
4128
  if (stat.size > MAX_FILE_SIZE) continue;
3958
- const content = readFileSync4(absPath, "utf8");
4129
+ const content = readFileSync5(absPath, "utf8");
3959
4130
  const lines = content.split("\n");
3960
4131
  const matches = content.match(regex);
3961
4132
  if (!matches || matches.length === 0) continue;
@@ -3978,15 +4149,15 @@ function collectFiles(root, patterns) {
3978
4149
  const files = [];
3979
4150
  function walk(dir, relative) {
3980
4151
  if (files.length >= MAX_FILES) return;
3981
- const basename = path9.basename(dir);
4152
+ const basename = path10.basename(dir);
3982
4153
  if (EXCLUDE_DIRS.includes(basename)) return;
3983
4154
  try {
3984
4155
  const entries = readdirSync2(dir, { withFileTypes: true });
3985
4156
  for (const entry of entries) {
3986
4157
  if (files.length >= MAX_FILES) return;
3987
- const rel = path9.join(relative, entry.name);
4158
+ const rel = path10.join(relative, entry.name);
3988
4159
  if (entry.isDirectory()) {
3989
- walk(path9.join(dir, entry.name), rel);
4160
+ walk(path10.join(dir, entry.name), rel);
3990
4161
  } else if (entry.isFile()) {
3991
4162
  for (const pat of patterns) {
3992
4163
  if (matchGlob(rel, pat)) {
@@ -4018,7 +4189,7 @@ function matchGlob(filePath, pattern) {
4018
4189
  if (slashIdx !== -1) {
4019
4190
  const dir = pattern.slice(0, slashIdx);
4020
4191
  const ext2 = pattern.slice(slashIdx + 1).replace("*", "");
4021
- const fileDir = path9.dirname(filePath);
4192
+ const fileDir = path10.dirname(filePath);
4022
4193
  return fileDir === dir && filePath.endsWith(ext2);
4023
4194
  }
4024
4195
  const ext = pattern.replace("*", "");
@@ -4026,9 +4197,9 @@ function matchGlob(filePath, pattern) {
4026
4197
  }
4027
4198
  function buildSnippet(hit, projectRoot) {
4028
4199
  try {
4029
- const absPath = path9.join(projectRoot, hit.filePath);
4030
- if (!existsSync7(absPath)) return hit.matchLine;
4031
- const lines = readFileSync4(absPath, "utf8").split("\n");
4200
+ const absPath = path10.join(projectRoot, hit.filePath);
4201
+ if (!existsSync9(absPath)) return hit.matchLine;
4202
+ const lines = readFileSync5(absPath, "utf8").split("\n");
4032
4203
  const start = Math.max(0, hit.lineNumber - 3);
4033
4204
  const end = Math.min(lines.length, hit.lineNumber + 2);
4034
4205
  return lines.slice(start, end).join("\n").slice(0, 500);