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