@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
@@ -4,9 +4,34 @@ var __esm = (fn, res) => function __init() {
4
4
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
5
  };
6
6
 
7
+ // src/lib/secure-files.ts
8
+ import { chmodSync, existsSync, mkdirSync } from "fs";
9
+ import { chmod, mkdir } from "fs/promises";
10
+ function ensurePrivateDirSync(dirPath) {
11
+ mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
12
+ try {
13
+ chmodSync(dirPath, PRIVATE_DIR_MODE);
14
+ } catch {
15
+ }
16
+ }
17
+ function enforcePrivateFileSync(filePath) {
18
+ try {
19
+ if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
20
+ } catch {
21
+ }
22
+ }
23
+ var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
24
+ var init_secure_files = __esm({
25
+ "src/lib/secure-files.ts"() {
26
+ "use strict";
27
+ PRIVATE_DIR_MODE = 448;
28
+ PRIVATE_FILE_MODE = 384;
29
+ }
30
+ });
31
+
7
32
  // src/lib/config.ts
8
- import { readFile, writeFile, mkdir, chmod } from "fs/promises";
9
- import { readFileSync, existsSync, renameSync } from "fs";
33
+ import { readFile, writeFile } from "fs/promises";
34
+ import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
10
35
  import path from "path";
11
36
  import os from "os";
12
37
  function resolveDataDir() {
@@ -14,7 +39,7 @@ function resolveDataDir() {
14
39
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
15
40
  const newDir = path.join(os.homedir(), ".exe-os");
16
41
  const legacyDir = path.join(os.homedir(), ".exe-mem");
17
- if (!existsSync(newDir) && existsSync(legacyDir)) {
42
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
18
43
  try {
19
44
  renameSync(legacyDir, newDir);
20
45
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -29,6 +54,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
29
54
  var init_config = __esm({
30
55
  "src/lib/config.ts"() {
31
56
  "use strict";
57
+ init_secure_files();
32
58
  EXE_AI_DIR = resolveDataDir();
33
59
  DB_PATH = path.join(EXE_AI_DIR, "memories.db");
34
60
  MODELS_DIR = path.join(EXE_AI_DIR, "models");
@@ -123,10 +149,10 @@ var init_runtime_table = __esm({
123
149
  });
124
150
 
125
151
  // src/lib/agent-config.ts
126
- import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync2, mkdirSync } from "fs";
152
+ import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
127
153
  import path2 from "path";
128
154
  function loadAgentConfig() {
129
- if (!existsSync2(AGENT_CONFIG_PATH)) return {};
155
+ if (!existsSync3(AGENT_CONFIG_PATH)) return {};
130
156
  try {
131
157
  return JSON.parse(readFileSync2(AGENT_CONFIG_PATH, "utf-8"));
132
158
  } catch {
@@ -135,8 +161,9 @@ function loadAgentConfig() {
135
161
  }
136
162
  function saveAgentConfig(config) {
137
163
  const dir = path2.dirname(AGENT_CONFIG_PATH);
138
- if (!existsSync2(dir)) mkdirSync(dir, { recursive: true });
164
+ ensurePrivateDirSync(dir);
139
165
  writeFileSync(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
166
+ enforcePrivateFileSync(AGENT_CONFIG_PATH);
140
167
  }
141
168
  function getAgentRuntime(agentId) {
142
169
  const config = loadAgentConfig();
@@ -176,6 +203,7 @@ var init_agent_config = __esm({
176
203
  "use strict";
177
204
  init_config();
178
205
  init_runtime_table();
206
+ init_secure_files();
179
207
  AGENT_CONFIG_PATH = path2.join(EXE_AI_DIR, "agent-config.json");
180
208
  KNOWN_RUNTIMES = {
181
209
  claude: ["claude-opus-4", "claude-sonnet-4", "claude-haiku-4.5"],
@@ -197,13 +225,13 @@ init_runtime_table();
197
225
  // src/lib/employees.ts
198
226
  init_config();
199
227
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
200
- import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
228
+ import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
201
229
  import { execSync } from "child_process";
202
230
  import path3 from "path";
203
231
  import os2 from "os";
204
232
  var EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
205
233
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
206
- if (!existsSync3(employeesPath)) return [];
234
+ if (!existsSync4(employeesPath)) return [];
207
235
  try {
208
236
  return JSON.parse(readFileSync3(employeesPath, "utf-8"));
209
237
  } catch {
@@ -4,9 +4,18 @@ var __esm = (fn, res) => function __init() {
4
4
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
5
  };
6
6
 
7
+ // src/lib/secure-files.ts
8
+ import { chmodSync, existsSync, mkdirSync } from "fs";
9
+ import { chmod, mkdir } from "fs/promises";
10
+ var init_secure_files = __esm({
11
+ "src/lib/secure-files.ts"() {
12
+ "use strict";
13
+ }
14
+ });
15
+
7
16
  // src/lib/config.ts
8
- import { readFile, writeFile, mkdir, chmod } from "fs/promises";
9
- import { readFileSync, existsSync, renameSync } from "fs";
17
+ import { readFile, writeFile } from "fs/promises";
18
+ import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
10
19
  import path from "path";
11
20
  import os from "os";
12
21
  function resolveDataDir() {
@@ -14,7 +23,7 @@ function resolveDataDir() {
14
23
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
15
24
  const newDir = path.join(os.homedir(), ".exe-os");
16
25
  const legacyDir = path.join(os.homedir(), ".exe-mem");
17
- if (!existsSync(newDir) && existsSync(legacyDir)) {
26
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
18
27
  try {
19
28
  renameSync(legacyDir, newDir);
20
29
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -29,6 +38,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
29
38
  var init_config = __esm({
30
39
  "src/lib/config.ts"() {
31
40
  "use strict";
41
+ init_secure_files();
32
42
  EXE_AI_DIR = resolveDataDir();
33
43
  DB_PATH = path.join(EXE_AI_DIR, "memories.db");
34
44
  MODELS_DIR = path.join(EXE_AI_DIR, "models");
@@ -1259,7 +1269,7 @@ import { createClient } from "@libsql/client";
1259
1269
  // src/lib/employees.ts
1260
1270
  init_config();
1261
1271
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
1262
- import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
1272
+ import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
1263
1273
  import { execSync } from "child_process";
1264
1274
  import path2 from "path";
1265
1275
  import os2 from "os";
@@ -9,9 +9,47 @@ var __export = (target, all) => {
9
9
  __defProp(target, name, { get: all[name], enumerable: true });
10
10
  };
11
11
 
12
+ // src/lib/secure-files.ts
13
+ import { chmodSync, existsSync, mkdirSync } from "fs";
14
+ import { chmod, mkdir } from "fs/promises";
15
+ async function ensurePrivateDir(dirPath) {
16
+ await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
17
+ try {
18
+ await chmod(dirPath, PRIVATE_DIR_MODE);
19
+ } catch {
20
+ }
21
+ }
22
+ function ensurePrivateDirSync(dirPath) {
23
+ mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
24
+ try {
25
+ chmodSync(dirPath, PRIVATE_DIR_MODE);
26
+ } catch {
27
+ }
28
+ }
29
+ async function enforcePrivateFile(filePath) {
30
+ try {
31
+ await chmod(filePath, PRIVATE_FILE_MODE);
32
+ } catch {
33
+ }
34
+ }
35
+ function enforcePrivateFileSync(filePath) {
36
+ try {
37
+ if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
38
+ } catch {
39
+ }
40
+ }
41
+ var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
42
+ var init_secure_files = __esm({
43
+ "src/lib/secure-files.ts"() {
44
+ "use strict";
45
+ PRIVATE_DIR_MODE = 448;
46
+ PRIVATE_FILE_MODE = 384;
47
+ }
48
+ });
49
+
12
50
  // src/lib/config.ts
13
- import { readFile, writeFile, mkdir, chmod } from "fs/promises";
14
- import { readFileSync, existsSync, renameSync } from "fs";
51
+ import { readFile, writeFile } from "fs/promises";
52
+ import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
15
53
  import path from "path";
16
54
  import os from "os";
17
55
  function resolveDataDir() {
@@ -19,7 +57,7 @@ function resolveDataDir() {
19
57
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
20
58
  const newDir = path.join(os.homedir(), ".exe-os");
21
59
  const legacyDir = path.join(os.homedir(), ".exe-mem");
22
- if (!existsSync(newDir) && existsSync(legacyDir)) {
60
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
23
61
  try {
24
62
  renameSync(legacyDir, newDir);
25
63
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -82,9 +120,9 @@ function normalizeAutoUpdate(raw) {
82
120
  }
83
121
  async function loadConfig() {
84
122
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
85
- await mkdir(dir, { recursive: true });
123
+ await ensurePrivateDir(dir);
86
124
  const configPath = path.join(dir, "config.json");
87
- if (!existsSync(configPath)) {
125
+ if (!existsSync2(configPath)) {
88
126
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
89
127
  }
90
128
  const raw = await readFile(configPath, "utf-8");
@@ -97,6 +135,7 @@ async function loadConfig() {
97
135
  `);
98
136
  try {
99
137
  await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
138
+ await enforcePrivateFile(configPath);
100
139
  } catch {
101
140
  }
102
141
  }
@@ -116,6 +155,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
116
155
  var init_config = __esm({
117
156
  "src/lib/config.ts"() {
118
157
  "use strict";
158
+ init_secure_files();
119
159
  EXE_AI_DIR = resolveDataDir();
120
160
  DB_PATH = path.join(EXE_AI_DIR, "memories.db");
121
161
  MODELS_DIR = path.join(EXE_AI_DIR, "models");
@@ -194,7 +234,7 @@ var init_config = __esm({
194
234
 
195
235
  // src/lib/employees.ts
196
236
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
197
- import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
237
+ import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
198
238
  import { execSync } from "child_process";
199
239
  import path2 from "path";
200
240
  import os2 from "os";
@@ -211,7 +251,7 @@ function getCoordinatorName(employees = loadEmployeesSync()) {
211
251
  return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
212
252
  }
213
253
  async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
214
- if (!existsSync2(employeesPath)) {
254
+ if (!existsSync3(employeesPath)) {
215
255
  return [];
216
256
  }
217
257
  const raw = await readFile2(employeesPath, "utf-8");
@@ -222,7 +262,7 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
222
262
  }
223
263
  }
224
264
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
225
- if (!existsSync2(employeesPath)) return [];
265
+ if (!existsSync3(employeesPath)) return [];
226
266
  try {
227
267
  return JSON.parse(readFileSync2(employeesPath, "utf-8"));
228
268
  } catch {
@@ -244,13 +284,50 @@ var init_employees = __esm({
244
284
  }
245
285
  });
246
286
 
287
+ // src/lib/daemon-auth.ts
288
+ import crypto from "crypto";
289
+ import path3 from "path";
290
+ import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
291
+ function normalizeToken(token) {
292
+ if (!token) return null;
293
+ const trimmed = token.trim();
294
+ return trimmed.length > 0 ? trimmed : null;
295
+ }
296
+ function readDaemonToken() {
297
+ try {
298
+ if (!existsSync4(DAEMON_TOKEN_PATH)) return null;
299
+ return normalizeToken(readFileSync3(DAEMON_TOKEN_PATH, "utf8"));
300
+ } catch {
301
+ return null;
302
+ }
303
+ }
304
+ function ensureDaemonToken(seed) {
305
+ const existing = readDaemonToken();
306
+ if (existing) return existing;
307
+ const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
308
+ ensurePrivateDirSync(EXE_AI_DIR);
309
+ writeFileSync2(DAEMON_TOKEN_PATH, `${token}
310
+ `, "utf8");
311
+ enforcePrivateFileSync(DAEMON_TOKEN_PATH);
312
+ return token;
313
+ }
314
+ var DAEMON_TOKEN_PATH;
315
+ var init_daemon_auth = __esm({
316
+ "src/lib/daemon-auth.ts"() {
317
+ "use strict";
318
+ init_config();
319
+ init_secure_files();
320
+ DAEMON_TOKEN_PATH = path3.join(EXE_AI_DIR, "exed.token");
321
+ }
322
+ });
323
+
247
324
  // src/lib/exe-daemon-client.ts
248
325
  import net from "net";
249
326
  import os3 from "os";
250
327
  import { spawn } from "child_process";
251
328
  import { randomUUID as randomUUID2 } from "crypto";
252
- import { existsSync as existsSync3, unlinkSync as unlinkSync2, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
253
- import path3 from "path";
329
+ import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
330
+ import path4 from "path";
254
331
  import { fileURLToPath } from "url";
255
332
  function handleData(chunk) {
256
333
  _buffer += chunk.toString();
@@ -278,9 +355,9 @@ function handleData(chunk) {
278
355
  }
279
356
  }
280
357
  function cleanupStaleFiles() {
281
- if (existsSync3(PID_PATH)) {
358
+ if (existsSync5(PID_PATH)) {
282
359
  try {
283
- const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
360
+ const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
284
361
  if (pid > 0) {
285
362
  try {
286
363
  process.kill(pid, 0);
@@ -301,11 +378,11 @@ function cleanupStaleFiles() {
301
378
  }
302
379
  }
303
380
  function findPackageRoot() {
304
- let dir = path3.dirname(fileURLToPath(import.meta.url));
305
- const { root } = path3.parse(dir);
381
+ let dir = path4.dirname(fileURLToPath(import.meta.url));
382
+ const { root } = path4.parse(dir);
306
383
  while (dir !== root) {
307
- if (existsSync3(path3.join(dir, "package.json"))) return dir;
308
- dir = path3.dirname(dir);
384
+ if (existsSync5(path4.join(dir, "package.json"))) return dir;
385
+ dir = path4.dirname(dir);
309
386
  }
310
387
  return null;
311
388
  }
@@ -331,16 +408,17 @@ function spawnDaemon() {
331
408
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
332
409
  return;
333
410
  }
334
- const daemonPath = path3.join(pkgRoot, "dist", "lib", "exe-daemon.js");
335
- if (!existsSync3(daemonPath)) {
411
+ const daemonPath = path4.join(pkgRoot, "dist", "lib", "exe-daemon.js");
412
+ if (!existsSync5(daemonPath)) {
336
413
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
337
414
  `);
338
415
  return;
339
416
  }
340
417
  const resolvedPath = daemonPath;
418
+ const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
341
419
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
342
420
  `);
343
- const logPath = path3.join(path3.dirname(SOCKET_PATH), "exed.log");
421
+ const logPath = path4.join(path4.dirname(SOCKET_PATH), "exed.log");
344
422
  let stderrFd = "ignore";
345
423
  try {
346
424
  stderrFd = openSync(logPath, "a");
@@ -358,7 +436,8 @@ function spawnDaemon() {
358
436
  TMUX_PANE: void 0,
359
437
  // Prevents resolveExeSession() from scoping to one session
360
438
  EXE_DAEMON_SOCK: SOCKET_PATH,
361
- EXE_DAEMON_PID: PID_PATH
439
+ EXE_DAEMON_PID: PID_PATH,
440
+ [DAEMON_TOKEN_ENV]: daemonToken
362
441
  }
363
442
  });
364
443
  child.unref();
@@ -468,13 +547,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
468
547
  return;
469
548
  }
470
549
  const id = randomUUID2();
550
+ const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
471
551
  const timer = setTimeout(() => {
472
552
  _pending.delete(id);
473
553
  resolve({ error: "Request timeout" });
474
554
  }, timeoutMs);
475
555
  _pending.set(id, { resolve, timer });
476
556
  try {
477
- _socket.write(JSON.stringify({ id, ...payload }) + "\n");
557
+ _socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
478
558
  } catch {
479
559
  clearTimeout(timer);
480
560
  _pending.delete(id);
@@ -503,9 +583,9 @@ function killAndRespawnDaemon() {
503
583
  }
504
584
  try {
505
585
  process.stderr.write("[exed-client] Killing daemon for restart...\n");
506
- if (existsSync3(PID_PATH)) {
586
+ if (existsSync5(PID_PATH)) {
507
587
  try {
508
- const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
588
+ const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
509
589
  if (pid > 0) {
510
590
  try {
511
591
  process.kill(pid, "SIGKILL");
@@ -609,17 +689,19 @@ async function embedViaClient(text, priority = "high") {
609
689
  );
610
690
  return !result.error && result.vectors?.[0] ? result.vectors[0] : null;
611
691
  }
612
- 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;
692
+ 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;
613
693
  var init_exe_daemon_client = __esm({
614
694
  "src/lib/exe-daemon-client.ts"() {
615
695
  "use strict";
616
696
  init_config();
617
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path3.join(EXE_AI_DIR, "exed.sock");
618
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path3.join(EXE_AI_DIR, "exed.pid");
619
- SPAWN_LOCK_PATH = path3.join(EXE_AI_DIR, "exed-spawn.lock");
697
+ init_daemon_auth();
698
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path4.join(EXE_AI_DIR, "exed.sock");
699
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path4.join(EXE_AI_DIR, "exed.pid");
700
+ SPAWN_LOCK_PATH = path4.join(EXE_AI_DIR, "exed-spawn.lock");
620
701
  SPAWN_LOCK_STALE_MS = 3e4;
621
702
  CONNECT_TIMEOUT_MS = 15e3;
622
703
  REQUEST_TIMEOUT_MS = 3e4;
704
+ DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
623
705
  _socket = null;
624
706
  _connected = false;
625
707
  _buffer = "";
@@ -691,7 +773,7 @@ var init_db_retry = __esm({
691
773
 
692
774
  // src/lib/database-adapter.ts
693
775
  import os4 from "os";
694
- import path4 from "path";
776
+ import path5 from "path";
695
777
  import { createRequire } from "module";
696
778
  import { pathToFileURL } from "url";
697
779
  function quotedIdentifier(identifier) {
@@ -1002,8 +1084,8 @@ async function loadPrismaClient() {
1002
1084
  }
1003
1085
  return new PrismaClient2();
1004
1086
  }
1005
- const exeDbRoot = process.env.EXE_DB_ROOT ?? path4.join(os4.homedir(), "exe-db");
1006
- const requireFromExeDb = createRequire(path4.join(exeDbRoot, "package.json"));
1087
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path5.join(os4.homedir(), "exe-db");
1088
+ const requireFromExeDb = createRequire(path5.join(exeDbRoot, "package.json"));
1007
1089
  const prismaEntry = requireFromExeDb.resolve("@prisma/client");
1008
1090
  const module = await import(pathToFileURL(prismaEntry).href);
1009
1091
  const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
@@ -1615,6 +1697,7 @@ async function ensureSchema() {
1615
1697
  project TEXT NOT NULL,
1616
1698
  summary TEXT NOT NULL,
1617
1699
  task_file TEXT,
1700
+ session_scope TEXT,
1618
1701
  read INTEGER NOT NULL DEFAULT 0,
1619
1702
  created_at TEXT NOT NULL
1620
1703
  );
@@ -1623,7 +1706,7 @@ async function ensureSchema() {
1623
1706
  ON notifications(read);
1624
1707
 
1625
1708
  CREATE INDEX IF NOT EXISTS idx_notifications_agent
1626
- ON notifications(agent_id);
1709
+ ON notifications(agent_id, session_scope);
1627
1710
 
1628
1711
  CREATE INDEX IF NOT EXISTS idx_notifications_task_file
1629
1712
  ON notifications(task_file);
@@ -1661,6 +1744,7 @@ async function ensureSchema() {
1661
1744
  target_agent TEXT NOT NULL,
1662
1745
  target_project TEXT,
1663
1746
  target_device TEXT NOT NULL DEFAULT 'local',
1747
+ session_scope TEXT,
1664
1748
  content TEXT NOT NULL,
1665
1749
  priority TEXT DEFAULT 'normal',
1666
1750
  status TEXT DEFAULT 'pending',
@@ -1674,10 +1758,31 @@ async function ensureSchema() {
1674
1758
  );
1675
1759
 
1676
1760
  CREATE INDEX IF NOT EXISTS idx_messages_target
1677
- ON messages(target_agent, status);
1761
+ ON messages(target_agent, session_scope, status);
1678
1762
 
1679
1763
  CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
1680
- ON messages(target_agent, from_agent, server_seq);
1764
+ ON messages(target_agent, session_scope, from_agent, server_seq);
1765
+ `);
1766
+ try {
1767
+ await client.execute({
1768
+ sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
1769
+ args: []
1770
+ });
1771
+ } catch {
1772
+ }
1773
+ try {
1774
+ await client.execute({
1775
+ sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
1776
+ args: []
1777
+ });
1778
+ } catch {
1779
+ }
1780
+ await client.executeMultiple(`
1781
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
1782
+ ON notifications(agent_id, session_scope, read, created_at);
1783
+
1784
+ CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
1785
+ ON messages(target_agent, session_scope, status, created_at);
1681
1786
  `);
1682
1787
  try {
1683
1788
  await client.execute({
@@ -2261,6 +2366,13 @@ async function ensureSchema() {
2261
2366
  } catch {
2262
2367
  }
2263
2368
  }
2369
+ try {
2370
+ await client.execute({
2371
+ sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
2372
+ args: []
2373
+ });
2374
+ } catch {
2375
+ }
2264
2376
  }
2265
2377
  var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso;
2266
2378
  var init_database = __esm({
@@ -2283,6 +2395,7 @@ var shard_manager_exports = {};
2283
2395
  __export(shard_manager_exports, {
2284
2396
  disposeShards: () => disposeShards,
2285
2397
  ensureShardSchema: () => ensureShardSchema,
2398
+ getOpenShardCount: () => getOpenShardCount,
2286
2399
  getReadyShardClient: () => getReadyShardClient,
2287
2400
  getShardClient: () => getShardClient,
2288
2401
  getShardsDir: () => getShardsDir,
@@ -2291,15 +2404,18 @@ __export(shard_manager_exports, {
2291
2404
  listShards: () => listShards,
2292
2405
  shardExists: () => shardExists
2293
2406
  });
2294
- import path6 from "path";
2295
- import { existsSync as existsSync5, mkdirSync, readdirSync } from "fs";
2407
+ import path7 from "path";
2408
+ import { existsSync as existsSync7, mkdirSync as mkdirSync2, readdirSync } from "fs";
2296
2409
  import { createClient as createClient2 } from "@libsql/client";
2297
2410
  function initShardManager(encryptionKey) {
2298
2411
  _encryptionKey = encryptionKey;
2299
- if (!existsSync5(SHARDS_DIR)) {
2300
- mkdirSync(SHARDS_DIR, { recursive: true });
2412
+ if (!existsSync7(SHARDS_DIR)) {
2413
+ mkdirSync2(SHARDS_DIR, { recursive: true });
2301
2414
  }
2302
2415
  _shardingEnabled = true;
2416
+ if (_evictionTimer) clearInterval(_evictionTimer);
2417
+ _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
2418
+ _evictionTimer.unref();
2303
2419
  }
2304
2420
  function isShardingEnabled() {
2305
2421
  return _shardingEnabled;
@@ -2316,21 +2432,28 @@ function getShardClient(projectName) {
2316
2432
  throw new Error(`Invalid project name for shard: "${projectName}"`);
2317
2433
  }
2318
2434
  const cached = _shards.get(safeName);
2319
- if (cached) return cached;
2320
- const dbPath = path6.join(SHARDS_DIR, `${safeName}.db`);
2435
+ if (cached) {
2436
+ _shardLastAccess.set(safeName, Date.now());
2437
+ return cached;
2438
+ }
2439
+ while (_shards.size >= MAX_OPEN_SHARDS) {
2440
+ evictLRU();
2441
+ }
2442
+ const dbPath = path7.join(SHARDS_DIR, `${safeName}.db`);
2321
2443
  const client = createClient2({
2322
2444
  url: `file:${dbPath}`,
2323
2445
  encryptionKey: _encryptionKey
2324
2446
  });
2325
2447
  _shards.set(safeName, client);
2448
+ _shardLastAccess.set(safeName, Date.now());
2326
2449
  return client;
2327
2450
  }
2328
2451
  function shardExists(projectName) {
2329
2452
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
2330
- return existsSync5(path6.join(SHARDS_DIR, `${safeName}.db`));
2453
+ return existsSync7(path7.join(SHARDS_DIR, `${safeName}.db`));
2331
2454
  }
2332
2455
  function listShards() {
2333
- if (!existsSync5(SHARDS_DIR)) return [];
2456
+ if (!existsSync7(SHARDS_DIR)) return [];
2334
2457
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
2335
2458
  }
2336
2459
  async function ensureShardSchema(client) {
@@ -2382,6 +2505,8 @@ async function ensureShardSchema(client) {
2382
2505
  for (const col of [
2383
2506
  "ALTER TABLE memories ADD COLUMN task_id TEXT",
2384
2507
  "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
2508
+ "ALTER TABLE memories ADD COLUMN author_device_id TEXT",
2509
+ "ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
2385
2510
  "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
2386
2511
  "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
2387
2512
  "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
@@ -2519,21 +2644,69 @@ async function getReadyShardClient(projectName) {
2519
2644
  await ensureShardSchema(client);
2520
2645
  return client;
2521
2646
  }
2647
+ function evictLRU() {
2648
+ let oldest = null;
2649
+ let oldestTime = Infinity;
2650
+ for (const [name, time] of _shardLastAccess) {
2651
+ if (time < oldestTime) {
2652
+ oldestTime = time;
2653
+ oldest = name;
2654
+ }
2655
+ }
2656
+ if (oldest) {
2657
+ const client = _shards.get(oldest);
2658
+ if (client) {
2659
+ client.close();
2660
+ }
2661
+ _shards.delete(oldest);
2662
+ _shardLastAccess.delete(oldest);
2663
+ }
2664
+ }
2665
+ function evictIdleShards() {
2666
+ const now = Date.now();
2667
+ const toEvict = [];
2668
+ for (const [name, lastAccess] of _shardLastAccess) {
2669
+ if (now - lastAccess > SHARD_IDLE_MS) {
2670
+ toEvict.push(name);
2671
+ }
2672
+ }
2673
+ for (const name of toEvict) {
2674
+ const client = _shards.get(name);
2675
+ if (client) {
2676
+ client.close();
2677
+ }
2678
+ _shards.delete(name);
2679
+ _shardLastAccess.delete(name);
2680
+ }
2681
+ }
2682
+ function getOpenShardCount() {
2683
+ return _shards.size;
2684
+ }
2522
2685
  function disposeShards() {
2686
+ if (_evictionTimer) {
2687
+ clearInterval(_evictionTimer);
2688
+ _evictionTimer = null;
2689
+ }
2523
2690
  for (const [, client] of _shards) {
2524
2691
  client.close();
2525
2692
  }
2526
2693
  _shards.clear();
2694
+ _shardLastAccess.clear();
2527
2695
  _shardingEnabled = false;
2528
2696
  _encryptionKey = null;
2529
2697
  }
2530
- var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
2698
+ var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
2531
2699
  var init_shard_manager = __esm({
2532
2700
  "src/lib/shard-manager.ts"() {
2533
2701
  "use strict";
2534
2702
  init_config();
2535
- SHARDS_DIR = path6.join(EXE_AI_DIR, "shards");
2703
+ SHARDS_DIR = path7.join(EXE_AI_DIR, "shards");
2704
+ SHARD_IDLE_MS = 5 * 60 * 1e3;
2705
+ MAX_OPEN_SHARDS = 10;
2706
+ EVICTION_INTERVAL_MS = 60 * 1e3;
2536
2707
  _shards = /* @__PURE__ */ new Map();
2708
+ _shardLastAccess = /* @__PURE__ */ new Map();
2709
+ _evictionTimer = null;
2537
2710
  _encryptionKey = null;
2538
2711
  _shardingEnabled = false;
2539
2712
  }
@@ -2880,16 +3053,16 @@ init_database();
2880
3053
 
2881
3054
  // src/lib/keychain.ts
2882
3055
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
2883
- import { existsSync as existsSync4 } from "fs";
2884
- import path5 from "path";
3056
+ import { existsSync as existsSync6 } from "fs";
3057
+ import path6 from "path";
2885
3058
  import os5 from "os";
2886
3059
  var SERVICE = "exe-mem";
2887
3060
  var ACCOUNT = "master-key";
2888
3061
  function getKeyDir() {
2889
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path5.join(os5.homedir(), ".exe-os");
3062
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path6.join(os5.homedir(), ".exe-os");
2890
3063
  }
2891
3064
  function getKeyPath() {
2892
- return path5.join(getKeyDir(), "master.key");
3065
+ return path6.join(getKeyDir(), "master.key");
2893
3066
  }
2894
3067
  async function tryKeytar() {
2895
3068
  try {
@@ -2910,7 +3083,7 @@ async function getMasterKey() {
2910
3083
  }
2911
3084
  }
2912
3085
  const keyPath = getKeyPath();
2913
- if (!existsSync4(keyPath)) {
3086
+ if (!existsSync6(keyPath)) {
2914
3087
  process.stderr.write(
2915
3088
  `[keychain] Key not found at ${keyPath} (HOME=${os5.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
2916
3089
  `