@askexenow/exe-os 0.9.8 → 0.9.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/dist/bin/backfill-conversations.js +222 -49
  2. package/dist/bin/backfill-responses.js +221 -48
  3. package/dist/bin/backfill-vectors.js +225 -52
  4. package/dist/bin/cleanup-stale-review-tasks.js +150 -28
  5. package/dist/bin/cli.js +1295 -856
  6. package/dist/bin/exe-agent-config.js +36 -8
  7. package/dist/bin/exe-agent.js +14 -4
  8. package/dist/bin/exe-assign.js +221 -48
  9. package/dist/bin/exe-boot.js +778 -427
  10. package/dist/bin/exe-call.js +41 -13
  11. package/dist/bin/exe-cloud.js +163 -58
  12. package/dist/bin/exe-dispatch.js +276 -139
  13. package/dist/bin/exe-doctor.js +145 -27
  14. package/dist/bin/exe-export-behaviors.js +141 -23
  15. package/dist/bin/exe-forget.js +137 -19
  16. package/dist/bin/exe-gateway.js +677 -388
  17. package/dist/bin/exe-heartbeat.js +227 -108
  18. package/dist/bin/exe-kill.js +138 -20
  19. package/dist/bin/exe-launch-agent.js +172 -39
  20. package/dist/bin/exe-link.js +291 -100
  21. package/dist/bin/exe-new-employee.js +214 -106
  22. package/dist/bin/exe-pending-messages.js +395 -33
  23. package/dist/bin/exe-pending-notifications.js +684 -99
  24. package/dist/bin/exe-pending-reviews.js +420 -74
  25. package/dist/bin/exe-rename.js +147 -49
  26. package/dist/bin/exe-review.js +138 -20
  27. package/dist/bin/exe-search.js +240 -69
  28. package/dist/bin/exe-session-cleanup.js +440 -250
  29. package/dist/bin/exe-settings.js +61 -17
  30. package/dist/bin/exe-start-codex.js +158 -39
  31. package/dist/bin/exe-start-opencode.js +157 -38
  32. package/dist/bin/exe-status.js +151 -29
  33. package/dist/bin/exe-team.js +138 -20
  34. package/dist/bin/git-sweep.js +404 -212
  35. package/dist/bin/graph-backfill.js +137 -19
  36. package/dist/bin/graph-export.js +140 -22
  37. package/dist/bin/install.js +90 -61
  38. package/dist/bin/scan-tasks.js +412 -220
  39. package/dist/bin/setup.js +564 -293
  40. package/dist/bin/shard-migrate.js +139 -21
  41. package/dist/bin/update.js +138 -49
  42. package/dist/bin/wiki-sync.js +137 -19
  43. package/dist/gateway/index.js +533 -320
  44. package/dist/hooks/bug-report-worker.js +344 -193
  45. package/dist/hooks/codex-stop-task-finalizer.js +4678 -0
  46. package/dist/hooks/commit-complete.js +402 -210
  47. package/dist/hooks/error-recall.js +245 -74
  48. package/dist/hooks/exe-heartbeat-hook.js +16 -6
  49. package/dist/hooks/ingest-worker.js +3423 -3157
  50. package/dist/hooks/ingest.js +832 -97
  51. package/dist/hooks/instructions-loaded.js +227 -54
  52. package/dist/hooks/notification.js +216 -43
  53. package/dist/hooks/post-compact.js +239 -62
  54. package/dist/hooks/pre-compact.js +408 -216
  55. package/dist/hooks/pre-tool-use.js +268 -90
  56. package/dist/hooks/prompt-ingest-worker.js +352 -102
  57. package/dist/hooks/prompt-submit.js +541 -328
  58. package/dist/hooks/response-ingest-worker.js +372 -122
  59. package/dist/hooks/session-end.js +443 -240
  60. package/dist/hooks/session-start.js +313 -127
  61. package/dist/hooks/stop.js +293 -98
  62. package/dist/hooks/subagent-stop.js +239 -62
  63. package/dist/hooks/summary-worker.js +568 -236
  64. package/dist/index.js +538 -324
  65. package/dist/lib/agent-config.js +28 -6
  66. package/dist/lib/cloud-sync.js +284 -105
  67. package/dist/lib/config.js +30 -10
  68. package/dist/lib/consolidation.js +16 -6
  69. package/dist/lib/database.js +123 -25
  70. package/dist/lib/db-daemon-client.js +73 -19
  71. package/dist/lib/db.js +123 -25
  72. package/dist/lib/device-registry.js +133 -35
  73. package/dist/lib/embedder.js +107 -32
  74. package/dist/lib/employee-templates.js +14 -4
  75. package/dist/lib/employees.js +41 -13
  76. package/dist/lib/exe-daemon-client.js +88 -22
  77. package/dist/lib/exe-daemon.js +935 -587
  78. package/dist/lib/hybrid-search.js +240 -69
  79. package/dist/lib/identity.js +18 -8
  80. package/dist/lib/license.js +133 -48
  81. package/dist/lib/messaging.js +116 -56
  82. package/dist/lib/reminders.js +14 -4
  83. package/dist/lib/schedules.js +137 -19
  84. package/dist/lib/skill-learning.js +33 -6
  85. package/dist/lib/store.js +137 -19
  86. package/dist/lib/task-router.js +14 -4
  87. package/dist/lib/tasks.js +280 -234
  88. package/dist/lib/tmux-routing.js +172 -125
  89. package/dist/lib/token-spend.js +26 -8
  90. package/dist/mcp/server.js +1326 -609
  91. package/dist/mcp/tools/complete-reminder.js +14 -4
  92. package/dist/mcp/tools/create-reminder.js +14 -4
  93. package/dist/mcp/tools/create-task.js +306 -248
  94. package/dist/mcp/tools/deactivate-behavior.js +16 -6
  95. package/dist/mcp/tools/list-reminders.js +14 -4
  96. package/dist/mcp/tools/list-tasks.js +123 -107
  97. package/dist/mcp/tools/send-message.js +75 -29
  98. package/dist/mcp/tools/update-task.js +1848 -199
  99. package/dist/runtime/index.js +441 -248
  100. package/dist/tui/App.js +761 -424
  101. package/package.json +1 -1
@@ -8,9 +8,47 @@ var __export = (target, all) => {
8
8
  __defProp(target, name, { get: all[name], enumerable: true });
9
9
  };
10
10
 
11
+ // src/lib/secure-files.ts
12
+ import { chmodSync, existsSync, mkdirSync } from "fs";
13
+ import { chmod, mkdir } from "fs/promises";
14
+ async function ensurePrivateDir(dirPath) {
15
+ await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
16
+ try {
17
+ await chmod(dirPath, PRIVATE_DIR_MODE);
18
+ } catch {
19
+ }
20
+ }
21
+ function ensurePrivateDirSync(dirPath) {
22
+ mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
23
+ try {
24
+ chmodSync(dirPath, PRIVATE_DIR_MODE);
25
+ } catch {
26
+ }
27
+ }
28
+ async function enforcePrivateFile(filePath) {
29
+ try {
30
+ await chmod(filePath, PRIVATE_FILE_MODE);
31
+ } catch {
32
+ }
33
+ }
34
+ function enforcePrivateFileSync(filePath) {
35
+ try {
36
+ if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
37
+ } catch {
38
+ }
39
+ }
40
+ var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
41
+ var init_secure_files = __esm({
42
+ "src/lib/secure-files.ts"() {
43
+ "use strict";
44
+ PRIVATE_DIR_MODE = 448;
45
+ PRIVATE_FILE_MODE = 384;
46
+ }
47
+ });
48
+
11
49
  // src/lib/config.ts
12
- import { readFile, writeFile, mkdir, chmod } from "fs/promises";
13
- import { readFileSync, existsSync, renameSync } from "fs";
50
+ import { readFile, writeFile } from "fs/promises";
51
+ import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
14
52
  import path from "path";
15
53
  import os from "os";
16
54
  function resolveDataDir() {
@@ -18,7 +56,7 @@ function resolveDataDir() {
18
56
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
19
57
  const newDir = path.join(os.homedir(), ".exe-os");
20
58
  const legacyDir = path.join(os.homedir(), ".exe-mem");
21
- if (!existsSync(newDir) && existsSync(legacyDir)) {
59
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
22
60
  try {
23
61
  renameSync(legacyDir, newDir);
24
62
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -81,9 +119,9 @@ function normalizeAutoUpdate(raw) {
81
119
  }
82
120
  async function loadConfig() {
83
121
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
84
- await mkdir(dir, { recursive: true });
122
+ await ensurePrivateDir(dir);
85
123
  const configPath = path.join(dir, "config.json");
86
- if (!existsSync(configPath)) {
124
+ if (!existsSync2(configPath)) {
87
125
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
88
126
  }
89
127
  const raw = await readFile(configPath, "utf-8");
@@ -96,6 +134,7 @@ async function loadConfig() {
96
134
  `);
97
135
  try {
98
136
  await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
137
+ await enforcePrivateFile(configPath);
99
138
  } catch {
100
139
  }
101
140
  }
@@ -115,6 +154,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
115
154
  var init_config = __esm({
116
155
  "src/lib/config.ts"() {
117
156
  "use strict";
157
+ init_secure_files();
118
158
  EXE_AI_DIR = resolveDataDir();
119
159
  DB_PATH = path.join(EXE_AI_DIR, "memories.db");
120
160
  MODELS_DIR = path.join(EXE_AI_DIR, "models");
@@ -193,7 +233,7 @@ var init_config = __esm({
193
233
 
194
234
  // src/lib/employees.ts
195
235
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
196
- import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
236
+ import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
197
237
  import { execSync as execSync2 } from "child_process";
198
238
  import path2 from "path";
199
239
  import os2 from "os";
@@ -210,7 +250,7 @@ function getCoordinatorName(employees = loadEmployeesSync()) {
210
250
  return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
211
251
  }
212
252
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
213
- if (!existsSync2(employeesPath)) return [];
253
+ if (!existsSync3(employeesPath)) return [];
214
254
  try {
215
255
  return JSON.parse(readFileSync2(employeesPath, "utf-8"));
216
256
  } catch {
@@ -871,13 +911,50 @@ var init_database_adapter = __esm({
871
911
  }
872
912
  });
873
913
 
914
+ // src/lib/daemon-auth.ts
915
+ import crypto from "crypto";
916
+ import path5 from "path";
917
+ import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
918
+ function normalizeToken(token) {
919
+ if (!token) return null;
920
+ const trimmed = token.trim();
921
+ return trimmed.length > 0 ? trimmed : null;
922
+ }
923
+ function readDaemonToken() {
924
+ try {
925
+ if (!existsSync4(DAEMON_TOKEN_PATH)) return null;
926
+ return normalizeToken(readFileSync4(DAEMON_TOKEN_PATH, "utf8"));
927
+ } catch {
928
+ return null;
929
+ }
930
+ }
931
+ function ensureDaemonToken(seed) {
932
+ const existing = readDaemonToken();
933
+ if (existing) return existing;
934
+ const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
935
+ ensurePrivateDirSync(EXE_AI_DIR);
936
+ writeFileSync3(DAEMON_TOKEN_PATH, `${token}
937
+ `, "utf8");
938
+ enforcePrivateFileSync(DAEMON_TOKEN_PATH);
939
+ return token;
940
+ }
941
+ var DAEMON_TOKEN_PATH;
942
+ var init_daemon_auth = __esm({
943
+ "src/lib/daemon-auth.ts"() {
944
+ "use strict";
945
+ init_config();
946
+ init_secure_files();
947
+ DAEMON_TOKEN_PATH = path5.join(EXE_AI_DIR, "exed.token");
948
+ }
949
+ });
950
+
874
951
  // src/lib/exe-daemon-client.ts
875
952
  import net from "net";
876
953
  import os4 from "os";
877
954
  import { spawn } from "child_process";
878
955
  import { randomUUID } from "crypto";
879
- import { existsSync as existsSync3, unlinkSync as unlinkSync3, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
880
- import path5 from "path";
956
+ import { existsSync as existsSync5, unlinkSync as unlinkSync3, readFileSync as readFileSync5, openSync, closeSync, statSync } from "fs";
957
+ import path6 from "path";
881
958
  import { fileURLToPath } from "url";
882
959
  function handleData(chunk) {
883
960
  _buffer += chunk.toString();
@@ -905,9 +982,9 @@ function handleData(chunk) {
905
982
  }
906
983
  }
907
984
  function cleanupStaleFiles() {
908
- if (existsSync3(PID_PATH)) {
985
+ if (existsSync5(PID_PATH)) {
909
986
  try {
910
- const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
987
+ const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
911
988
  if (pid > 0) {
912
989
  try {
913
990
  process.kill(pid, 0);
@@ -928,11 +1005,11 @@ function cleanupStaleFiles() {
928
1005
  }
929
1006
  }
930
1007
  function findPackageRoot() {
931
- let dir = path5.dirname(fileURLToPath(import.meta.url));
932
- const { root } = path5.parse(dir);
1008
+ let dir = path6.dirname(fileURLToPath(import.meta.url));
1009
+ const { root } = path6.parse(dir);
933
1010
  while (dir !== root) {
934
- if (existsSync3(path5.join(dir, "package.json"))) return dir;
935
- dir = path5.dirname(dir);
1011
+ if (existsSync5(path6.join(dir, "package.json"))) return dir;
1012
+ dir = path6.dirname(dir);
936
1013
  }
937
1014
  return null;
938
1015
  }
@@ -958,16 +1035,17 @@ function spawnDaemon() {
958
1035
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
959
1036
  return;
960
1037
  }
961
- const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
962
- if (!existsSync3(daemonPath)) {
1038
+ const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1039
+ if (!existsSync5(daemonPath)) {
963
1040
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
964
1041
  `);
965
1042
  return;
966
1043
  }
967
1044
  const resolvedPath = daemonPath;
1045
+ const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
968
1046
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
969
1047
  `);
970
- const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
1048
+ const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
971
1049
  let stderrFd = "ignore";
972
1050
  try {
973
1051
  stderrFd = openSync(logPath, "a");
@@ -985,7 +1063,8 @@ function spawnDaemon() {
985
1063
  TMUX_PANE: void 0,
986
1064
  // Prevents resolveExeSession() from scoping to one session
987
1065
  EXE_DAEMON_SOCK: SOCKET_PATH,
988
- EXE_DAEMON_PID: PID_PATH
1066
+ EXE_DAEMON_PID: PID_PATH,
1067
+ [DAEMON_TOKEN_ENV]: daemonToken
989
1068
  }
990
1069
  });
991
1070
  child.unref();
@@ -1092,13 +1171,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
1092
1171
  return;
1093
1172
  }
1094
1173
  const id = randomUUID();
1174
+ const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
1095
1175
  const timer = setTimeout(() => {
1096
1176
  _pending.delete(id);
1097
1177
  resolve({ error: "Request timeout" });
1098
1178
  }, timeoutMs);
1099
1179
  _pending.set(id, { resolve, timer });
1100
1180
  try {
1101
- _socket.write(JSON.stringify({ id, ...payload }) + "\n");
1181
+ _socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
1102
1182
  } catch {
1103
1183
  clearTimeout(timer);
1104
1184
  _pending.delete(id);
@@ -1109,17 +1189,19 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
1109
1189
  function isClientConnected() {
1110
1190
  return _connected;
1111
1191
  }
1112
- var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
1192
+ var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, DAEMON_TOKEN_ENV, _socket, _connected, _buffer, _pending, MAX_BUFFER;
1113
1193
  var init_exe_daemon_client = __esm({
1114
1194
  "src/lib/exe-daemon-client.ts"() {
1115
1195
  "use strict";
1116
1196
  init_config();
1117
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path5.join(EXE_AI_DIR, "exed.sock");
1118
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path5.join(EXE_AI_DIR, "exed.pid");
1119
- SPAWN_LOCK_PATH = path5.join(EXE_AI_DIR, "exed-spawn.lock");
1197
+ init_daemon_auth();
1198
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
1199
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
1200
+ SPAWN_LOCK_PATH = path6.join(EXE_AI_DIR, "exed-spawn.lock");
1120
1201
  SPAWN_LOCK_STALE_MS = 3e4;
1121
1202
  CONNECT_TIMEOUT_MS = 15e3;
1122
1203
  REQUEST_TIMEOUT_MS = 3e4;
1204
+ DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
1123
1205
  _socket = null;
1124
1206
  _connected = false;
1125
1207
  _buffer = "";
@@ -1698,6 +1780,7 @@ async function ensureSchema() {
1698
1780
  project TEXT NOT NULL,
1699
1781
  summary TEXT NOT NULL,
1700
1782
  task_file TEXT,
1783
+ session_scope TEXT,
1701
1784
  read INTEGER NOT NULL DEFAULT 0,
1702
1785
  created_at TEXT NOT NULL
1703
1786
  );
@@ -1706,7 +1789,7 @@ async function ensureSchema() {
1706
1789
  ON notifications(read);
1707
1790
 
1708
1791
  CREATE INDEX IF NOT EXISTS idx_notifications_agent
1709
- ON notifications(agent_id);
1792
+ ON notifications(agent_id, session_scope);
1710
1793
 
1711
1794
  CREATE INDEX IF NOT EXISTS idx_notifications_task_file
1712
1795
  ON notifications(task_file);
@@ -1744,6 +1827,7 @@ async function ensureSchema() {
1744
1827
  target_agent TEXT NOT NULL,
1745
1828
  target_project TEXT,
1746
1829
  target_device TEXT NOT NULL DEFAULT 'local',
1830
+ session_scope TEXT,
1747
1831
  content TEXT NOT NULL,
1748
1832
  priority TEXT DEFAULT 'normal',
1749
1833
  status TEXT DEFAULT 'pending',
@@ -1757,10 +1841,31 @@ async function ensureSchema() {
1757
1841
  );
1758
1842
 
1759
1843
  CREATE INDEX IF NOT EXISTS idx_messages_target
1760
- ON messages(target_agent, status);
1844
+ ON messages(target_agent, session_scope, status);
1761
1845
 
1762
1846
  CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
1763
- ON messages(target_agent, from_agent, server_seq);
1847
+ ON messages(target_agent, session_scope, from_agent, server_seq);
1848
+ `);
1849
+ try {
1850
+ await client.execute({
1851
+ sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
1852
+ args: []
1853
+ });
1854
+ } catch {
1855
+ }
1856
+ try {
1857
+ await client.execute({
1858
+ sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
1859
+ args: []
1860
+ });
1861
+ } catch {
1862
+ }
1863
+ await client.executeMultiple(`
1864
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
1865
+ ON notifications(agent_id, session_scope, read, created_at);
1866
+
1867
+ CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
1868
+ ON messages(target_agent, session_scope, status, created_at);
1764
1869
  `);
1765
1870
  try {
1766
1871
  await client.execute({
@@ -2344,6 +2449,13 @@ async function ensureSchema() {
2344
2449
  } catch {
2345
2450
  }
2346
2451
  }
2452
+ try {
2453
+ await client.execute({
2454
+ sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
2455
+ args: []
2456
+ });
2457
+ } catch {
2458
+ }
2347
2459
  }
2348
2460
  async function disposeDatabase() {
2349
2461
  if (_walCheckpointTimer) {
@@ -2392,14 +2504,14 @@ var init_memory = __esm({
2392
2504
 
2393
2505
  // src/lib/keychain.ts
2394
2506
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
2395
- import { existsSync as existsSync5 } from "fs";
2396
- import path7 from "path";
2507
+ import { existsSync as existsSync7 } from "fs";
2508
+ import path8 from "path";
2397
2509
  import os5 from "os";
2398
2510
  function getKeyDir() {
2399
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path7.join(os5.homedir(), ".exe-os");
2511
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path8.join(os5.homedir(), ".exe-os");
2400
2512
  }
2401
2513
  function getKeyPath() {
2402
- return path7.join(getKeyDir(), "master.key");
2514
+ return path8.join(getKeyDir(), "master.key");
2403
2515
  }
2404
2516
  async function tryKeytar() {
2405
2517
  try {
@@ -2420,7 +2532,7 @@ async function getMasterKey() {
2420
2532
  }
2421
2533
  }
2422
2534
  const keyPath = getKeyPath();
2423
- if (!existsSync5(keyPath)) {
2535
+ if (!existsSync7(keyPath)) {
2424
2536
  process.stderr.write(
2425
2537
  `[keychain] Key not found at ${keyPath} (HOME=${os5.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
2426
2538
  `
@@ -2507,6 +2619,7 @@ var shard_manager_exports = {};
2507
2619
  __export(shard_manager_exports, {
2508
2620
  disposeShards: () => disposeShards,
2509
2621
  ensureShardSchema: () => ensureShardSchema,
2622
+ getOpenShardCount: () => getOpenShardCount,
2510
2623
  getReadyShardClient: () => getReadyShardClient,
2511
2624
  getShardClient: () => getShardClient,
2512
2625
  getShardsDir: () => getShardsDir,
@@ -2515,15 +2628,18 @@ __export(shard_manager_exports, {
2515
2628
  listShards: () => listShards,
2516
2629
  shardExists: () => shardExists
2517
2630
  });
2518
- import path8 from "path";
2519
- import { existsSync as existsSync6, mkdirSync as mkdirSync3, readdirSync as readdirSync3 } from "fs";
2631
+ import path9 from "path";
2632
+ import { existsSync as existsSync8, mkdirSync as mkdirSync4, readdirSync as readdirSync3 } from "fs";
2520
2633
  import { createClient as createClient2 } from "@libsql/client";
2521
2634
  function initShardManager(encryptionKey) {
2522
2635
  _encryptionKey = encryptionKey;
2523
- if (!existsSync6(SHARDS_DIR)) {
2524
- mkdirSync3(SHARDS_DIR, { recursive: true });
2636
+ if (!existsSync8(SHARDS_DIR)) {
2637
+ mkdirSync4(SHARDS_DIR, { recursive: true });
2525
2638
  }
2526
2639
  _shardingEnabled = true;
2640
+ if (_evictionTimer) clearInterval(_evictionTimer);
2641
+ _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
2642
+ _evictionTimer.unref();
2527
2643
  }
2528
2644
  function isShardingEnabled() {
2529
2645
  return _shardingEnabled;
@@ -2540,21 +2656,28 @@ function getShardClient(projectName) {
2540
2656
  throw new Error(`Invalid project name for shard: "${projectName}"`);
2541
2657
  }
2542
2658
  const cached = _shards.get(safeName);
2543
- if (cached) return cached;
2544
- const dbPath = path8.join(SHARDS_DIR, `${safeName}.db`);
2659
+ if (cached) {
2660
+ _shardLastAccess.set(safeName, Date.now());
2661
+ return cached;
2662
+ }
2663
+ while (_shards.size >= MAX_OPEN_SHARDS) {
2664
+ evictLRU();
2665
+ }
2666
+ const dbPath = path9.join(SHARDS_DIR, `${safeName}.db`);
2545
2667
  const client = createClient2({
2546
2668
  url: `file:${dbPath}`,
2547
2669
  encryptionKey: _encryptionKey
2548
2670
  });
2549
2671
  _shards.set(safeName, client);
2672
+ _shardLastAccess.set(safeName, Date.now());
2550
2673
  return client;
2551
2674
  }
2552
2675
  function shardExists(projectName) {
2553
2676
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
2554
- return existsSync6(path8.join(SHARDS_DIR, `${safeName}.db`));
2677
+ return existsSync8(path9.join(SHARDS_DIR, `${safeName}.db`));
2555
2678
  }
2556
2679
  function listShards() {
2557
- if (!existsSync6(SHARDS_DIR)) return [];
2680
+ if (!existsSync8(SHARDS_DIR)) return [];
2558
2681
  return readdirSync3(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
2559
2682
  }
2560
2683
  async function ensureShardSchema(client) {
@@ -2606,6 +2729,8 @@ async function ensureShardSchema(client) {
2606
2729
  for (const col of [
2607
2730
  "ALTER TABLE memories ADD COLUMN task_id TEXT",
2608
2731
  "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
2732
+ "ALTER TABLE memories ADD COLUMN author_device_id TEXT",
2733
+ "ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
2609
2734
  "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
2610
2735
  "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
2611
2736
  "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
@@ -2743,21 +2868,69 @@ async function getReadyShardClient(projectName) {
2743
2868
  await ensureShardSchema(client);
2744
2869
  return client;
2745
2870
  }
2871
+ function evictLRU() {
2872
+ let oldest = null;
2873
+ let oldestTime = Infinity;
2874
+ for (const [name, time] of _shardLastAccess) {
2875
+ if (time < oldestTime) {
2876
+ oldestTime = time;
2877
+ oldest = name;
2878
+ }
2879
+ }
2880
+ if (oldest) {
2881
+ const client = _shards.get(oldest);
2882
+ if (client) {
2883
+ client.close();
2884
+ }
2885
+ _shards.delete(oldest);
2886
+ _shardLastAccess.delete(oldest);
2887
+ }
2888
+ }
2889
+ function evictIdleShards() {
2890
+ const now = Date.now();
2891
+ const toEvict = [];
2892
+ for (const [name, lastAccess] of _shardLastAccess) {
2893
+ if (now - lastAccess > SHARD_IDLE_MS) {
2894
+ toEvict.push(name);
2895
+ }
2896
+ }
2897
+ for (const name of toEvict) {
2898
+ const client = _shards.get(name);
2899
+ if (client) {
2900
+ client.close();
2901
+ }
2902
+ _shards.delete(name);
2903
+ _shardLastAccess.delete(name);
2904
+ }
2905
+ }
2906
+ function getOpenShardCount() {
2907
+ return _shards.size;
2908
+ }
2746
2909
  function disposeShards() {
2910
+ if (_evictionTimer) {
2911
+ clearInterval(_evictionTimer);
2912
+ _evictionTimer = null;
2913
+ }
2747
2914
  for (const [, client] of _shards) {
2748
2915
  client.close();
2749
2916
  }
2750
2917
  _shards.clear();
2918
+ _shardLastAccess.clear();
2751
2919
  _shardingEnabled = false;
2752
2920
  _encryptionKey = null;
2753
2921
  }
2754
- var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
2922
+ var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
2755
2923
  var init_shard_manager = __esm({
2756
2924
  "src/lib/shard-manager.ts"() {
2757
2925
  "use strict";
2758
2926
  init_config();
2759
- SHARDS_DIR = path8.join(EXE_AI_DIR, "shards");
2927
+ SHARDS_DIR = path9.join(EXE_AI_DIR, "shards");
2928
+ SHARD_IDLE_MS = 5 * 60 * 1e3;
2929
+ MAX_OPEN_SHARDS = 10;
2930
+ EVICTION_INTERVAL_MS = 60 * 1e3;
2760
2931
  _shards = /* @__PURE__ */ new Map();
2932
+ _shardLastAccess = /* @__PURE__ */ new Map();
2933
+ _evictionTimer = null;
2761
2934
  _encryptionKey = null;
2762
2935
  _shardingEnabled = false;
2763
2936
  }
@@ -3522,7 +3695,7 @@ var init_store = __esm({
3522
3695
 
3523
3696
  // src/lib/active-agent.ts
3524
3697
  init_config();
3525
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync, unlinkSync as unlinkSync2, readdirSync } from "fs";
3698
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2, readdirSync } from "fs";
3526
3699
  import { execSync as execSync3 } from "child_process";
3527
3700
  import path3 from "path";
3528
3701
 
@@ -3683,18 +3856,18 @@ function getActiveAgent() {
3683
3856
  // src/lib/identity.ts
3684
3857
  init_config();
3685
3858
  init_database();
3686
- import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
3859
+ import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
3687
3860
  import { readdirSync as readdirSync2 } from "fs";
3688
- import path6 from "path";
3861
+ import path7 from "path";
3689
3862
  import { createHash } from "crypto";
3690
- var IDENTITY_DIR2 = path6.join(EXE_AI_DIR, "identity");
3863
+ var IDENTITY_DIR2 = path7.join(EXE_AI_DIR, "identity");
3691
3864
  function ensureDir() {
3692
- if (!existsSync4(IDENTITY_DIR2)) {
3693
- mkdirSync2(IDENTITY_DIR2, { recursive: true });
3865
+ if (!existsSync6(IDENTITY_DIR2)) {
3866
+ mkdirSync3(IDENTITY_DIR2, { recursive: true });
3694
3867
  }
3695
3868
  }
3696
3869
  function identityPath(agentId) {
3697
- return path6.join(IDENTITY_DIR2, `${agentId}.md`);
3870
+ return path7.join(IDENTITY_DIR2, `${agentId}.md`);
3698
3871
  }
3699
3872
  function parseFrontmatter(raw) {
3700
3873
  const match = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
@@ -3735,8 +3908,8 @@ function contentHash(content) {
3735
3908
  }
3736
3909
  function getIdentity(agentId) {
3737
3910
  const filePath = identityPath(agentId);
3738
- if (!existsSync4(filePath)) return null;
3739
- const raw = readFileSync5(filePath, "utf-8");
3911
+ if (!existsSync6(filePath)) return null;
3912
+ const raw = readFileSync6(filePath, "utf-8");
3740
3913
  const { frontmatter, body } = parseFrontmatter(raw);
3741
3914
  return {
3742
3915
  agentId,
@@ -3836,12 +4009,12 @@ process.stdin.on("end", async () => {
3836
4009
  } catch {
3837
4010
  }
3838
4011
  try {
3839
- const { readFileSync: readFileSync6, existsSync: existsSync7 } = await import("fs");
4012
+ const { readFileSync: readFileSync7, existsSync: existsSync9 } = await import("fs");
3840
4013
  const { join } = await import("path");
3841
4014
  const os6 = await import("os");
3842
4015
  const claudeMd = join(os6.homedir(), ".claude", "CLAUDE.md");
3843
- if (existsSync7(claudeMd)) {
3844
- const content = readFileSync6(claudeMd, "utf8");
4016
+ if (existsSync9(claudeMd)) {
4017
+ const content = readFileSync7(claudeMd, "utf8");
3845
4018
  const hasOrchRules = content.includes("exe-os:orchestration-start");
3846
4019
  if (hasOrchRules) {
3847
4020
  checks.push("PASS: orchestration rules intact in CLAUDE.md");