@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
@@ -64,9 +64,34 @@ var init_db_retry = __esm({
64
64
  }
65
65
  });
66
66
 
67
+ // src/lib/secure-files.ts
68
+ import { chmodSync, existsSync, mkdirSync } from "fs";
69
+ import { chmod, mkdir } from "fs/promises";
70
+ async function ensurePrivateDir(dirPath) {
71
+ await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
72
+ try {
73
+ await chmod(dirPath, PRIVATE_DIR_MODE);
74
+ } catch {
75
+ }
76
+ }
77
+ async function enforcePrivateFile(filePath) {
78
+ try {
79
+ await chmod(filePath, PRIVATE_FILE_MODE);
80
+ } catch {
81
+ }
82
+ }
83
+ var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
84
+ var init_secure_files = __esm({
85
+ "src/lib/secure-files.ts"() {
86
+ "use strict";
87
+ PRIVATE_DIR_MODE = 448;
88
+ PRIVATE_FILE_MODE = 384;
89
+ }
90
+ });
91
+
67
92
  // src/lib/config.ts
68
- import { readFile, writeFile, mkdir, chmod } from "fs/promises";
69
- import { readFileSync, existsSync, renameSync } from "fs";
93
+ import { readFile, writeFile } from "fs/promises";
94
+ import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
70
95
  import path from "path";
71
96
  import os from "os";
72
97
  function resolveDataDir() {
@@ -74,7 +99,7 @@ function resolveDataDir() {
74
99
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
75
100
  const newDir = path.join(os.homedir(), ".exe-os");
76
101
  const legacyDir = path.join(os.homedir(), ".exe-mem");
77
- if (!existsSync(newDir) && existsSync(legacyDir)) {
102
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
78
103
  try {
79
104
  renameSync(legacyDir, newDir);
80
105
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -137,9 +162,9 @@ function normalizeAutoUpdate(raw) {
137
162
  }
138
163
  async function loadConfig() {
139
164
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
140
- await mkdir(dir, { recursive: true });
165
+ await ensurePrivateDir(dir);
141
166
  const configPath = path.join(dir, "config.json");
142
- if (!existsSync(configPath)) {
167
+ if (!existsSync2(configPath)) {
143
168
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
144
169
  }
145
170
  const raw = await readFile(configPath, "utf-8");
@@ -152,6 +177,7 @@ async function loadConfig() {
152
177
  `);
153
178
  try {
154
179
  await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
180
+ await enforcePrivateFile(configPath);
155
181
  } catch {
156
182
  }
157
183
  }
@@ -171,6 +197,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
171
197
  var init_config = __esm({
172
198
  "src/lib/config.ts"() {
173
199
  "use strict";
200
+ init_secure_files();
174
201
  EXE_AI_DIR = resolveDataDir();
175
202
  DB_PATH = path.join(EXE_AI_DIR, "memories.db");
176
203
  MODELS_DIR = path.join(EXE_AI_DIR, "models");
@@ -249,7 +276,7 @@ var init_config = __esm({
249
276
 
250
277
  // src/lib/employees.ts
251
278
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
252
- import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
279
+ import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
253
280
  import { execSync } from "child_process";
254
281
  import path2 from "path";
255
282
  import os2 from "os";
@@ -266,7 +293,7 @@ function getCoordinatorName(employees = loadEmployeesSync()) {
266
293
  return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
267
294
  }
268
295
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
269
- if (!existsSync2(employeesPath)) return [];
296
+ if (!existsSync3(employeesPath)) return [];
270
297
  try {
271
298
  return JSON.parse(readFileSync2(employeesPath, "utf-8"));
272
299
  } catch {
@@ -1211,6 +1238,7 @@ async function ensureSchema() {
1211
1238
  project TEXT NOT NULL,
1212
1239
  summary TEXT NOT NULL,
1213
1240
  task_file TEXT,
1241
+ session_scope TEXT,
1214
1242
  read INTEGER NOT NULL DEFAULT 0,
1215
1243
  created_at TEXT NOT NULL
1216
1244
  );
@@ -1219,7 +1247,7 @@ async function ensureSchema() {
1219
1247
  ON notifications(read);
1220
1248
 
1221
1249
  CREATE INDEX IF NOT EXISTS idx_notifications_agent
1222
- ON notifications(agent_id);
1250
+ ON notifications(agent_id, session_scope);
1223
1251
 
1224
1252
  CREATE INDEX IF NOT EXISTS idx_notifications_task_file
1225
1253
  ON notifications(task_file);
@@ -1257,6 +1285,7 @@ async function ensureSchema() {
1257
1285
  target_agent TEXT NOT NULL,
1258
1286
  target_project TEXT,
1259
1287
  target_device TEXT NOT NULL DEFAULT 'local',
1288
+ session_scope TEXT,
1260
1289
  content TEXT NOT NULL,
1261
1290
  priority TEXT DEFAULT 'normal',
1262
1291
  status TEXT DEFAULT 'pending',
@@ -1270,10 +1299,31 @@ async function ensureSchema() {
1270
1299
  );
1271
1300
 
1272
1301
  CREATE INDEX IF NOT EXISTS idx_messages_target
1273
- ON messages(target_agent, status);
1302
+ ON messages(target_agent, session_scope, status);
1274
1303
 
1275
1304
  CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
1276
- ON messages(target_agent, from_agent, server_seq);
1305
+ ON messages(target_agent, session_scope, from_agent, server_seq);
1306
+ `);
1307
+ try {
1308
+ await client.execute({
1309
+ sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
1310
+ args: []
1311
+ });
1312
+ } catch {
1313
+ }
1314
+ try {
1315
+ await client.execute({
1316
+ sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
1317
+ args: []
1318
+ });
1319
+ } catch {
1320
+ }
1321
+ await client.executeMultiple(`
1322
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
1323
+ ON notifications(agent_id, session_scope, read, created_at);
1324
+
1325
+ CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
1326
+ ON messages(target_agent, session_scope, status, created_at);
1277
1327
  `);
1278
1328
  try {
1279
1329
  await client.execute({
@@ -1857,6 +1907,13 @@ async function ensureSchema() {
1857
1907
  } catch {
1858
1908
  }
1859
1909
  }
1910
+ try {
1911
+ await client.execute({
1912
+ sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
1913
+ args: []
1914
+ });
1915
+ } catch {
1916
+ }
1860
1917
  }
1861
1918
  async function disposeDatabase() {
1862
1919
  if (_walCheckpointTimer) {
@@ -1899,6 +1956,7 @@ var shard_manager_exports = {};
1899
1956
  __export(shard_manager_exports, {
1900
1957
  disposeShards: () => disposeShards,
1901
1958
  ensureShardSchema: () => ensureShardSchema,
1959
+ getOpenShardCount: () => getOpenShardCount,
1902
1960
  getReadyShardClient: () => getReadyShardClient,
1903
1961
  getShardClient: () => getShardClient,
1904
1962
  getShardsDir: () => getShardsDir,
@@ -1908,14 +1966,17 @@ __export(shard_manager_exports, {
1908
1966
  shardExists: () => shardExists
1909
1967
  });
1910
1968
  import path5 from "path";
1911
- import { existsSync as existsSync4, mkdirSync, readdirSync } from "fs";
1969
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, readdirSync } from "fs";
1912
1970
  import { createClient as createClient2 } from "@libsql/client";
1913
1971
  function initShardManager(encryptionKey) {
1914
1972
  _encryptionKey = encryptionKey;
1915
- if (!existsSync4(SHARDS_DIR)) {
1916
- mkdirSync(SHARDS_DIR, { recursive: true });
1973
+ if (!existsSync5(SHARDS_DIR)) {
1974
+ mkdirSync2(SHARDS_DIR, { recursive: true });
1917
1975
  }
1918
1976
  _shardingEnabled = true;
1977
+ if (_evictionTimer) clearInterval(_evictionTimer);
1978
+ _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
1979
+ _evictionTimer.unref();
1919
1980
  }
1920
1981
  function isShardingEnabled() {
1921
1982
  return _shardingEnabled;
@@ -1932,21 +1993,28 @@ function getShardClient(projectName2) {
1932
1993
  throw new Error(`Invalid project name for shard: "${projectName2}"`);
1933
1994
  }
1934
1995
  const cached = _shards.get(safeName);
1935
- if (cached) return cached;
1996
+ if (cached) {
1997
+ _shardLastAccess.set(safeName, Date.now());
1998
+ return cached;
1999
+ }
2000
+ while (_shards.size >= MAX_OPEN_SHARDS) {
2001
+ evictLRU();
2002
+ }
1936
2003
  const dbPath = path5.join(SHARDS_DIR, `${safeName}.db`);
1937
2004
  const client = createClient2({
1938
2005
  url: `file:${dbPath}`,
1939
2006
  encryptionKey: _encryptionKey
1940
2007
  });
1941
2008
  _shards.set(safeName, client);
2009
+ _shardLastAccess.set(safeName, Date.now());
1942
2010
  return client;
1943
2011
  }
1944
2012
  function shardExists(projectName2) {
1945
2013
  const safeName = projectName2.replace(/[^a-zA-Z0-9_-]/g, "_");
1946
- return existsSync4(path5.join(SHARDS_DIR, `${safeName}.db`));
2014
+ return existsSync5(path5.join(SHARDS_DIR, `${safeName}.db`));
1947
2015
  }
1948
2016
  function listShards() {
1949
- if (!existsSync4(SHARDS_DIR)) return [];
2017
+ if (!existsSync5(SHARDS_DIR)) return [];
1950
2018
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
1951
2019
  }
1952
2020
  async function ensureShardSchema(client) {
@@ -1998,6 +2066,8 @@ async function ensureShardSchema(client) {
1998
2066
  for (const col of [
1999
2067
  "ALTER TABLE memories ADD COLUMN task_id TEXT",
2000
2068
  "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
2069
+ "ALTER TABLE memories ADD COLUMN author_device_id TEXT",
2070
+ "ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
2001
2071
  "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
2002
2072
  "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
2003
2073
  "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
@@ -2135,21 +2205,69 @@ async function getReadyShardClient(projectName2) {
2135
2205
  await ensureShardSchema(client);
2136
2206
  return client;
2137
2207
  }
2208
+ function evictLRU() {
2209
+ let oldest = null;
2210
+ let oldestTime = Infinity;
2211
+ for (const [name, time] of _shardLastAccess) {
2212
+ if (time < oldestTime) {
2213
+ oldestTime = time;
2214
+ oldest = name;
2215
+ }
2216
+ }
2217
+ if (oldest) {
2218
+ const client = _shards.get(oldest);
2219
+ if (client) {
2220
+ client.close();
2221
+ }
2222
+ _shards.delete(oldest);
2223
+ _shardLastAccess.delete(oldest);
2224
+ }
2225
+ }
2226
+ function evictIdleShards() {
2227
+ const now = Date.now();
2228
+ const toEvict = [];
2229
+ for (const [name, lastAccess] of _shardLastAccess) {
2230
+ if (now - lastAccess > SHARD_IDLE_MS) {
2231
+ toEvict.push(name);
2232
+ }
2233
+ }
2234
+ for (const name of toEvict) {
2235
+ const client = _shards.get(name);
2236
+ if (client) {
2237
+ client.close();
2238
+ }
2239
+ _shards.delete(name);
2240
+ _shardLastAccess.delete(name);
2241
+ }
2242
+ }
2243
+ function getOpenShardCount() {
2244
+ return _shards.size;
2245
+ }
2138
2246
  function disposeShards() {
2247
+ if (_evictionTimer) {
2248
+ clearInterval(_evictionTimer);
2249
+ _evictionTimer = null;
2250
+ }
2139
2251
  for (const [, client] of _shards) {
2140
2252
  client.close();
2141
2253
  }
2142
2254
  _shards.clear();
2255
+ _shardLastAccess.clear();
2143
2256
  _shardingEnabled = false;
2144
2257
  _encryptionKey = null;
2145
2258
  }
2146
- var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
2259
+ var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
2147
2260
  var init_shard_manager = __esm({
2148
2261
  "src/lib/shard-manager.ts"() {
2149
2262
  "use strict";
2150
2263
  init_config();
2151
2264
  SHARDS_DIR = path5.join(EXE_AI_DIR, "shards");
2265
+ SHARD_IDLE_MS = 5 * 60 * 1e3;
2266
+ MAX_OPEN_SHARDS = 10;
2267
+ EVICTION_INTERVAL_MS = 60 * 1e3;
2152
2268
  _shards = /* @__PURE__ */ new Map();
2269
+ _shardLastAccess = /* @__PURE__ */ new Map();
2270
+ _evictionTimer = null;
2153
2271
  _encryptionKey = null;
2154
2272
  _shardingEnabled = false;
2155
2273
  }
@@ -2348,7 +2466,7 @@ init_database();
2348
2466
 
2349
2467
  // src/lib/keychain.ts
2350
2468
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
2351
- import { existsSync as existsSync3 } from "fs";
2469
+ import { existsSync as existsSync4 } from "fs";
2352
2470
  import path4 from "path";
2353
2471
  import os4 from "os";
2354
2472
  var SERVICE = "exe-mem";
@@ -2378,7 +2496,7 @@ async function getMasterKey() {
2378
2496
  }
2379
2497
  }
2380
2498
  const keyPath = getKeyPath();
2381
- if (!existsSync3(keyPath)) {
2499
+ if (!existsSync4(keyPath)) {
2382
2500
  process.stderr.write(
2383
2501
  `[keychain] Key not found at ${keyPath} (HOME=${os4.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
2384
2502
  `
@@ -2694,8 +2812,8 @@ function vectorToBlob(vector) {
2694
2812
  import os5 from "os";
2695
2813
  import path6 from "path";
2696
2814
  import {
2697
- existsSync as existsSync5,
2698
- mkdirSync as mkdirSync2,
2815
+ existsSync as existsSync6,
2816
+ mkdirSync as mkdirSync3,
2699
2817
  readdirSync as readdirSync2,
2700
2818
  statSync,
2701
2819
  unlinkSync as unlinkSync2,
@@ -2742,7 +2860,7 @@ var BEHAVIORS_EXPORT_DIR = path6.join(
2742
2860
  var STALE_EXPORT_AGE_MS = 60 * 60 * 1e3;
2743
2861
  var EXPORT_BEHAVIOR_LIMIT = 30;
2744
2862
  function sweepStaleBehaviorExports(now = Date.now()) {
2745
- if (!existsSync5(BEHAVIORS_EXPORT_DIR)) return;
2863
+ if (!existsSync6(BEHAVIORS_EXPORT_DIR)) return;
2746
2864
  let entries;
2747
2865
  try {
2748
2866
  entries = readdirSync2(BEHAVIORS_EXPORT_DIR);
@@ -2792,7 +2910,7 @@ function exportFilePath(agentId2, projectName2, sessionKey2) {
2792
2910
  );
2793
2911
  }
2794
2912
  async function exportBehaviorsForAgent(agentId2, projectName2, sessionKey2) {
2795
- mkdirSync2(BEHAVIORS_EXPORT_DIR, { recursive: true });
2913
+ mkdirSync3(BEHAVIORS_EXPORT_DIR, { recursive: true });
2796
2914
  sweepStaleBehaviorExports();
2797
2915
  const behaviors = await listBehaviors(agentId2, projectName2, EXPORT_BEHAVIOR_LIMIT);
2798
2916
  if (behaviors.length === 0) return null;
@@ -71,9 +71,34 @@ var init_db_retry = __esm({
71
71
  }
72
72
  });
73
73
 
74
+ // src/lib/secure-files.ts
75
+ import { chmodSync, existsSync, mkdirSync } from "fs";
76
+ import { chmod, mkdir } from "fs/promises";
77
+ async function ensurePrivateDir(dirPath) {
78
+ await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
79
+ try {
80
+ await chmod(dirPath, PRIVATE_DIR_MODE);
81
+ } catch {
82
+ }
83
+ }
84
+ async function enforcePrivateFile(filePath) {
85
+ try {
86
+ await chmod(filePath, PRIVATE_FILE_MODE);
87
+ } catch {
88
+ }
89
+ }
90
+ var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
91
+ var init_secure_files = __esm({
92
+ "src/lib/secure-files.ts"() {
93
+ "use strict";
94
+ PRIVATE_DIR_MODE = 448;
95
+ PRIVATE_FILE_MODE = 384;
96
+ }
97
+ });
98
+
74
99
  // src/lib/config.ts
75
- import { readFile, writeFile, mkdir, chmod } from "fs/promises";
76
- import { readFileSync, existsSync, renameSync } from "fs";
100
+ import { readFile, writeFile } from "fs/promises";
101
+ import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
77
102
  import path from "path";
78
103
  import os from "os";
79
104
  function resolveDataDir() {
@@ -81,7 +106,7 @@ function resolveDataDir() {
81
106
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
82
107
  const newDir = path.join(os.homedir(), ".exe-os");
83
108
  const legacyDir = path.join(os.homedir(), ".exe-mem");
84
- if (!existsSync(newDir) && existsSync(legacyDir)) {
109
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
85
110
  try {
86
111
  renameSync(legacyDir, newDir);
87
112
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -144,9 +169,9 @@ function normalizeAutoUpdate(raw) {
144
169
  }
145
170
  async function loadConfig() {
146
171
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
147
- await mkdir(dir, { recursive: true });
172
+ await ensurePrivateDir(dir);
148
173
  const configPath = path.join(dir, "config.json");
149
- if (!existsSync(configPath)) {
174
+ if (!existsSync2(configPath)) {
150
175
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
151
176
  }
152
177
  const raw = await readFile(configPath, "utf-8");
@@ -159,6 +184,7 @@ async function loadConfig() {
159
184
  `);
160
185
  try {
161
186
  await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
187
+ await enforcePrivateFile(configPath);
162
188
  } catch {
163
189
  }
164
190
  }
@@ -178,6 +204,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
178
204
  var init_config = __esm({
179
205
  "src/lib/config.ts"() {
180
206
  "use strict";
207
+ init_secure_files();
181
208
  EXE_AI_DIR = resolveDataDir();
182
209
  DB_PATH = path.join(EXE_AI_DIR, "memories.db");
183
210
  MODELS_DIR = path.join(EXE_AI_DIR, "models");
@@ -256,7 +283,7 @@ var init_config = __esm({
256
283
 
257
284
  // src/lib/employees.ts
258
285
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
259
- import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
286
+ import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
260
287
  import { execSync } from "child_process";
261
288
  import path2 from "path";
262
289
  import os2 from "os";
@@ -273,7 +300,7 @@ function getCoordinatorName(employees = loadEmployeesSync()) {
273
300
  return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
274
301
  }
275
302
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
276
- if (!existsSync2(employeesPath)) return [];
303
+ if (!existsSync3(employeesPath)) return [];
277
304
  try {
278
305
  return JSON.parse(readFileSync2(employeesPath, "utf-8"));
279
306
  } catch {
@@ -1218,6 +1245,7 @@ async function ensureSchema() {
1218
1245
  project TEXT NOT NULL,
1219
1246
  summary TEXT NOT NULL,
1220
1247
  task_file TEXT,
1248
+ session_scope TEXT,
1221
1249
  read INTEGER NOT NULL DEFAULT 0,
1222
1250
  created_at TEXT NOT NULL
1223
1251
  );
@@ -1226,7 +1254,7 @@ async function ensureSchema() {
1226
1254
  ON notifications(read);
1227
1255
 
1228
1256
  CREATE INDEX IF NOT EXISTS idx_notifications_agent
1229
- ON notifications(agent_id);
1257
+ ON notifications(agent_id, session_scope);
1230
1258
 
1231
1259
  CREATE INDEX IF NOT EXISTS idx_notifications_task_file
1232
1260
  ON notifications(task_file);
@@ -1264,6 +1292,7 @@ async function ensureSchema() {
1264
1292
  target_agent TEXT NOT NULL,
1265
1293
  target_project TEXT,
1266
1294
  target_device TEXT NOT NULL DEFAULT 'local',
1295
+ session_scope TEXT,
1267
1296
  content TEXT NOT NULL,
1268
1297
  priority TEXT DEFAULT 'normal',
1269
1298
  status TEXT DEFAULT 'pending',
@@ -1277,10 +1306,31 @@ async function ensureSchema() {
1277
1306
  );
1278
1307
 
1279
1308
  CREATE INDEX IF NOT EXISTS idx_messages_target
1280
- ON messages(target_agent, status);
1309
+ ON messages(target_agent, session_scope, status);
1281
1310
 
1282
1311
  CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
1283
- ON messages(target_agent, from_agent, server_seq);
1312
+ ON messages(target_agent, session_scope, from_agent, server_seq);
1313
+ `);
1314
+ try {
1315
+ await client.execute({
1316
+ sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
1317
+ args: []
1318
+ });
1319
+ } catch {
1320
+ }
1321
+ try {
1322
+ await client.execute({
1323
+ sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
1324
+ args: []
1325
+ });
1326
+ } catch {
1327
+ }
1328
+ await client.executeMultiple(`
1329
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
1330
+ ON notifications(agent_id, session_scope, read, created_at);
1331
+
1332
+ CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
1333
+ ON messages(target_agent, session_scope, status, created_at);
1284
1334
  `);
1285
1335
  try {
1286
1336
  await client.execute({
@@ -1864,6 +1914,13 @@ async function ensureSchema() {
1864
1914
  } catch {
1865
1915
  }
1866
1916
  }
1917
+ try {
1918
+ await client.execute({
1919
+ sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
1920
+ args: []
1921
+ });
1922
+ } catch {
1923
+ }
1867
1924
  }
1868
1925
  var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso;
1869
1926
  var init_database = __esm({
@@ -1883,7 +1940,7 @@ var init_database = __esm({
1883
1940
 
1884
1941
  // src/lib/keychain.ts
1885
1942
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
1886
- import { existsSync as existsSync3 } from "fs";
1943
+ import { existsSync as existsSync4 } from "fs";
1887
1944
  import path4 from "path";
1888
1945
  import os4 from "os";
1889
1946
  function getKeyDir() {
@@ -1911,7 +1968,7 @@ async function getMasterKey() {
1911
1968
  }
1912
1969
  }
1913
1970
  const keyPath = getKeyPath();
1914
- if (!existsSync3(keyPath)) {
1971
+ if (!existsSync4(keyPath)) {
1915
1972
  process.stderr.write(
1916
1973
  `[keychain] Key not found at ${keyPath} (HOME=${os4.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
1917
1974
  `
@@ -1998,6 +2055,7 @@ var shard_manager_exports = {};
1998
2055
  __export(shard_manager_exports, {
1999
2056
  disposeShards: () => disposeShards,
2000
2057
  ensureShardSchema: () => ensureShardSchema,
2058
+ getOpenShardCount: () => getOpenShardCount,
2001
2059
  getReadyShardClient: () => getReadyShardClient,
2002
2060
  getShardClient: () => getShardClient,
2003
2061
  getShardsDir: () => getShardsDir,
@@ -2007,14 +2065,17 @@ __export(shard_manager_exports, {
2007
2065
  shardExists: () => shardExists
2008
2066
  });
2009
2067
  import path5 from "path";
2010
- import { existsSync as existsSync4, mkdirSync, readdirSync } from "fs";
2068
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, readdirSync } from "fs";
2011
2069
  import { createClient as createClient2 } from "@libsql/client";
2012
2070
  function initShardManager(encryptionKey) {
2013
2071
  _encryptionKey = encryptionKey;
2014
- if (!existsSync4(SHARDS_DIR)) {
2015
- mkdirSync(SHARDS_DIR, { recursive: true });
2072
+ if (!existsSync5(SHARDS_DIR)) {
2073
+ mkdirSync2(SHARDS_DIR, { recursive: true });
2016
2074
  }
2017
2075
  _shardingEnabled = true;
2076
+ if (_evictionTimer) clearInterval(_evictionTimer);
2077
+ _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
2078
+ _evictionTimer.unref();
2018
2079
  }
2019
2080
  function isShardingEnabled() {
2020
2081
  return _shardingEnabled;
@@ -2031,21 +2092,28 @@ function getShardClient(projectName) {
2031
2092
  throw new Error(`Invalid project name for shard: "${projectName}"`);
2032
2093
  }
2033
2094
  const cached = _shards.get(safeName);
2034
- if (cached) return cached;
2095
+ if (cached) {
2096
+ _shardLastAccess.set(safeName, Date.now());
2097
+ return cached;
2098
+ }
2099
+ while (_shards.size >= MAX_OPEN_SHARDS) {
2100
+ evictLRU();
2101
+ }
2035
2102
  const dbPath = path5.join(SHARDS_DIR, `${safeName}.db`);
2036
2103
  const client = createClient2({
2037
2104
  url: `file:${dbPath}`,
2038
2105
  encryptionKey: _encryptionKey
2039
2106
  });
2040
2107
  _shards.set(safeName, client);
2108
+ _shardLastAccess.set(safeName, Date.now());
2041
2109
  return client;
2042
2110
  }
2043
2111
  function shardExists(projectName) {
2044
2112
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
2045
- return existsSync4(path5.join(SHARDS_DIR, `${safeName}.db`));
2113
+ return existsSync5(path5.join(SHARDS_DIR, `${safeName}.db`));
2046
2114
  }
2047
2115
  function listShards() {
2048
- if (!existsSync4(SHARDS_DIR)) return [];
2116
+ if (!existsSync5(SHARDS_DIR)) return [];
2049
2117
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
2050
2118
  }
2051
2119
  async function ensureShardSchema(client) {
@@ -2097,6 +2165,8 @@ async function ensureShardSchema(client) {
2097
2165
  for (const col of [
2098
2166
  "ALTER TABLE memories ADD COLUMN task_id TEXT",
2099
2167
  "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
2168
+ "ALTER TABLE memories ADD COLUMN author_device_id TEXT",
2169
+ "ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
2100
2170
  "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
2101
2171
  "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
2102
2172
  "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
@@ -2234,21 +2304,69 @@ async function getReadyShardClient(projectName) {
2234
2304
  await ensureShardSchema(client);
2235
2305
  return client;
2236
2306
  }
2307
+ function evictLRU() {
2308
+ let oldest = null;
2309
+ let oldestTime = Infinity;
2310
+ for (const [name, time] of _shardLastAccess) {
2311
+ if (time < oldestTime) {
2312
+ oldestTime = time;
2313
+ oldest = name;
2314
+ }
2315
+ }
2316
+ if (oldest) {
2317
+ const client = _shards.get(oldest);
2318
+ if (client) {
2319
+ client.close();
2320
+ }
2321
+ _shards.delete(oldest);
2322
+ _shardLastAccess.delete(oldest);
2323
+ }
2324
+ }
2325
+ function evictIdleShards() {
2326
+ const now = Date.now();
2327
+ const toEvict = [];
2328
+ for (const [name, lastAccess] of _shardLastAccess) {
2329
+ if (now - lastAccess > SHARD_IDLE_MS) {
2330
+ toEvict.push(name);
2331
+ }
2332
+ }
2333
+ for (const name of toEvict) {
2334
+ const client = _shards.get(name);
2335
+ if (client) {
2336
+ client.close();
2337
+ }
2338
+ _shards.delete(name);
2339
+ _shardLastAccess.delete(name);
2340
+ }
2341
+ }
2342
+ function getOpenShardCount() {
2343
+ return _shards.size;
2344
+ }
2237
2345
  function disposeShards() {
2346
+ if (_evictionTimer) {
2347
+ clearInterval(_evictionTimer);
2348
+ _evictionTimer = null;
2349
+ }
2238
2350
  for (const [, client] of _shards) {
2239
2351
  client.close();
2240
2352
  }
2241
2353
  _shards.clear();
2354
+ _shardLastAccess.clear();
2242
2355
  _shardingEnabled = false;
2243
2356
  _encryptionKey = null;
2244
2357
  }
2245
- var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
2358
+ var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
2246
2359
  var init_shard_manager = __esm({
2247
2360
  "src/lib/shard-manager.ts"() {
2248
2361
  "use strict";
2249
2362
  init_config();
2250
2363
  SHARDS_DIR = path5.join(EXE_AI_DIR, "shards");
2364
+ SHARD_IDLE_MS = 5 * 60 * 1e3;
2365
+ MAX_OPEN_SHARDS = 10;
2366
+ EVICTION_INTERVAL_MS = 60 * 1e3;
2251
2367
  _shards = /* @__PURE__ */ new Map();
2368
+ _shardLastAccess = /* @__PURE__ */ new Map();
2369
+ _evictionTimer = null;
2252
2370
  _encryptionKey = null;
2253
2371
  _shardingEnabled = false;
2254
2372
  }