@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
@@ -1,14 +1,35 @@
1
1
  // src/lib/config.ts
2
- import { readFile, writeFile, mkdir, chmod } from "fs/promises";
3
- import { readFileSync, existsSync, renameSync } from "fs";
2
+ import { readFile, writeFile } from "fs/promises";
3
+ import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
4
4
  import path from "path";
5
5
  import os from "os";
6
+
7
+ // src/lib/secure-files.ts
8
+ import { chmodSync, existsSync, mkdirSync } from "fs";
9
+ import { chmod, mkdir } from "fs/promises";
10
+ var PRIVATE_DIR_MODE = 448;
11
+ var PRIVATE_FILE_MODE = 384;
12
+ async function ensurePrivateDir(dirPath) {
13
+ await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
14
+ try {
15
+ await chmod(dirPath, PRIVATE_DIR_MODE);
16
+ } catch {
17
+ }
18
+ }
19
+ async function enforcePrivateFile(filePath) {
20
+ try {
21
+ await chmod(filePath, PRIVATE_FILE_MODE);
22
+ } catch {
23
+ }
24
+ }
25
+
26
+ // src/lib/config.ts
6
27
  function resolveDataDir() {
7
28
  if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
8
29
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
9
30
  const newDir = path.join(os.homedir(), ".exe-os");
10
31
  const legacyDir = path.join(os.homedir(), ".exe-mem");
11
- if (!existsSync(newDir) && existsSync(legacyDir)) {
32
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
12
33
  try {
13
34
  renameSync(legacyDir, newDir);
14
35
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -144,9 +165,9 @@ function normalizeAutoUpdate(raw) {
144
165
  }
145
166
  async function loadConfig() {
146
167
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
147
- await mkdir(dir, { recursive: true });
168
+ await ensurePrivateDir(dir);
148
169
  const configPath = path.join(dir, "config.json");
149
- if (!existsSync(configPath)) {
170
+ if (!existsSync2(configPath)) {
150
171
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
151
172
  }
152
173
  const raw = await readFile(configPath, "utf-8");
@@ -159,6 +180,7 @@ async function loadConfig() {
159
180
  `);
160
181
  try {
161
182
  await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
183
+ await enforcePrivateFile(configPath);
162
184
  } catch {
163
185
  }
164
186
  }
@@ -177,7 +199,7 @@ async function loadConfig() {
177
199
  function loadConfigSync() {
178
200
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
179
201
  const configPath = path.join(dir, "config.json");
180
- if (!existsSync(configPath)) {
202
+ if (!existsSync2(configPath)) {
181
203
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
182
204
  }
183
205
  try {
@@ -195,12 +217,10 @@ function loadConfigSync() {
195
217
  }
196
218
  async function saveConfig(config) {
197
219
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
198
- await mkdir(dir, { recursive: true });
220
+ await ensurePrivateDir(dir);
199
221
  const configPath = path.join(dir, "config.json");
200
222
  await writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
201
- if (config.cloud?.apiKey) {
202
- await chmod(configPath, 384);
203
- }
223
+ await enforcePrivateFile(configPath);
204
224
  }
205
225
  async function loadConfigFrom(configPath) {
206
226
  const raw = await readFile(configPath, "utf-8");
@@ -10,9 +10,18 @@ var init_db_retry = __esm({
10
10
  }
11
11
  });
12
12
 
13
+ // src/lib/secure-files.ts
14
+ import { chmodSync, existsSync, mkdirSync } from "fs";
15
+ import { chmod, mkdir } from "fs/promises";
16
+ var init_secure_files = __esm({
17
+ "src/lib/secure-files.ts"() {
18
+ "use strict";
19
+ }
20
+ });
21
+
13
22
  // src/lib/config.ts
14
- import { readFile, writeFile, mkdir, chmod } from "fs/promises";
15
- import { readFileSync, existsSync, renameSync } from "fs";
23
+ import { readFile, writeFile } from "fs/promises";
24
+ import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
16
25
  import path from "path";
17
26
  import os from "os";
18
27
  function resolveDataDir() {
@@ -20,7 +29,7 @@ function resolveDataDir() {
20
29
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
21
30
  const newDir = path.join(os.homedir(), ".exe-os");
22
31
  const legacyDir = path.join(os.homedir(), ".exe-mem");
23
- if (!existsSync(newDir) && existsSync(legacyDir)) {
32
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
24
33
  try {
25
34
  renameSync(legacyDir, newDir);
26
35
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -35,6 +44,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
35
44
  var init_config = __esm({
36
45
  "src/lib/config.ts"() {
37
46
  "use strict";
47
+ init_secure_files();
38
48
  EXE_AI_DIR = resolveDataDir();
39
49
  DB_PATH = path.join(EXE_AI_DIR, "memories.db");
40
50
  MODELS_DIR = path.join(EXE_AI_DIR, "models");
@@ -103,7 +113,7 @@ var init_config = __esm({
103
113
 
104
114
  // src/lib/employees.ts
105
115
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
106
- import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
116
+ import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
107
117
  import { execSync } from "child_process";
108
118
  import path2 from "path";
109
119
  import os2 from "os";
@@ -124,7 +134,7 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
124
134
  return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
125
135
  }
126
136
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
127
- if (!existsSync2(employeesPath)) return [];
137
+ if (!existsSync3(employeesPath)) return [];
128
138
  try {
129
139
  return JSON.parse(readFileSync2(employeesPath, "utf-8"));
130
140
  } catch {
@@ -184,7 +194,7 @@ init_database();
184
194
 
185
195
  // src/lib/keychain.ts
186
196
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
187
- import { existsSync as existsSync3 } from "fs";
197
+ import { existsSync as existsSync4 } from "fs";
188
198
  import path4 from "path";
189
199
  import os4 from "os";
190
200
 
@@ -8,9 +8,34 @@ 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
+ function ensurePrivateDirSync(dirPath) {
15
+ mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
16
+ try {
17
+ chmodSync(dirPath, PRIVATE_DIR_MODE);
18
+ } catch {
19
+ }
20
+ }
21
+ function enforcePrivateFileSync(filePath) {
22
+ try {
23
+ if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
24
+ } catch {
25
+ }
26
+ }
27
+ var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
28
+ var init_secure_files = __esm({
29
+ "src/lib/secure-files.ts"() {
30
+ "use strict";
31
+ PRIVATE_DIR_MODE = 448;
32
+ PRIVATE_FILE_MODE = 384;
33
+ }
34
+ });
35
+
11
36
  // src/lib/config.ts
12
- import { readFile, writeFile, mkdir, chmod } from "fs/promises";
13
- import { readFileSync, existsSync, renameSync } from "fs";
37
+ import { readFile, writeFile } from "fs/promises";
38
+ import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
14
39
  import path from "path";
15
40
  import os from "os";
16
41
  function resolveDataDir() {
@@ -18,7 +43,7 @@ function resolveDataDir() {
18
43
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
19
44
  const newDir = path.join(os.homedir(), ".exe-os");
20
45
  const legacyDir = path.join(os.homedir(), ".exe-mem");
21
- if (!existsSync(newDir) && existsSync(legacyDir)) {
46
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
22
47
  try {
23
48
  renameSync(legacyDir, newDir);
24
49
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -33,6 +58,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
33
58
  var init_config = __esm({
34
59
  "src/lib/config.ts"() {
35
60
  "use strict";
61
+ init_secure_files();
36
62
  EXE_AI_DIR = resolveDataDir();
37
63
  DB_PATH = path.join(EXE_AI_DIR, "memories.db");
38
64
  MODELS_DIR = path.join(EXE_AI_DIR, "models");
@@ -99,13 +125,50 @@ var init_config = __esm({
99
125
  }
100
126
  });
101
127
 
128
+ // src/lib/daemon-auth.ts
129
+ import crypto from "crypto";
130
+ import path4 from "path";
131
+ import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
132
+ function normalizeToken(token) {
133
+ if (!token) return null;
134
+ const trimmed = token.trim();
135
+ return trimmed.length > 0 ? trimmed : null;
136
+ }
137
+ function readDaemonToken() {
138
+ try {
139
+ if (!existsSync4(DAEMON_TOKEN_PATH)) return null;
140
+ return normalizeToken(readFileSync3(DAEMON_TOKEN_PATH, "utf8"));
141
+ } catch {
142
+ return null;
143
+ }
144
+ }
145
+ function ensureDaemonToken(seed) {
146
+ const existing = readDaemonToken();
147
+ if (existing) return existing;
148
+ const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
149
+ ensurePrivateDirSync(EXE_AI_DIR);
150
+ writeFileSync2(DAEMON_TOKEN_PATH, `${token}
151
+ `, "utf8");
152
+ enforcePrivateFileSync(DAEMON_TOKEN_PATH);
153
+ return token;
154
+ }
155
+ var DAEMON_TOKEN_PATH;
156
+ var init_daemon_auth = __esm({
157
+ "src/lib/daemon-auth.ts"() {
158
+ "use strict";
159
+ init_config();
160
+ init_secure_files();
161
+ DAEMON_TOKEN_PATH = path4.join(EXE_AI_DIR, "exed.token");
162
+ }
163
+ });
164
+
102
165
  // src/lib/exe-daemon-client.ts
103
166
  import net from "net";
104
167
  import os4 from "os";
105
168
  import { spawn } from "child_process";
106
169
  import { randomUUID } from "crypto";
107
- import { existsSync as existsSync3, unlinkSync as unlinkSync2, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
108
- import path4 from "path";
170
+ import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
171
+ import path5 from "path";
109
172
  import { fileURLToPath } from "url";
110
173
  function handleData(chunk) {
111
174
  _buffer += chunk.toString();
@@ -133,9 +196,9 @@ function handleData(chunk) {
133
196
  }
134
197
  }
135
198
  function cleanupStaleFiles() {
136
- if (existsSync3(PID_PATH)) {
199
+ if (existsSync5(PID_PATH)) {
137
200
  try {
138
- const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
201
+ const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
139
202
  if (pid > 0) {
140
203
  try {
141
204
  process.kill(pid, 0);
@@ -156,11 +219,11 @@ function cleanupStaleFiles() {
156
219
  }
157
220
  }
158
221
  function findPackageRoot() {
159
- let dir = path4.dirname(fileURLToPath(import.meta.url));
160
- const { root } = path4.parse(dir);
222
+ let dir = path5.dirname(fileURLToPath(import.meta.url));
223
+ const { root } = path5.parse(dir);
161
224
  while (dir !== root) {
162
- if (existsSync3(path4.join(dir, "package.json"))) return dir;
163
- dir = path4.dirname(dir);
225
+ if (existsSync5(path5.join(dir, "package.json"))) return dir;
226
+ dir = path5.dirname(dir);
164
227
  }
165
228
  return null;
166
229
  }
@@ -186,16 +249,17 @@ function spawnDaemon() {
186
249
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
187
250
  return;
188
251
  }
189
- const daemonPath = path4.join(pkgRoot, "dist", "lib", "exe-daemon.js");
190
- if (!existsSync3(daemonPath)) {
252
+ const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
253
+ if (!existsSync5(daemonPath)) {
191
254
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
192
255
  `);
193
256
  return;
194
257
  }
195
258
  const resolvedPath = daemonPath;
259
+ const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
196
260
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
197
261
  `);
198
- const logPath = path4.join(path4.dirname(SOCKET_PATH), "exed.log");
262
+ const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
199
263
  let stderrFd = "ignore";
200
264
  try {
201
265
  stderrFd = openSync(logPath, "a");
@@ -213,7 +277,8 @@ function spawnDaemon() {
213
277
  TMUX_PANE: void 0,
214
278
  // Prevents resolveExeSession() from scoping to one session
215
279
  EXE_DAEMON_SOCK: SOCKET_PATH,
216
- EXE_DAEMON_PID: PID_PATH
280
+ EXE_DAEMON_PID: PID_PATH,
281
+ [DAEMON_TOKEN_ENV]: daemonToken
217
282
  }
218
283
  });
219
284
  child.unref();
@@ -320,13 +385,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
320
385
  return;
321
386
  }
322
387
  const id = randomUUID();
388
+ const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
323
389
  const timer = setTimeout(() => {
324
390
  _pending.delete(id);
325
391
  resolve({ error: "Request timeout" });
326
392
  }, timeoutMs);
327
393
  _pending.set(id, { resolve, timer });
328
394
  try {
329
- _socket.write(JSON.stringify({ id, ...payload }) + "\n");
395
+ _socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
330
396
  } catch {
331
397
  clearTimeout(timer);
332
398
  _pending.delete(id);
@@ -337,17 +403,19 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
337
403
  function isClientConnected() {
338
404
  return _connected;
339
405
  }
340
- var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
406
+ 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;
341
407
  var init_exe_daemon_client = __esm({
342
408
  "src/lib/exe-daemon-client.ts"() {
343
409
  "use strict";
344
410
  init_config();
345
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path4.join(EXE_AI_DIR, "exed.sock");
346
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path4.join(EXE_AI_DIR, "exed.pid");
347
- SPAWN_LOCK_PATH = path4.join(EXE_AI_DIR, "exed-spawn.lock");
411
+ init_daemon_auth();
412
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path5.join(EXE_AI_DIR, "exed.sock");
413
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path5.join(EXE_AI_DIR, "exed.pid");
414
+ SPAWN_LOCK_PATH = path5.join(EXE_AI_DIR, "exed-spawn.lock");
348
415
  SPAWN_LOCK_STALE_MS = 3e4;
349
416
  CONNECT_TIMEOUT_MS = 15e3;
350
417
  REQUEST_TIMEOUT_MS = 3e4;
418
+ DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
351
419
  _socket = null;
352
420
  _connected = false;
353
421
  _buffer = "";
@@ -610,7 +678,7 @@ function wrapWithRetry(client) {
610
678
  // src/lib/employees.ts
611
679
  init_config();
612
680
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
613
- import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
681
+ import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
614
682
  import { execSync } from "child_process";
615
683
  import path2 from "path";
616
684
  import os2 from "os";
@@ -630,7 +698,7 @@ function getCoordinatorName(employees = loadEmployeesSync()) {
630
698
  return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
631
699
  }
632
700
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
633
- if (!existsSync2(employeesPath)) return [];
701
+ if (!existsSync3(employeesPath)) return [];
634
702
  try {
635
703
  return JSON.parse(readFileSync2(employeesPath, "utf-8"));
636
704
  } catch {
@@ -1581,6 +1649,7 @@ async function ensureSchema() {
1581
1649
  project TEXT NOT NULL,
1582
1650
  summary TEXT NOT NULL,
1583
1651
  task_file TEXT,
1652
+ session_scope TEXT,
1584
1653
  read INTEGER NOT NULL DEFAULT 0,
1585
1654
  created_at TEXT NOT NULL
1586
1655
  );
@@ -1589,7 +1658,7 @@ async function ensureSchema() {
1589
1658
  ON notifications(read);
1590
1659
 
1591
1660
  CREATE INDEX IF NOT EXISTS idx_notifications_agent
1592
- ON notifications(agent_id);
1661
+ ON notifications(agent_id, session_scope);
1593
1662
 
1594
1663
  CREATE INDEX IF NOT EXISTS idx_notifications_task_file
1595
1664
  ON notifications(task_file);
@@ -1627,6 +1696,7 @@ async function ensureSchema() {
1627
1696
  target_agent TEXT NOT NULL,
1628
1697
  target_project TEXT,
1629
1698
  target_device TEXT NOT NULL DEFAULT 'local',
1699
+ session_scope TEXT,
1630
1700
  content TEXT NOT NULL,
1631
1701
  priority TEXT DEFAULT 'normal',
1632
1702
  status TEXT DEFAULT 'pending',
@@ -1640,10 +1710,31 @@ async function ensureSchema() {
1640
1710
  );
1641
1711
 
1642
1712
  CREATE INDEX IF NOT EXISTS idx_messages_target
1643
- ON messages(target_agent, status);
1713
+ ON messages(target_agent, session_scope, status);
1644
1714
 
1645
1715
  CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
1646
- ON messages(target_agent, from_agent, server_seq);
1716
+ ON messages(target_agent, session_scope, from_agent, server_seq);
1717
+ `);
1718
+ try {
1719
+ await client.execute({
1720
+ sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
1721
+ args: []
1722
+ });
1723
+ } catch {
1724
+ }
1725
+ try {
1726
+ await client.execute({
1727
+ sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
1728
+ args: []
1729
+ });
1730
+ } catch {
1731
+ }
1732
+ await client.executeMultiple(`
1733
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
1734
+ ON notifications(agent_id, session_scope, read, created_at);
1735
+
1736
+ CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
1737
+ ON messages(target_agent, session_scope, status, created_at);
1647
1738
  `);
1648
1739
  try {
1649
1740
  await client.execute({
@@ -2227,6 +2318,13 @@ async function ensureSchema() {
2227
2318
  } catch {
2228
2319
  }
2229
2320
  }
2321
+ try {
2322
+ await client.execute({
2323
+ sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
2324
+ args: []
2325
+ });
2326
+ } catch {
2327
+ }
2230
2328
  }
2231
2329
  var disposeTurso = disposeDatabase;
2232
2330
  async function disposeDatabase() {
@@ -3,21 +3,42 @@ import net from "net";
3
3
  import os2 from "os";
4
4
  import { spawn } from "child_process";
5
5
  import { randomUUID } from "crypto";
6
- import { existsSync as existsSync2, unlinkSync, readFileSync as readFileSync2, openSync, closeSync, statSync } from "fs";
7
- import path2 from "path";
6
+ import { existsSync as existsSync4, unlinkSync, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
7
+ import path3 from "path";
8
8
  import { fileURLToPath } from "url";
9
9
 
10
10
  // src/lib/config.ts
11
- import { readFile, writeFile, mkdir, chmod } from "fs/promises";
12
- import { readFileSync, existsSync, renameSync } from "fs";
11
+ import { readFile, writeFile } from "fs/promises";
12
+ import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
13
13
  import path from "path";
14
14
  import os from "os";
15
+
16
+ // src/lib/secure-files.ts
17
+ import { chmodSync, existsSync, mkdirSync } from "fs";
18
+ import { chmod, mkdir } from "fs/promises";
19
+ var PRIVATE_DIR_MODE = 448;
20
+ var PRIVATE_FILE_MODE = 384;
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
+ function enforcePrivateFileSync(filePath) {
29
+ try {
30
+ if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
31
+ } catch {
32
+ }
33
+ }
34
+
35
+ // src/lib/config.ts
15
36
  function resolveDataDir() {
16
37
  if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
17
38
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
18
39
  const newDir = path.join(os.homedir(), ".exe-os");
19
40
  const legacyDir = path.join(os.homedir(), ".exe-mem");
20
- if (!existsSync(newDir) && existsSync(legacyDir)) {
41
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
21
42
  try {
22
43
  renameSync(legacyDir, newDir);
23
44
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -92,13 +113,43 @@ var DEFAULT_CONFIG = {
92
113
  }
93
114
  };
94
115
 
116
+ // src/lib/daemon-auth.ts
117
+ import crypto from "crypto";
118
+ import path2 from "path";
119
+ import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync } from "fs";
120
+ var DAEMON_TOKEN_PATH = path2.join(EXE_AI_DIR, "exed.token");
121
+ function normalizeToken(token) {
122
+ if (!token) return null;
123
+ const trimmed = token.trim();
124
+ return trimmed.length > 0 ? trimmed : null;
125
+ }
126
+ function readDaemonToken() {
127
+ try {
128
+ if (!existsSync3(DAEMON_TOKEN_PATH)) return null;
129
+ return normalizeToken(readFileSync2(DAEMON_TOKEN_PATH, "utf8"));
130
+ } catch {
131
+ return null;
132
+ }
133
+ }
134
+ function ensureDaemonToken(seed) {
135
+ const existing = readDaemonToken();
136
+ if (existing) return existing;
137
+ const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
138
+ ensurePrivateDirSync(EXE_AI_DIR);
139
+ writeFileSync(DAEMON_TOKEN_PATH, `${token}
140
+ `, "utf8");
141
+ enforcePrivateFileSync(DAEMON_TOKEN_PATH);
142
+ return token;
143
+ }
144
+
95
145
  // src/lib/exe-daemon-client.ts
96
- var SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path2.join(EXE_AI_DIR, "exed.sock");
97
- var PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path2.join(EXE_AI_DIR, "exed.pid");
98
- var SPAWN_LOCK_PATH = path2.join(EXE_AI_DIR, "exed-spawn.lock");
146
+ var SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path3.join(EXE_AI_DIR, "exed.sock");
147
+ var PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path3.join(EXE_AI_DIR, "exed.pid");
148
+ var SPAWN_LOCK_PATH = path3.join(EXE_AI_DIR, "exed-spawn.lock");
99
149
  var SPAWN_LOCK_STALE_MS = 3e4;
100
150
  var CONNECT_TIMEOUT_MS = 15e3;
101
151
  var REQUEST_TIMEOUT_MS = 3e4;
152
+ var DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
102
153
  var _socket = null;
103
154
  var _connected = false;
104
155
  var _buffer = "";
@@ -130,9 +181,9 @@ function handleData(chunk) {
130
181
  }
131
182
  }
132
183
  function cleanupStaleFiles() {
133
- if (existsSync2(PID_PATH)) {
184
+ if (existsSync4(PID_PATH)) {
134
185
  try {
135
- const pid = parseInt(readFileSync2(PID_PATH, "utf8").trim(), 10);
186
+ const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
136
187
  if (pid > 0) {
137
188
  try {
138
189
  process.kill(pid, 0);
@@ -153,11 +204,11 @@ function cleanupStaleFiles() {
153
204
  }
154
205
  }
155
206
  function findPackageRoot() {
156
- let dir = path2.dirname(fileURLToPath(import.meta.url));
157
- const { root } = path2.parse(dir);
207
+ let dir = path3.dirname(fileURLToPath(import.meta.url));
208
+ const { root } = path3.parse(dir);
158
209
  while (dir !== root) {
159
- if (existsSync2(path2.join(dir, "package.json"))) return dir;
160
- dir = path2.dirname(dir);
210
+ if (existsSync4(path3.join(dir, "package.json"))) return dir;
211
+ dir = path3.dirname(dir);
161
212
  }
162
213
  return null;
163
214
  }
@@ -183,16 +234,17 @@ function spawnDaemon() {
183
234
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
184
235
  return;
185
236
  }
186
- const daemonPath = path2.join(pkgRoot, "dist", "lib", "exe-daemon.js");
187
- if (!existsSync2(daemonPath)) {
237
+ const daemonPath = path3.join(pkgRoot, "dist", "lib", "exe-daemon.js");
238
+ if (!existsSync4(daemonPath)) {
188
239
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
189
240
  `);
190
241
  return;
191
242
  }
192
243
  const resolvedPath = daemonPath;
244
+ const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
193
245
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
194
246
  `);
195
- const logPath = path2.join(path2.dirname(SOCKET_PATH), "exed.log");
247
+ const logPath = path3.join(path3.dirname(SOCKET_PATH), "exed.log");
196
248
  let stderrFd = "ignore";
197
249
  try {
198
250
  stderrFd = openSync(logPath, "a");
@@ -210,7 +262,8 @@ function spawnDaemon() {
210
262
  TMUX_PANE: void 0,
211
263
  // Prevents resolveExeSession() from scoping to one session
212
264
  EXE_DAEMON_SOCK: SOCKET_PATH,
213
- EXE_DAEMON_PID: PID_PATH
265
+ EXE_DAEMON_PID: PID_PATH,
266
+ [DAEMON_TOKEN_ENV]: daemonToken
214
267
  }
215
268
  });
216
269
  child.unref();
@@ -317,13 +370,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
317
370
  return;
318
371
  }
319
372
  const id = randomUUID();
373
+ const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
320
374
  const timer = setTimeout(() => {
321
375
  _pending.delete(id);
322
376
  resolve({ error: "Request timeout" });
323
377
  }, timeoutMs);
324
378
  _pending.set(id, { resolve, timer });
325
379
  try {
326
- _socket.write(JSON.stringify({ id, ...payload }) + "\n");
380
+ _socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
327
381
  } catch {
328
382
  clearTimeout(timer);
329
383
  _pending.delete(id);