@askexenow/exe-os 0.9.7 → 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 +953 -105
  2. package/dist/bin/backfill-responses.js +952 -104
  3. package/dist/bin/backfill-vectors.js +956 -108
  4. package/dist/bin/cleanup-stale-review-tasks.js +802 -58
  5. package/dist/bin/cli.js +2292 -1070
  6. package/dist/bin/exe-agent-config.js +157 -101
  7. package/dist/bin/exe-agent.js +55 -29
  8. package/dist/bin/exe-assign.js +940 -92
  9. package/dist/bin/exe-boot.js +1424 -442
  10. package/dist/bin/exe-call.js +240 -141
  11. package/dist/bin/exe-cloud.js +198 -70
  12. package/dist/bin/exe-dispatch.js +951 -192
  13. package/dist/bin/exe-doctor.js +791 -51
  14. package/dist/bin/exe-export-behaviors.js +790 -42
  15. package/dist/bin/exe-forget.js +771 -31
  16. package/dist/bin/exe-gateway.js +1592 -521
  17. package/dist/bin/exe-heartbeat.js +850 -109
  18. package/dist/bin/exe-kill.js +783 -35
  19. package/dist/bin/exe-launch-agent.js +1030 -107
  20. package/dist/bin/exe-link.js +916 -110
  21. package/dist/bin/exe-new-employee.js +526 -217
  22. package/dist/bin/exe-pending-messages.js +1046 -62
  23. package/dist/bin/exe-pending-notifications.js +1318 -111
  24. package/dist/bin/exe-pending-reviews.js +1040 -72
  25. package/dist/bin/exe-rename.js +772 -59
  26. package/dist/bin/exe-review.js +772 -32
  27. package/dist/bin/exe-search.js +982 -128
  28. package/dist/bin/exe-session-cleanup.js +1180 -306
  29. package/dist/bin/exe-settings.js +185 -105
  30. package/dist/bin/exe-start-codex.js +886 -132
  31. package/dist/bin/exe-start-opencode.js +873 -119
  32. package/dist/bin/exe-status.js +803 -59
  33. package/dist/bin/exe-team.js +772 -32
  34. package/dist/bin/git-sweep.js +1046 -223
  35. package/dist/bin/graph-backfill.js +779 -31
  36. package/dist/bin/graph-export.js +785 -37
  37. package/dist/bin/install.js +632 -200
  38. package/dist/bin/scan-tasks.js +1055 -232
  39. package/dist/bin/setup.js +1419 -320
  40. package/dist/bin/shard-migrate.js +783 -35
  41. package/dist/bin/update.js +138 -49
  42. package/dist/bin/wiki-sync.js +782 -34
  43. package/dist/gateway/index.js +1444 -449
  44. package/dist/hooks/bug-report-worker.js +1141 -269
  45. package/dist/hooks/codex-stop-task-finalizer.js +4678 -0
  46. package/dist/hooks/commit-complete.js +1044 -221
  47. package/dist/hooks/error-recall.js +989 -135
  48. package/dist/hooks/exe-heartbeat-hook.js +99 -75
  49. package/dist/hooks/ingest-worker.js +4176 -3226
  50. package/dist/hooks/ingest.js +920 -168
  51. package/dist/hooks/instructions-loaded.js +874 -70
  52. package/dist/hooks/notification.js +860 -56
  53. package/dist/hooks/post-compact.js +881 -73
  54. package/dist/hooks/pre-compact.js +1050 -227
  55. package/dist/hooks/pre-tool-use.js +1084 -159
  56. package/dist/hooks/prompt-ingest-worker.js +1089 -164
  57. package/dist/hooks/prompt-submit.js +1469 -515
  58. package/dist/hooks/response-ingest-worker.js +1104 -179
  59. package/dist/hooks/session-end.js +1085 -251
  60. package/dist/hooks/session-start.js +1241 -231
  61. package/dist/hooks/stop.js +935 -109
  62. package/dist/hooks/subagent-stop.js +881 -73
  63. package/dist/hooks/summary-worker.js +1323 -307
  64. package/dist/index.js +1449 -452
  65. package/dist/lib/agent-config.js +28 -6
  66. package/dist/lib/cloud-sync.js +909 -115
  67. package/dist/lib/config.js +30 -10
  68. package/dist/lib/consolidation.js +42 -9
  69. package/dist/lib/database.js +739 -33
  70. package/dist/lib/db-daemon-client.js +73 -19
  71. package/dist/lib/db.js +2359 -0
  72. package/dist/lib/device-registry.js +760 -47
  73. package/dist/lib/embedder.js +201 -73
  74. package/dist/lib/employee-templates.js +30 -4
  75. package/dist/lib/employees.js +290 -86
  76. package/dist/lib/exe-daemon-client.js +187 -83
  77. package/dist/lib/exe-daemon.js +1696 -616
  78. package/dist/lib/hybrid-search.js +982 -128
  79. package/dist/lib/identity.js +43 -13
  80. package/dist/lib/license.js +133 -48
  81. package/dist/lib/messaging.js +167 -80
  82. package/dist/lib/reminders.js +35 -5
  83. package/dist/lib/schedules.js +772 -32
  84. package/dist/lib/skill-learning.js +54 -7
  85. package/dist/lib/store.js +779 -31
  86. package/dist/lib/task-router.js +94 -73
  87. package/dist/lib/tasks.js +298 -225
  88. package/dist/lib/tmux-routing.js +246 -172
  89. package/dist/lib/token-spend.js +52 -14
  90. package/dist/mcp/server.js +2893 -850
  91. package/dist/mcp/tools/complete-reminder.js +35 -5
  92. package/dist/mcp/tools/create-reminder.js +35 -5
  93. package/dist/mcp/tools/create-task.js +507 -323
  94. package/dist/mcp/tools/deactivate-behavior.js +40 -10
  95. package/dist/mcp/tools/list-reminders.js +35 -5
  96. package/dist/mcp/tools/list-tasks.js +277 -104
  97. package/dist/mcp/tools/send-message.js +129 -56
  98. package/dist/mcp/tools/update-task.js +1864 -188
  99. package/dist/runtime/index.js +1083 -259
  100. package/dist/tui/App.js +1501 -434
  101. package/package.json +3 -2
@@ -8,6 +8,44 @@ 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
50
  var config_exports = {};
13
51
  __export(config_exports, {
@@ -24,8 +62,8 @@ __export(config_exports, {
24
62
  migrateConfig: () => migrateConfig,
25
63
  saveConfig: () => saveConfig
26
64
  });
27
- import { readFile, writeFile, mkdir, chmod } from "fs/promises";
28
- import { readFileSync, existsSync, renameSync } from "fs";
65
+ import { readFile, writeFile } from "fs/promises";
66
+ import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
29
67
  import path from "path";
30
68
  import os from "os";
31
69
  function resolveDataDir() {
@@ -33,7 +71,7 @@ function resolveDataDir() {
33
71
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
34
72
  const newDir = path.join(os.homedir(), ".exe-os");
35
73
  const legacyDir = path.join(os.homedir(), ".exe-mem");
36
- if (!existsSync(newDir) && existsSync(legacyDir)) {
74
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
37
75
  try {
38
76
  renameSync(legacyDir, newDir);
39
77
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -96,9 +134,9 @@ function normalizeAutoUpdate(raw) {
96
134
  }
97
135
  async function loadConfig() {
98
136
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
99
- await mkdir(dir, { recursive: true });
137
+ await ensurePrivateDir(dir);
100
138
  const configPath = path.join(dir, "config.json");
101
- if (!existsSync(configPath)) {
139
+ if (!existsSync2(configPath)) {
102
140
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
103
141
  }
104
142
  const raw = await readFile(configPath, "utf-8");
@@ -111,6 +149,7 @@ async function loadConfig() {
111
149
  `);
112
150
  try {
113
151
  await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
152
+ await enforcePrivateFile(configPath);
114
153
  } catch {
115
154
  }
116
155
  }
@@ -129,7 +168,7 @@ async function loadConfig() {
129
168
  function loadConfigSync() {
130
169
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
131
170
  const configPath = path.join(dir, "config.json");
132
- if (!existsSync(configPath)) {
171
+ if (!existsSync2(configPath)) {
133
172
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
134
173
  }
135
174
  try {
@@ -147,12 +186,10 @@ function loadConfigSync() {
147
186
  }
148
187
  async function saveConfig(config) {
149
188
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
150
- await mkdir(dir, { recursive: true });
189
+ await ensurePrivateDir(dir);
151
190
  const configPath = path.join(dir, "config.json");
152
191
  await writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
153
- if (config.cloud?.apiKey) {
154
- await chmod(configPath, 384);
155
- }
192
+ await enforcePrivateFile(configPath);
156
193
  }
157
194
  async function loadConfigFrom(configPath) {
158
195
  const raw = await readFile(configPath, "utf-8");
@@ -172,6 +209,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
172
209
  var init_config = __esm({
173
210
  "src/lib/config.ts"() {
174
211
  "use strict";
212
+ init_secure_files();
175
213
  EXE_AI_DIR = resolveDataDir();
176
214
  DB_PATH = path.join(EXE_AI_DIR, "memories.db");
177
215
  MODELS_DIR = path.join(EXE_AI_DIR, "models");
@@ -314,7 +352,7 @@ var init_db_retry = __esm({
314
352
 
315
353
  // src/lib/employees.ts
316
354
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
317
- import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
355
+ import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
318
356
  import { execSync } from "child_process";
319
357
  import path2 from "path";
320
358
  import os2 from "os";
@@ -331,7 +369,7 @@ function getCoordinatorName(employees = loadEmployeesSync()) {
331
369
  return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
332
370
  }
333
371
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
334
- if (!existsSync2(employeesPath)) return [];
372
+ if (!existsSync3(employeesPath)) return [];
335
373
  try {
336
374
  return JSON.parse(readFileSync2(employeesPath, "utf-8"));
337
375
  } catch {
@@ -341,7 +379,7 @@ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
341
379
  function getEmployee(employees, name) {
342
380
  return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
343
381
  }
344
- var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE;
382
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, IDENTITY_DIR;
345
383
  var init_employees = __esm({
346
384
  "src/lib/employees.ts"() {
347
385
  "use strict";
@@ -349,12 +387,609 @@ var init_employees = __esm({
349
387
  EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
350
388
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
351
389
  COORDINATOR_ROLE = "COO";
390
+ IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
391
+ }
392
+ });
393
+
394
+ // src/lib/database-adapter.ts
395
+ import os3 from "os";
396
+ import path3 from "path";
397
+ import { createRequire } from "module";
398
+ import { pathToFileURL } from "url";
399
+ function quotedIdentifier(identifier) {
400
+ return `"${identifier.replace(/"/g, '""')}"`;
401
+ }
402
+ function unqualifiedTableName(name) {
403
+ const raw = name.trim().replace(/^"|"$/g, "");
404
+ const parts = raw.split(".");
405
+ return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
406
+ }
407
+ function stripTrailingSemicolon(sql) {
408
+ return sql.trim().replace(/;+\s*$/u, "");
409
+ }
410
+ function appendClause(sql, clause) {
411
+ const trimmed = stripTrailingSemicolon(sql);
412
+ const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
413
+ if (!returningMatch) {
414
+ return `${trimmed}${clause}`;
415
+ }
416
+ const idx = returningMatch.index;
417
+ return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
418
+ }
419
+ function normalizeStatement(stmt) {
420
+ if (typeof stmt === "string") {
421
+ return { kind: "positional", sql: stmt, args: [] };
422
+ }
423
+ const sql = stmt.sql;
424
+ if (Array.isArray(stmt.args) || stmt.args === void 0) {
425
+ return { kind: "positional", sql, args: stmt.args ?? [] };
426
+ }
427
+ return { kind: "named", sql, args: stmt.args };
428
+ }
429
+ function rewriteBooleanLiterals(sql) {
430
+ let out = sql;
431
+ for (const column of BOOLEAN_COLUMN_NAMES) {
432
+ const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
433
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
434
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
435
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
436
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
437
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
438
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
439
+ }
440
+ return out;
441
+ }
442
+ function rewriteInsertOrIgnore(sql) {
443
+ if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
444
+ return sql;
445
+ }
446
+ const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
447
+ return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
448
+ }
449
+ function rewriteInsertOrReplace(sql) {
450
+ const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
451
+ if (!match) {
452
+ return sql;
453
+ }
454
+ const rawTable = match[1];
455
+ const rawColumns = match[2];
456
+ const remainder = match[3];
457
+ const tableName = unqualifiedTableName(rawTable);
458
+ const conflictKeys = UPSERT_KEYS[tableName];
459
+ if (!conflictKeys?.length) {
460
+ return sql;
461
+ }
462
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
463
+ const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
464
+ const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
465
+ const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
466
+ return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
467
+ }
468
+ function rewriteSql(sql) {
469
+ let out = sql;
470
+ out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
471
+ out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
472
+ out = rewriteBooleanLiterals(out);
473
+ out = rewriteInsertOrReplace(out);
474
+ out = rewriteInsertOrIgnore(out);
475
+ return stripTrailingSemicolon(out);
476
+ }
477
+ function toBoolean(value) {
478
+ if (value === null || value === void 0) return value;
479
+ if (typeof value === "boolean") return value;
480
+ if (typeof value === "number") return value !== 0;
481
+ if (typeof value === "bigint") return value !== 0n;
482
+ if (typeof value === "string") {
483
+ const normalized = value.trim().toLowerCase();
484
+ if (normalized === "0" || normalized === "false") return false;
485
+ if (normalized === "1" || normalized === "true") return true;
486
+ }
487
+ return Boolean(value);
488
+ }
489
+ function countQuestionMarks(sql, end) {
490
+ let count = 0;
491
+ let inSingle = false;
492
+ let inDouble = false;
493
+ let inLineComment = false;
494
+ let inBlockComment = false;
495
+ for (let i = 0; i < end; i++) {
496
+ const ch = sql[i];
497
+ const next = sql[i + 1];
498
+ if (inLineComment) {
499
+ if (ch === "\n") inLineComment = false;
500
+ continue;
501
+ }
502
+ if (inBlockComment) {
503
+ if (ch === "*" && next === "/") {
504
+ inBlockComment = false;
505
+ i += 1;
506
+ }
507
+ continue;
508
+ }
509
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
510
+ inLineComment = true;
511
+ i += 1;
512
+ continue;
513
+ }
514
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
515
+ inBlockComment = true;
516
+ i += 1;
517
+ continue;
518
+ }
519
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
520
+ inSingle = !inSingle;
521
+ continue;
522
+ }
523
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
524
+ inDouble = !inDouble;
525
+ continue;
526
+ }
527
+ if (!inSingle && !inDouble && ch === "?") {
528
+ count += 1;
529
+ }
530
+ }
531
+ return count;
532
+ }
533
+ function findBooleanPlaceholderIndexes(sql) {
534
+ const indexes = /* @__PURE__ */ new Set();
535
+ for (const column of BOOLEAN_COLUMN_NAMES) {
536
+ const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
537
+ for (const match of sql.matchAll(pattern)) {
538
+ const matchText = match[0];
539
+ const qIndex = match.index + matchText.lastIndexOf("?");
540
+ indexes.add(countQuestionMarks(sql, qIndex + 1));
541
+ }
542
+ }
543
+ return indexes;
544
+ }
545
+ function coerceInsertBooleanArgs(sql, args) {
546
+ const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
547
+ if (!match) return;
548
+ const rawTable = match[1];
549
+ const rawColumns = match[2];
550
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
551
+ if (!boolColumns?.size) return;
552
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
553
+ for (const [index, column] of columns.entries()) {
554
+ if (boolColumns.has(column) && index < args.length) {
555
+ args[index] = toBoolean(args[index]);
556
+ }
557
+ }
558
+ }
559
+ function coerceUpdateBooleanArgs(sql, args) {
560
+ const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
561
+ if (!match) return;
562
+ const rawTable = match[1];
563
+ const setClause = match[2];
564
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
565
+ if (!boolColumns?.size) return;
566
+ const assignments = setClause.split(",");
567
+ let placeholderIndex = 0;
568
+ for (const assignment of assignments) {
569
+ if (!assignment.includes("?")) continue;
570
+ placeholderIndex += 1;
571
+ const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
572
+ if (colMatch && boolColumns.has(colMatch[1])) {
573
+ args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
574
+ }
575
+ }
576
+ }
577
+ function coerceBooleanArgs(sql, args) {
578
+ const nextArgs = [...args];
579
+ coerceInsertBooleanArgs(sql, nextArgs);
580
+ coerceUpdateBooleanArgs(sql, nextArgs);
581
+ const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
582
+ for (const index of placeholderIndexes) {
583
+ if (index > 0 && index <= nextArgs.length) {
584
+ nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
585
+ }
586
+ }
587
+ return nextArgs;
588
+ }
589
+ function convertQuestionMarksToDollarParams(sql) {
590
+ let out = "";
591
+ let placeholder = 0;
592
+ let inSingle = false;
593
+ let inDouble = false;
594
+ let inLineComment = false;
595
+ let inBlockComment = false;
596
+ for (let i = 0; i < sql.length; i++) {
597
+ const ch = sql[i];
598
+ const next = sql[i + 1];
599
+ if (inLineComment) {
600
+ out += ch;
601
+ if (ch === "\n") inLineComment = false;
602
+ continue;
603
+ }
604
+ if (inBlockComment) {
605
+ out += ch;
606
+ if (ch === "*" && next === "/") {
607
+ out += next;
608
+ inBlockComment = false;
609
+ i += 1;
610
+ }
611
+ continue;
612
+ }
613
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
614
+ out += ch + next;
615
+ inLineComment = true;
616
+ i += 1;
617
+ continue;
618
+ }
619
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
620
+ out += ch + next;
621
+ inBlockComment = true;
622
+ i += 1;
623
+ continue;
624
+ }
625
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
626
+ inSingle = !inSingle;
627
+ out += ch;
628
+ continue;
629
+ }
630
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
631
+ inDouble = !inDouble;
632
+ out += ch;
633
+ continue;
634
+ }
635
+ if (!inSingle && !inDouble && ch === "?") {
636
+ placeholder += 1;
637
+ out += `$${placeholder}`;
638
+ continue;
639
+ }
640
+ out += ch;
641
+ }
642
+ return out;
643
+ }
644
+ function translateStatementForPostgres(stmt) {
645
+ const normalized = normalizeStatement(stmt);
646
+ if (normalized.kind === "named") {
647
+ throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
648
+ }
649
+ const rewrittenSql = rewriteSql(normalized.sql);
650
+ const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
651
+ return {
652
+ sql: convertQuestionMarksToDollarParams(rewrittenSql),
653
+ args: coercedArgs
654
+ };
655
+ }
656
+ function shouldBypassPostgres(stmt) {
657
+ const normalized = normalizeStatement(stmt);
658
+ if (normalized.kind === "named") {
659
+ return true;
660
+ }
661
+ return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
662
+ }
663
+ function shouldFallbackOnError(error) {
664
+ const message = error instanceof Error ? error.message : String(error);
665
+ return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
666
+ }
667
+ function isReadQuery(sql) {
668
+ const trimmed = sql.trimStart();
669
+ return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
670
+ }
671
+ function buildRow(row, columns) {
672
+ const values = columns.map((column) => row[column]);
673
+ return Object.assign(values, row);
674
+ }
675
+ function buildResultSet(rows, rowsAffected = 0) {
676
+ const columns = rows[0] ? Object.keys(rows[0]) : [];
677
+ const resultRows = rows.map((row) => buildRow(row, columns));
678
+ return {
679
+ columns,
680
+ columnTypes: columns.map(() => ""),
681
+ rows: resultRows,
682
+ rowsAffected,
683
+ lastInsertRowid: void 0,
684
+ toJSON() {
685
+ return {
686
+ columns,
687
+ columnTypes: columns.map(() => ""),
688
+ rows,
689
+ rowsAffected,
690
+ lastInsertRowid: void 0
691
+ };
692
+ }
693
+ };
694
+ }
695
+ async function loadPrismaClient() {
696
+ if (!prismaClientPromise) {
697
+ prismaClientPromise = (async () => {
698
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
699
+ if (explicitPath) {
700
+ const module2 = await import(pathToFileURL(explicitPath).href);
701
+ const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
702
+ if (!PrismaClient2) {
703
+ throw new Error(`No PrismaClient export found at ${explicitPath}`);
704
+ }
705
+ return new PrismaClient2();
706
+ }
707
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path3.join(os3.homedir(), "exe-db");
708
+ const requireFromExeDb = createRequire(path3.join(exeDbRoot, "package.json"));
709
+ const prismaEntry = requireFromExeDb.resolve("@prisma/client");
710
+ const module = await import(pathToFileURL(prismaEntry).href);
711
+ const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
712
+ if (!PrismaClient) {
713
+ throw new Error(`No PrismaClient export found in ${prismaEntry}`);
714
+ }
715
+ return new PrismaClient();
716
+ })();
717
+ }
718
+ return prismaClientPromise;
719
+ }
720
+ async function ensureCompatibilityViews(prisma) {
721
+ if (!compatibilityBootstrapPromise) {
722
+ compatibilityBootstrapPromise = (async () => {
723
+ for (const mapping of VIEW_MAPPINGS) {
724
+ const relation = mapping.source.replace(/"/g, "");
725
+ const rows = await prisma.$queryRawUnsafe(
726
+ "SELECT to_regclass($1) AS regclass",
727
+ relation
728
+ );
729
+ if (!rows[0]?.regclass) {
730
+ continue;
731
+ }
732
+ await prisma.$executeRawUnsafe(
733
+ `CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
734
+ );
735
+ }
736
+ })();
737
+ }
738
+ return compatibilityBootstrapPromise;
739
+ }
740
+ async function executeOnPrisma(executor, stmt) {
741
+ const translated = translateStatementForPostgres(stmt);
742
+ if (isReadQuery(translated.sql)) {
743
+ const rows = await executor.$queryRawUnsafe(
744
+ translated.sql,
745
+ ...translated.args
746
+ );
747
+ return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
748
+ }
749
+ const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
750
+ return buildResultSet([], rowsAffected);
751
+ }
752
+ function splitSqlStatements(sql) {
753
+ const parts = [];
754
+ let current = "";
755
+ let inSingle = false;
756
+ let inDouble = false;
757
+ let inLineComment = false;
758
+ let inBlockComment = false;
759
+ for (let i = 0; i < sql.length; i++) {
760
+ const ch = sql[i];
761
+ const next = sql[i + 1];
762
+ if (inLineComment) {
763
+ current += ch;
764
+ if (ch === "\n") inLineComment = false;
765
+ continue;
766
+ }
767
+ if (inBlockComment) {
768
+ current += ch;
769
+ if (ch === "*" && next === "/") {
770
+ current += next;
771
+ inBlockComment = false;
772
+ i += 1;
773
+ }
774
+ continue;
775
+ }
776
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
777
+ current += ch + next;
778
+ inLineComment = true;
779
+ i += 1;
780
+ continue;
781
+ }
782
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
783
+ current += ch + next;
784
+ inBlockComment = true;
785
+ i += 1;
786
+ continue;
787
+ }
788
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
789
+ inSingle = !inSingle;
790
+ current += ch;
791
+ continue;
792
+ }
793
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
794
+ inDouble = !inDouble;
795
+ current += ch;
796
+ continue;
797
+ }
798
+ if (!inSingle && !inDouble && ch === ";") {
799
+ if (current.trim()) {
800
+ parts.push(current.trim());
801
+ }
802
+ current = "";
803
+ continue;
804
+ }
805
+ current += ch;
806
+ }
807
+ if (current.trim()) {
808
+ parts.push(current.trim());
809
+ }
810
+ return parts;
811
+ }
812
+ async function createPrismaDbAdapter(fallbackClient) {
813
+ const prisma = await loadPrismaClient();
814
+ await ensureCompatibilityViews(prisma);
815
+ let closed = false;
816
+ let adapter;
817
+ const fallbackExecute = async (stmt, error) => {
818
+ if (!fallbackClient) {
819
+ if (error) throw error;
820
+ throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
821
+ }
822
+ if (error) {
823
+ process.stderr.write(
824
+ `[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
825
+ `
826
+ );
827
+ }
828
+ return fallbackClient.execute(stmt);
829
+ };
830
+ adapter = {
831
+ async execute(stmt) {
832
+ if (shouldBypassPostgres(stmt)) {
833
+ return fallbackExecute(stmt);
834
+ }
835
+ try {
836
+ return await executeOnPrisma(prisma, stmt);
837
+ } catch (error) {
838
+ if (shouldFallbackOnError(error)) {
839
+ return fallbackExecute(stmt, error);
840
+ }
841
+ throw error;
842
+ }
843
+ },
844
+ async batch(stmts, mode) {
845
+ if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
846
+ if (!fallbackClient) {
847
+ throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
848
+ }
849
+ return fallbackClient.batch(stmts, mode);
850
+ }
851
+ try {
852
+ if (prisma.$transaction) {
853
+ return await prisma.$transaction(async (tx) => {
854
+ const results2 = [];
855
+ for (const stmt of stmts) {
856
+ results2.push(await executeOnPrisma(tx, stmt));
857
+ }
858
+ return results2;
859
+ });
860
+ }
861
+ const results = [];
862
+ for (const stmt of stmts) {
863
+ results.push(await executeOnPrisma(prisma, stmt));
864
+ }
865
+ return results;
866
+ } catch (error) {
867
+ if (fallbackClient && shouldFallbackOnError(error)) {
868
+ process.stderr.write(
869
+ `[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
870
+ `
871
+ );
872
+ return fallbackClient.batch(stmts, mode);
873
+ }
874
+ throw error;
875
+ }
876
+ },
877
+ async migrate(stmts) {
878
+ if (fallbackClient) {
879
+ return fallbackClient.migrate(stmts);
880
+ }
881
+ return adapter.batch(stmts, "deferred");
882
+ },
883
+ async transaction(mode) {
884
+ if (!fallbackClient) {
885
+ throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
886
+ }
887
+ return fallbackClient.transaction(mode);
888
+ },
889
+ async executeMultiple(sql) {
890
+ if (fallbackClient && shouldBypassPostgres(sql)) {
891
+ return fallbackClient.executeMultiple(sql);
892
+ }
893
+ for (const statement of splitSqlStatements(sql)) {
894
+ await adapter.execute(statement);
895
+ }
896
+ },
897
+ async sync() {
898
+ if (fallbackClient) {
899
+ return fallbackClient.sync();
900
+ }
901
+ return { frame_no: 0, frames_synced: 0 };
902
+ },
903
+ close() {
904
+ closed = true;
905
+ prismaClientPromise = null;
906
+ compatibilityBootstrapPromise = null;
907
+ void prisma.$disconnect?.();
908
+ },
909
+ get closed() {
910
+ return closed;
911
+ },
912
+ get protocol() {
913
+ return "prisma-postgres";
914
+ }
915
+ };
916
+ return adapter;
917
+ }
918
+ var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
919
+ var init_database_adapter = __esm({
920
+ "src/lib/database-adapter.ts"() {
921
+ "use strict";
922
+ VIEW_MAPPINGS = [
923
+ { view: "memories", source: "memory.memory_records" },
924
+ { view: "tasks", source: "memory.tasks" },
925
+ { view: "behaviors", source: "memory.behaviors" },
926
+ { view: "entities", source: "memory.entities" },
927
+ { view: "relationships", source: "memory.relationships" },
928
+ { view: "entity_memories", source: "memory.entity_memories" },
929
+ { view: "entity_aliases", source: "memory.entity_aliases" },
930
+ { view: "notifications", source: "memory.notifications" },
931
+ { view: "messages", source: "memory.messages" },
932
+ { view: "users", source: "wiki.users" },
933
+ { view: "workspaces", source: "wiki.workspaces" },
934
+ { view: "workspace_users", source: "wiki.workspace_users" },
935
+ { view: "documents", source: "wiki.workspace_documents" },
936
+ { view: "chats", source: "wiki.workspace_chats" }
937
+ ];
938
+ UPSERT_KEYS = {
939
+ memories: ["id"],
940
+ tasks: ["id"],
941
+ behaviors: ["id"],
942
+ entities: ["id"],
943
+ relationships: ["id"],
944
+ entity_aliases: ["alias"],
945
+ notifications: ["id"],
946
+ messages: ["id"],
947
+ users: ["id"],
948
+ workspaces: ["id"],
949
+ workspace_users: ["id"],
950
+ documents: ["id"],
951
+ chats: ["id"]
952
+ };
953
+ BOOLEAN_COLUMNS_BY_TABLE = {
954
+ memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
955
+ behaviors: /* @__PURE__ */ new Set(["active"]),
956
+ notifications: /* @__PURE__ */ new Set(["read"]),
957
+ users: /* @__PURE__ */ new Set(["has_personal_memory"])
958
+ };
959
+ BOOLEAN_COLUMN_NAMES = new Set(
960
+ Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
961
+ );
962
+ IMMEDIATE_FALLBACK_PATTERNS = [
963
+ /\bPRAGMA\b/i,
964
+ /\bsqlite_master\b/i,
965
+ /(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
966
+ /\bMATCH\b/i,
967
+ /\bvector_distance_cos\s*\(/i,
968
+ /\bjson_extract\s*\(/i,
969
+ /\bjulianday\s*\(/i,
970
+ /\bstrftime\s*\(/i,
971
+ /\blast_insert_rowid\s*\(/i
972
+ ];
973
+ prismaClientPromise = null;
974
+ compatibilityBootstrapPromise = null;
352
975
  }
353
976
  });
354
977
 
355
978
  // src/lib/database.ts
356
979
  import { createClient } from "@libsql/client";
357
980
  async function initDatabase(config) {
981
+ if (_walCheckpointTimer) {
982
+ clearInterval(_walCheckpointTimer);
983
+ _walCheckpointTimer = null;
984
+ }
985
+ if (_daemonClient) {
986
+ _daemonClient.close();
987
+ _daemonClient = null;
988
+ }
989
+ if (_adapterClient && _adapterClient !== _resilientClient) {
990
+ _adapterClient.close();
991
+ }
992
+ _adapterClient = null;
358
993
  if (_client) {
359
994
  _client.close();
360
995
  _client = null;
@@ -368,6 +1003,7 @@ async function initDatabase(config) {
368
1003
  }
369
1004
  _client = createClient(opts);
370
1005
  _resilientClient = wrapWithRetry(_client);
1006
+ _adapterClient = _resilientClient;
371
1007
  _client.execute("PRAGMA busy_timeout = 30000").catch(() => {
372
1008
  });
373
1009
  _client.execute("PRAGMA journal_mode = WAL").catch(() => {
@@ -378,11 +1014,17 @@ async function initDatabase(config) {
378
1014
  });
379
1015
  }, 3e4);
380
1016
  _walCheckpointTimer.unref();
1017
+ if (process.env.DATABASE_URL) {
1018
+ _adapterClient = await createPrismaDbAdapter(_resilientClient);
1019
+ }
381
1020
  }
382
1021
  function getClient() {
383
- if (!_resilientClient) {
1022
+ if (!_adapterClient) {
384
1023
  throw new Error("Database client not initialized. Call initDatabase() first.");
385
1024
  }
1025
+ if (process.env.DATABASE_URL) {
1026
+ return _adapterClient;
1027
+ }
386
1028
  if (process.env.EXE_IS_DAEMON === "1") {
387
1029
  return _resilientClient;
388
1030
  }
@@ -675,6 +1317,7 @@ async function ensureSchema() {
675
1317
  project TEXT NOT NULL,
676
1318
  summary TEXT NOT NULL,
677
1319
  task_file TEXT,
1320
+ session_scope TEXT,
678
1321
  read INTEGER NOT NULL DEFAULT 0,
679
1322
  created_at TEXT NOT NULL
680
1323
  );
@@ -683,7 +1326,7 @@ async function ensureSchema() {
683
1326
  ON notifications(read);
684
1327
 
685
1328
  CREATE INDEX IF NOT EXISTS idx_notifications_agent
686
- ON notifications(agent_id);
1329
+ ON notifications(agent_id, session_scope);
687
1330
 
688
1331
  CREATE INDEX IF NOT EXISTS idx_notifications_task_file
689
1332
  ON notifications(task_file);
@@ -721,6 +1364,7 @@ async function ensureSchema() {
721
1364
  target_agent TEXT NOT NULL,
722
1365
  target_project TEXT,
723
1366
  target_device TEXT NOT NULL DEFAULT 'local',
1367
+ session_scope TEXT,
724
1368
  content TEXT NOT NULL,
725
1369
  priority TEXT DEFAULT 'normal',
726
1370
  status TEXT DEFAULT 'pending',
@@ -734,10 +1378,31 @@ async function ensureSchema() {
734
1378
  );
735
1379
 
736
1380
  CREATE INDEX IF NOT EXISTS idx_messages_target
737
- ON messages(target_agent, status);
1381
+ ON messages(target_agent, session_scope, status);
738
1382
 
739
1383
  CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
740
- ON messages(target_agent, from_agent, server_seq);
1384
+ ON messages(target_agent, session_scope, from_agent, server_seq);
1385
+ `);
1386
+ try {
1387
+ await client.execute({
1388
+ sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
1389
+ args: []
1390
+ });
1391
+ } catch {
1392
+ }
1393
+ try {
1394
+ await client.execute({
1395
+ sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
1396
+ args: []
1397
+ });
1398
+ } catch {
1399
+ }
1400
+ await client.executeMultiple(`
1401
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
1402
+ ON notifications(agent_id, session_scope, read, created_at);
1403
+
1404
+ CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
1405
+ ON messages(target_agent, session_scope, status, created_at);
741
1406
  `);
742
1407
  try {
743
1408
  await client.execute({
@@ -1321,28 +1986,45 @@ async function ensureSchema() {
1321
1986
  } catch {
1322
1987
  }
1323
1988
  }
1989
+ try {
1990
+ await client.execute({
1991
+ sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
1992
+ args: []
1993
+ });
1994
+ } catch {
1995
+ }
1324
1996
  }
1325
1997
  async function disposeDatabase() {
1998
+ if (_walCheckpointTimer) {
1999
+ clearInterval(_walCheckpointTimer);
2000
+ _walCheckpointTimer = null;
2001
+ }
1326
2002
  if (_daemonClient) {
1327
2003
  _daemonClient.close();
1328
2004
  _daemonClient = null;
1329
2005
  }
2006
+ if (_adapterClient && _adapterClient !== _resilientClient) {
2007
+ _adapterClient.close();
2008
+ }
2009
+ _adapterClient = null;
1330
2010
  if (_client) {
1331
2011
  _client.close();
1332
2012
  _client = null;
1333
2013
  _resilientClient = null;
1334
2014
  }
1335
2015
  }
1336
- var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso, disposeTurso;
2016
+ var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
1337
2017
  var init_database = __esm({
1338
2018
  "src/lib/database.ts"() {
1339
2019
  "use strict";
1340
2020
  init_db_retry();
1341
2021
  init_employees();
2022
+ init_database_adapter();
1342
2023
  _client = null;
1343
2024
  _resilientClient = null;
1344
2025
  _walCheckpointTimer = null;
1345
2026
  _daemonClient = null;
2027
+ _adapterClient = null;
1346
2028
  initTurso = initDatabase;
1347
2029
  disposeTurso = disposeDatabase;
1348
2030
  }
@@ -1350,14 +2032,14 @@ var init_database = __esm({
1350
2032
 
1351
2033
  // src/lib/keychain.ts
1352
2034
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
1353
- import { existsSync as existsSync3 } from "fs";
1354
- import path3 from "path";
1355
- import os3 from "os";
2035
+ import { existsSync as existsSync4 } from "fs";
2036
+ import path4 from "path";
2037
+ import os4 from "os";
1356
2038
  function getKeyDir() {
1357
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path3.join(os3.homedir(), ".exe-os");
2039
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path4.join(os4.homedir(), ".exe-os");
1358
2040
  }
1359
2041
  function getKeyPath() {
1360
- return path3.join(getKeyDir(), "master.key");
2042
+ return path4.join(getKeyDir(), "master.key");
1361
2043
  }
1362
2044
  async function tryKeytar() {
1363
2045
  try {
@@ -1378,9 +2060,9 @@ async function getMasterKey() {
1378
2060
  }
1379
2061
  }
1380
2062
  const keyPath = getKeyPath();
1381
- if (!existsSync3(keyPath)) {
2063
+ if (!existsSync4(keyPath)) {
1382
2064
  process.stderr.write(
1383
- `[keychain] Key not found at ${keyPath} (HOME=${os3.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
2065
+ `[keychain] Key not found at ${keyPath} (HOME=${os4.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
1384
2066
  `
1385
2067
  );
1386
2068
  return null;
@@ -1465,6 +2147,7 @@ var shard_manager_exports = {};
1465
2147
  __export(shard_manager_exports, {
1466
2148
  disposeShards: () => disposeShards,
1467
2149
  ensureShardSchema: () => ensureShardSchema,
2150
+ getOpenShardCount: () => getOpenShardCount,
1468
2151
  getReadyShardClient: () => getReadyShardClient,
1469
2152
  getShardClient: () => getShardClient,
1470
2153
  getShardsDir: () => getShardsDir,
@@ -1473,15 +2156,18 @@ __export(shard_manager_exports, {
1473
2156
  listShards: () => listShards,
1474
2157
  shardExists: () => shardExists
1475
2158
  });
1476
- import path4 from "path";
1477
- import { existsSync as existsSync4, mkdirSync, readdirSync } from "fs";
2159
+ import path5 from "path";
2160
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, readdirSync } from "fs";
1478
2161
  import { createClient as createClient2 } from "@libsql/client";
1479
2162
  function initShardManager(encryptionKey) {
1480
2163
  _encryptionKey = encryptionKey;
1481
- if (!existsSync4(SHARDS_DIR)) {
1482
- mkdirSync(SHARDS_DIR, { recursive: true });
2164
+ if (!existsSync5(SHARDS_DIR)) {
2165
+ mkdirSync2(SHARDS_DIR, { recursive: true });
1483
2166
  }
1484
2167
  _shardingEnabled = true;
2168
+ if (_evictionTimer) clearInterval(_evictionTimer);
2169
+ _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
2170
+ _evictionTimer.unref();
1485
2171
  }
1486
2172
  function isShardingEnabled() {
1487
2173
  return _shardingEnabled;
@@ -1498,21 +2184,28 @@ function getShardClient(projectName) {
1498
2184
  throw new Error(`Invalid project name for shard: "${projectName}"`);
1499
2185
  }
1500
2186
  const cached = _shards.get(safeName);
1501
- if (cached) return cached;
1502
- const dbPath = path4.join(SHARDS_DIR, `${safeName}.db`);
2187
+ if (cached) {
2188
+ _shardLastAccess.set(safeName, Date.now());
2189
+ return cached;
2190
+ }
2191
+ while (_shards.size >= MAX_OPEN_SHARDS) {
2192
+ evictLRU();
2193
+ }
2194
+ const dbPath = path5.join(SHARDS_DIR, `${safeName}.db`);
1503
2195
  const client = createClient2({
1504
2196
  url: `file:${dbPath}`,
1505
2197
  encryptionKey: _encryptionKey
1506
2198
  });
1507
2199
  _shards.set(safeName, client);
2200
+ _shardLastAccess.set(safeName, Date.now());
1508
2201
  return client;
1509
2202
  }
1510
2203
  function shardExists(projectName) {
1511
2204
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
1512
- return existsSync4(path4.join(SHARDS_DIR, `${safeName}.db`));
2205
+ return existsSync5(path5.join(SHARDS_DIR, `${safeName}.db`));
1513
2206
  }
1514
2207
  function listShards() {
1515
- if (!existsSync4(SHARDS_DIR)) return [];
2208
+ if (!existsSync5(SHARDS_DIR)) return [];
1516
2209
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
1517
2210
  }
1518
2211
  async function ensureShardSchema(client) {
@@ -1564,6 +2257,8 @@ async function ensureShardSchema(client) {
1564
2257
  for (const col of [
1565
2258
  "ALTER TABLE memories ADD COLUMN task_id TEXT",
1566
2259
  "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
2260
+ "ALTER TABLE memories ADD COLUMN author_device_id TEXT",
2261
+ "ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
1567
2262
  "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
1568
2263
  "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
1569
2264
  "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
@@ -1586,7 +2281,23 @@ async function ensureShardSchema(client) {
1586
2281
  // MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
1587
2282
  "ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
1588
2283
  "ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
1589
- "ALTER TABLE memories ADD COLUMN trajectory TEXT"
2284
+ "ALTER TABLE memories ADD COLUMN trajectory TEXT",
2285
+ // Metadata enrichment columns (must match database.ts)
2286
+ "ALTER TABLE memories ADD COLUMN intent TEXT",
2287
+ "ALTER TABLE memories ADD COLUMN outcome TEXT",
2288
+ "ALTER TABLE memories ADD COLUMN domain TEXT",
2289
+ "ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
2290
+ "ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
2291
+ "ALTER TABLE memories ADD COLUMN chain_position TEXT",
2292
+ "ALTER TABLE memories ADD COLUMN review_status TEXT",
2293
+ "ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
2294
+ "ALTER TABLE memories ADD COLUMN file_paths TEXT",
2295
+ "ALTER TABLE memories ADD COLUMN commit_hash TEXT",
2296
+ "ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
2297
+ "ALTER TABLE memories ADD COLUMN token_cost REAL",
2298
+ "ALTER TABLE memories ADD COLUMN audience TEXT",
2299
+ "ALTER TABLE memories ADD COLUMN language_type TEXT",
2300
+ "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
1590
2301
  ]) {
1591
2302
  try {
1592
2303
  await client.execute(col);
@@ -1685,21 +2396,69 @@ async function getReadyShardClient(projectName) {
1685
2396
  await ensureShardSchema(client);
1686
2397
  return client;
1687
2398
  }
2399
+ function evictLRU() {
2400
+ let oldest = null;
2401
+ let oldestTime = Infinity;
2402
+ for (const [name, time] of _shardLastAccess) {
2403
+ if (time < oldestTime) {
2404
+ oldestTime = time;
2405
+ oldest = name;
2406
+ }
2407
+ }
2408
+ if (oldest) {
2409
+ const client = _shards.get(oldest);
2410
+ if (client) {
2411
+ client.close();
2412
+ }
2413
+ _shards.delete(oldest);
2414
+ _shardLastAccess.delete(oldest);
2415
+ }
2416
+ }
2417
+ function evictIdleShards() {
2418
+ const now = Date.now();
2419
+ const toEvict = [];
2420
+ for (const [name, lastAccess] of _shardLastAccess) {
2421
+ if (now - lastAccess > SHARD_IDLE_MS) {
2422
+ toEvict.push(name);
2423
+ }
2424
+ }
2425
+ for (const name of toEvict) {
2426
+ const client = _shards.get(name);
2427
+ if (client) {
2428
+ client.close();
2429
+ }
2430
+ _shards.delete(name);
2431
+ _shardLastAccess.delete(name);
2432
+ }
2433
+ }
2434
+ function getOpenShardCount() {
2435
+ return _shards.size;
2436
+ }
1688
2437
  function disposeShards() {
2438
+ if (_evictionTimer) {
2439
+ clearInterval(_evictionTimer);
2440
+ _evictionTimer = null;
2441
+ }
1689
2442
  for (const [, client] of _shards) {
1690
2443
  client.close();
1691
2444
  }
1692
2445
  _shards.clear();
2446
+ _shardLastAccess.clear();
1693
2447
  _shardingEnabled = false;
1694
2448
  _encryptionKey = null;
1695
2449
  }
1696
- var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
2450
+ var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
1697
2451
  var init_shard_manager = __esm({
1698
2452
  "src/lib/shard-manager.ts"() {
1699
2453
  "use strict";
1700
2454
  init_config();
1701
- SHARDS_DIR = path4.join(EXE_AI_DIR, "shards");
2455
+ SHARDS_DIR = path5.join(EXE_AI_DIR, "shards");
2456
+ SHARD_IDLE_MS = 5 * 60 * 1e3;
2457
+ MAX_OPEN_SHARDS = 10;
2458
+ EVICTION_INTERVAL_MS = 60 * 1e3;
1702
2459
  _shards = /* @__PURE__ */ new Map();
2460
+ _shardLastAccess = /* @__PURE__ */ new Map();
2461
+ _evictionTimer = null;
1703
2462
  _encryptionKey = null;
1704
2463
  _shardingEnabled = false;
1705
2464
  }
@@ -2561,8 +3320,8 @@ __export(reranker_exports, {
2561
3320
  rerankWithContext: () => rerankWithContext,
2562
3321
  rerankWithScores: () => rerankWithScores
2563
3322
  });
2564
- import path5 from "path";
2565
- import { existsSync as existsSync5 } from "fs";
3323
+ import path6 from "path";
3324
+ import { existsSync as existsSync6 } from "fs";
2566
3325
  function resetIdleTimer() {
2567
3326
  if (_idleTimer) clearTimeout(_idleTimer);
2568
3327
  _idleTimer = setTimeout(() => {
@@ -2573,18 +3332,18 @@ function resetIdleTimer() {
2573
3332
  }
2574
3333
  }
2575
3334
  function isRerankerAvailable() {
2576
- return existsSync5(path5.join(MODELS_DIR, RERANKER_MODEL_FILE));
3335
+ return existsSync6(path6.join(MODELS_DIR, RERANKER_MODEL_FILE));
2577
3336
  }
2578
3337
  function getRerankerModelPath() {
2579
- return path5.join(MODELS_DIR, RERANKER_MODEL_FILE);
3338
+ return path6.join(MODELS_DIR, RERANKER_MODEL_FILE);
2580
3339
  }
2581
3340
  async function ensureLoaded() {
2582
3341
  if (_rerankerContext) {
2583
3342
  resetIdleTimer();
2584
3343
  return;
2585
3344
  }
2586
- const modelPath = path5.join(MODELS_DIR, RERANKER_MODEL_FILE);
2587
- if (!existsSync5(modelPath)) {
3345
+ const modelPath = path6.join(MODELS_DIR, RERANKER_MODEL_FILE);
3346
+ if (!existsSync6(modelPath)) {
2588
3347
  throw new Error(
2589
3348
  `Reranker model not found at ${modelPath}. Run /exe-setup to download it.`
2590
3349
  );
@@ -2680,13 +3439,50 @@ var init_reranker = __esm({
2680
3439
  }
2681
3440
  });
2682
3441
 
3442
+ // src/lib/daemon-auth.ts
3443
+ import crypto2 from "crypto";
3444
+ import path7 from "path";
3445
+ import { existsSync as existsSync7, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
3446
+ function normalizeToken(token) {
3447
+ if (!token) return null;
3448
+ const trimmed = token.trim();
3449
+ return trimmed.length > 0 ? trimmed : null;
3450
+ }
3451
+ function readDaemonToken() {
3452
+ try {
3453
+ if (!existsSync7(DAEMON_TOKEN_PATH)) return null;
3454
+ return normalizeToken(readFileSync3(DAEMON_TOKEN_PATH, "utf8"));
3455
+ } catch {
3456
+ return null;
3457
+ }
3458
+ }
3459
+ function ensureDaemonToken(seed) {
3460
+ const existing = readDaemonToken();
3461
+ if (existing) return existing;
3462
+ const token = normalizeToken(seed) ?? crypto2.randomBytes(32).toString("hex");
3463
+ ensurePrivateDirSync(EXE_AI_DIR);
3464
+ writeFileSync2(DAEMON_TOKEN_PATH, `${token}
3465
+ `, "utf8");
3466
+ enforcePrivateFileSync(DAEMON_TOKEN_PATH);
3467
+ return token;
3468
+ }
3469
+ var DAEMON_TOKEN_PATH;
3470
+ var init_daemon_auth = __esm({
3471
+ "src/lib/daemon-auth.ts"() {
3472
+ "use strict";
3473
+ init_config();
3474
+ init_secure_files();
3475
+ DAEMON_TOKEN_PATH = path7.join(EXE_AI_DIR, "exed.token");
3476
+ }
3477
+ });
3478
+
2683
3479
  // src/lib/exe-daemon-client.ts
2684
3480
  import net from "net";
2685
- import os4 from "os";
3481
+ import os5 from "os";
2686
3482
  import { spawn } from "child_process";
2687
3483
  import { randomUUID as randomUUID2 } from "crypto";
2688
- import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync3, openSync, closeSync, statSync } from "fs";
2689
- import path6 from "path";
3484
+ import { existsSync as existsSync8, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
3485
+ import path8 from "path";
2690
3486
  import { fileURLToPath } from "url";
2691
3487
  function handleData(chunk) {
2692
3488
  _buffer += chunk.toString();
@@ -2714,9 +3510,9 @@ function handleData(chunk) {
2714
3510
  }
2715
3511
  }
2716
3512
  function cleanupStaleFiles() {
2717
- if (existsSync6(PID_PATH)) {
3513
+ if (existsSync8(PID_PATH)) {
2718
3514
  try {
2719
- const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
3515
+ const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
2720
3516
  if (pid > 0) {
2721
3517
  try {
2722
3518
  process.kill(pid, 0);
@@ -2737,17 +3533,17 @@ function cleanupStaleFiles() {
2737
3533
  }
2738
3534
  }
2739
3535
  function findPackageRoot() {
2740
- let dir = path6.dirname(fileURLToPath(import.meta.url));
2741
- const { root } = path6.parse(dir);
3536
+ let dir = path8.dirname(fileURLToPath(import.meta.url));
3537
+ const { root } = path8.parse(dir);
2742
3538
  while (dir !== root) {
2743
- if (existsSync6(path6.join(dir, "package.json"))) return dir;
2744
- dir = path6.dirname(dir);
3539
+ if (existsSync8(path8.join(dir, "package.json"))) return dir;
3540
+ dir = path8.dirname(dir);
2745
3541
  }
2746
3542
  return null;
2747
3543
  }
2748
3544
  function spawnDaemon() {
2749
- const freeGB = os4.freemem() / (1024 * 1024 * 1024);
2750
- const totalGB = os4.totalmem() / (1024 * 1024 * 1024);
3545
+ const freeGB = os5.freemem() / (1024 * 1024 * 1024);
3546
+ const totalGB = os5.totalmem() / (1024 * 1024 * 1024);
2751
3547
  if (totalGB <= 8) {
2752
3548
  process.stderr.write(
2753
3549
  `[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
@@ -2767,16 +3563,17 @@ function spawnDaemon() {
2767
3563
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
2768
3564
  return;
2769
3565
  }
2770
- const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
2771
- if (!existsSync6(daemonPath)) {
3566
+ const daemonPath = path8.join(pkgRoot, "dist", "lib", "exe-daemon.js");
3567
+ if (!existsSync8(daemonPath)) {
2772
3568
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
2773
3569
  `);
2774
3570
  return;
2775
3571
  }
2776
3572
  const resolvedPath = daemonPath;
3573
+ const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
2777
3574
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
2778
3575
  `);
2779
- const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
3576
+ const logPath = path8.join(path8.dirname(SOCKET_PATH), "exed.log");
2780
3577
  let stderrFd = "ignore";
2781
3578
  try {
2782
3579
  stderrFd = openSync(logPath, "a");
@@ -2794,7 +3591,8 @@ function spawnDaemon() {
2794
3591
  TMUX_PANE: void 0,
2795
3592
  // Prevents resolveExeSession() from scoping to one session
2796
3593
  EXE_DAEMON_SOCK: SOCKET_PATH,
2797
- EXE_DAEMON_PID: PID_PATH
3594
+ EXE_DAEMON_PID: PID_PATH,
3595
+ [DAEMON_TOKEN_ENV]: daemonToken
2798
3596
  }
2799
3597
  });
2800
3598
  child.unref();
@@ -2904,13 +3702,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
2904
3702
  return;
2905
3703
  }
2906
3704
  const id = randomUUID2();
3705
+ const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
2907
3706
  const timer = setTimeout(() => {
2908
3707
  _pending.delete(id);
2909
3708
  resolve({ error: "Request timeout" });
2910
3709
  }, timeoutMs);
2911
3710
  _pending.set(id, { resolve, timer });
2912
3711
  try {
2913
- _socket.write(JSON.stringify({ id, ...payload }) + "\n");
3712
+ _socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
2914
3713
  } catch {
2915
3714
  clearTimeout(timer);
2916
3715
  _pending.delete(id);
@@ -2927,74 +3726,123 @@ async function pingDaemon() {
2927
3726
  return null;
2928
3727
  }
2929
3728
  function killAndRespawnDaemon() {
2930
- process.stderr.write("[exed-client] Killing daemon for restart...\n");
2931
- if (existsSync6(PID_PATH)) {
2932
- try {
2933
- const pid = parseInt(readFileSync3(PID_PATH, "utf8").trim(), 10);
2934
- if (pid > 0) {
2935
- try {
2936
- process.kill(pid, "SIGKILL");
2937
- } catch {
3729
+ if (!acquireSpawnLock()) {
3730
+ process.stderr.write("[exed-client] Another process is already restarting daemon \u2014 skipping\n");
3731
+ if (_socket) {
3732
+ _socket.destroy();
3733
+ _socket = null;
3734
+ }
3735
+ _connected = false;
3736
+ _buffer = "";
3737
+ return;
3738
+ }
3739
+ try {
3740
+ process.stderr.write("[exed-client] Killing daemon for restart...\n");
3741
+ if (existsSync8(PID_PATH)) {
3742
+ try {
3743
+ const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
3744
+ if (pid > 0) {
3745
+ try {
3746
+ process.kill(pid, "SIGKILL");
3747
+ } catch {
3748
+ }
2938
3749
  }
3750
+ } catch {
2939
3751
  }
3752
+ }
3753
+ if (_socket) {
3754
+ _socket.destroy();
3755
+ _socket = null;
3756
+ }
3757
+ _connected = false;
3758
+ _buffer = "";
3759
+ try {
3760
+ unlinkSync2(PID_PATH);
2940
3761
  } catch {
2941
3762
  }
3763
+ try {
3764
+ unlinkSync2(SOCKET_PATH);
3765
+ } catch {
3766
+ }
3767
+ spawnDaemon();
3768
+ } finally {
3769
+ releaseSpawnLock();
2942
3770
  }
2943
- if (_socket) {
2944
- _socket.destroy();
2945
- _socket = null;
2946
- }
2947
- _connected = false;
2948
- _buffer = "";
3771
+ }
3772
+ function isDaemonTooYoung() {
2949
3773
  try {
2950
- unlinkSync2(PID_PATH);
3774
+ const stat = statSync(PID_PATH);
3775
+ return Date.now() - stat.mtimeMs < MIN_DAEMON_AGE_MS;
2951
3776
  } catch {
3777
+ return false;
2952
3778
  }
2953
- try {
2954
- unlinkSync2(SOCKET_PATH);
2955
- } catch {
3779
+ }
3780
+ async function retryThenRestart(doRequest, label) {
3781
+ const result = await doRequest();
3782
+ if (!result.error) {
3783
+ _consecutiveFailures = 0;
3784
+ return result;
3785
+ }
3786
+ _consecutiveFailures++;
3787
+ for (let i = 0; i < MAX_RETRIES_BEFORE_RESTART; i++) {
3788
+ const delayMs = RETRY_DELAYS_MS[i] ?? 5e3;
3789
+ process.stderr.write(`[exed-client] ${label} failed (${result.error}), retry ${i + 1}/${MAX_RETRIES_BEFORE_RESTART} in ${delayMs}ms
3790
+ `);
3791
+ await new Promise((r) => setTimeout(r, delayMs));
3792
+ if (!_connected) {
3793
+ if (!await connectToSocket()) continue;
3794
+ }
3795
+ const retry = await doRequest();
3796
+ if (!retry.error) {
3797
+ _consecutiveFailures = 0;
3798
+ return retry;
3799
+ }
3800
+ _consecutiveFailures++;
3801
+ }
3802
+ if (isDaemonTooYoung()) {
3803
+ process.stderr.write(`[exed-client] ${label}: daemon too young (< ${MIN_DAEMON_AGE_MS / 1e3}s) \u2014 skipping restart
3804
+ `);
3805
+ return { error: result.error };
2956
3806
  }
2957
- spawnDaemon();
3807
+ process.stderr.write(`[exed-client] ${label}: ${_consecutiveFailures} consecutive failures \u2014 restarting daemon
3808
+ `);
3809
+ killAndRespawnDaemon();
3810
+ const start = Date.now();
3811
+ let delay2 = 200;
3812
+ while (Date.now() - start < CONNECT_TIMEOUT_MS) {
3813
+ await new Promise((r) => setTimeout(r, delay2));
3814
+ if (await connectToSocket()) break;
3815
+ delay2 = Math.min(delay2 * 2, 3e3);
3816
+ }
3817
+ if (!_connected) return { error: "Daemon restart failed" };
3818
+ const final = await doRequest();
3819
+ if (!final.error) _consecutiveFailures = 0;
3820
+ return final;
2958
3821
  }
2959
3822
  async function embedViaClient(text, priority = "high") {
2960
3823
  if (!_connected && !await connectEmbedDaemon()) return null;
2961
3824
  _requestCount++;
2962
3825
  if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
2963
3826
  const health = await pingDaemon();
2964
- if (!health) {
3827
+ if (!health && !isDaemonTooYoung()) {
2965
3828
  process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
2966
3829
  `);
2967
3830
  killAndRespawnDaemon();
2968
3831
  const start = Date.now();
2969
- let delay2 = 200;
3832
+ let d = 200;
2970
3833
  while (Date.now() - start < CONNECT_TIMEOUT_MS) {
2971
- await new Promise((r) => setTimeout(r, delay2));
3834
+ await new Promise((r) => setTimeout(r, d));
2972
3835
  if (await connectToSocket()) break;
2973
- delay2 = Math.min(delay2 * 2, 3e3);
3836
+ d = Math.min(d * 2, 3e3);
2974
3837
  }
2975
3838
  if (!_connected) return null;
2976
3839
  }
2977
3840
  }
2978
- const result = await sendRequest([text], priority);
2979
- if (!result.error && result.vectors?.[0]) return result.vectors[0];
2980
- if (result.error) {
2981
- process.stderr.write(`[exed-client] Embed failed (${result.error}) \u2014 attempting restart
2982
- `);
2983
- killAndRespawnDaemon();
2984
- const start = Date.now();
2985
- let delay2 = 200;
2986
- while (Date.now() - start < CONNECT_TIMEOUT_MS) {
2987
- await new Promise((r) => setTimeout(r, delay2));
2988
- if (await connectToSocket()) break;
2989
- delay2 = Math.min(delay2 * 2, 3e3);
2990
- }
2991
- if (!_connected) return null;
2992
- const retry = await sendRequest([text], priority);
2993
- if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
2994
- process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
2995
- `);
2996
- }
2997
- return null;
3841
+ const result = await retryThenRestart(
3842
+ () => sendRequest([text], priority),
3843
+ "Embed"
3844
+ );
3845
+ return !result.error && result.vectors?.[0] ? result.vectors[0] : null;
2998
3846
  }
2999
3847
  function disconnectClient() {
3000
3848
  if (_socket) {
@@ -3009,22 +3857,28 @@ function disconnectClient() {
3009
3857
  entry.resolve({ error: "Client disconnected" });
3010
3858
  }
3011
3859
  }
3012
- var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _requestCount, HEALTH_CHECK_INTERVAL, _pending, MAX_BUFFER;
3860
+ 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;
3013
3861
  var init_exe_daemon_client = __esm({
3014
3862
  "src/lib/exe-daemon-client.ts"() {
3015
3863
  "use strict";
3016
3864
  init_config();
3017
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
3018
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
3019
- SPAWN_LOCK_PATH = path6.join(EXE_AI_DIR, "exed-spawn.lock");
3865
+ init_daemon_auth();
3866
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path8.join(EXE_AI_DIR, "exed.sock");
3867
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path8.join(EXE_AI_DIR, "exed.pid");
3868
+ SPAWN_LOCK_PATH = path8.join(EXE_AI_DIR, "exed-spawn.lock");
3020
3869
  SPAWN_LOCK_STALE_MS = 3e4;
3021
3870
  CONNECT_TIMEOUT_MS = 15e3;
3022
3871
  REQUEST_TIMEOUT_MS = 3e4;
3872
+ DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
3023
3873
  _socket = null;
3024
3874
  _connected = false;
3025
3875
  _buffer = "";
3026
3876
  _requestCount = 0;
3877
+ _consecutiveFailures = 0;
3027
3878
  HEALTH_CHECK_INTERVAL = 100;
3879
+ MAX_RETRIES_BEFORE_RESTART = 3;
3880
+ RETRY_DELAYS_MS = [1e3, 3e3, 5e3];
3881
+ MIN_DAEMON_AGE_MS = 3e4;
3028
3882
  _pending = /* @__PURE__ */ new Map();
3029
3883
  MAX_BUFFER = 1e7;
3030
3884
  }
@@ -3067,10 +3921,10 @@ async function disposeEmbedder() {
3067
3921
  async function embedDirect(text) {
3068
3922
  const llamaCpp = await import("node-llama-cpp");
3069
3923
  const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
3070
- const { existsSync: existsSync8 } = await import("fs");
3071
- const path10 = await import("path");
3072
- const modelPath = path10.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
3073
- if (!existsSync8(modelPath)) {
3924
+ const { existsSync: existsSync10 } = await import("fs");
3925
+ const path12 = await import("path");
3926
+ const modelPath = path12.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
3927
+ if (!existsSync10(modelPath)) {
3074
3928
  throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
3075
3929
  }
3076
3930
  const llama = await llamaCpp.getLlama();
@@ -3105,7 +3959,7 @@ __export(project_name_exports, {
3105
3959
  getProjectName: () => getProjectName
3106
3960
  });
3107
3961
  import { execSync as execSync2 } from "child_process";
3108
- import path7 from "path";
3962
+ import path9 from "path";
3109
3963
  function getProjectName(cwd) {
3110
3964
  const dir = cwd ?? process.cwd();
3111
3965
  if (_cached && _cachedCwd === dir) return _cached;
@@ -3118,7 +3972,7 @@ function getProjectName(cwd) {
3118
3972
  timeout: 2e3,
3119
3973
  stdio: ["pipe", "pipe", "pipe"]
3120
3974
  }).trim();
3121
- repoRoot = path7.dirname(gitCommonDir);
3975
+ repoRoot = path9.dirname(gitCommonDir);
3122
3976
  } catch {
3123
3977
  repoRoot = execSync2("git rev-parse --show-toplevel", {
3124
3978
  cwd: dir,
@@ -3127,11 +3981,11 @@ function getProjectName(cwd) {
3127
3981
  stdio: ["pipe", "pipe", "pipe"]
3128
3982
  }).trim();
3129
3983
  }
3130
- _cached = path7.basename(repoRoot);
3984
+ _cached = path9.basename(repoRoot);
3131
3985
  _cachedCwd = dir;
3132
3986
  return _cached;
3133
3987
  } catch {
3134
- _cached = path7.basename(dir);
3988
+ _cached = path9.basename(dir);
3135
3989
  _cachedCwd = dir;
3136
3990
  return _cached;
3137
3991
  }
@@ -3155,9 +4009,9 @@ __export(file_grep_exports, {
3155
4009
  grepProjectFiles: () => grepProjectFiles
3156
4010
  });
3157
4011
  import { execSync as execSync3 } from "child_process";
3158
- import { readFileSync as readFileSync4, readdirSync as readdirSync2, statSync as statSync2, existsSync as existsSync7 } from "fs";
3159
- import path8 from "path";
3160
- import crypto2 from "crypto";
4012
+ import { readFileSync as readFileSync5, readdirSync as readdirSync2, statSync as statSync2, existsSync as existsSync9 } from "fs";
4013
+ import path10 from "path";
4014
+ import crypto3 from "crypto";
3161
4015
  function hasRipgrep() {
3162
4016
  if (_hasRg === null) {
3163
4017
  try {
@@ -3190,13 +4044,13 @@ async function grepProjectFiles(query, projectRoot, options) {
3190
4044
  const chunkCtx = getChunkContext(hit.filePath, hit.lineNumber);
3191
4045
  const prefix = chunkCtx ? `[file: ${hit.filePath}:${hit.lineNumber} in ${chunkCtx}]` : `[file: ${hit.filePath}:${hit.lineNumber}]`;
3192
4046
  return {
3193
- id: crypto2.createHash("sha256").update(`${hit.filePath}:${hit.lineNumber}`).digest("hex").slice(0, 36),
4047
+ id: crypto3.createHash("sha256").update(`${hit.filePath}:${hit.lineNumber}`).digest("hex").slice(0, 36),
3194
4048
  agent_id: "project",
3195
4049
  agent_role: "file",
3196
4050
  session_id: "file-grep",
3197
4051
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3198
4052
  tool_name: "file_grep",
3199
- project_name: path8.basename(projectRoot),
4053
+ project_name: path10.basename(projectRoot),
3200
4054
  has_error: false,
3201
4055
  raw_text: `${prefix} ${buildSnippet(hit, projectRoot)}`,
3202
4056
  vector: null,
@@ -3208,7 +4062,7 @@ function getChunkContext(filePath, lineNumber) {
3208
4062
  try {
3209
4063
  const ext = filePath.split(".").pop()?.toLowerCase();
3210
4064
  if (ext !== "ts" && ext !== "tsx" && ext !== "js" && ext !== "jsx") return "";
3211
- const source = readFileSync4(filePath, "utf8");
4065
+ const source = readFileSync5(filePath, "utf8");
3212
4066
  const lines = source.split("\n");
3213
4067
  for (let i = Math.min(lineNumber - 1, lines.length - 1); i >= 0; i--) {
3214
4068
  const line = lines[i];
@@ -3270,11 +4124,11 @@ function grepWithNodeFs(pattern, projectRoot, patterns) {
3270
4124
  const files = collectFiles(projectRoot, patterns ?? DEFAULT_PATTERNS);
3271
4125
  const hits = [];
3272
4126
  for (const filePath of files.slice(0, MAX_FILES)) {
3273
- const absPath = path8.join(projectRoot, filePath);
4127
+ const absPath = path10.join(projectRoot, filePath);
3274
4128
  try {
3275
4129
  const stat = statSync2(absPath);
3276
4130
  if (stat.size > MAX_FILE_SIZE) continue;
3277
- const content = readFileSync4(absPath, "utf8");
4131
+ const content = readFileSync5(absPath, "utf8");
3278
4132
  const lines = content.split("\n");
3279
4133
  const matches = content.match(regex);
3280
4134
  if (!matches || matches.length === 0) continue;
@@ -3297,15 +4151,15 @@ function collectFiles(root, patterns) {
3297
4151
  const files = [];
3298
4152
  function walk(dir, relative) {
3299
4153
  if (files.length >= MAX_FILES) return;
3300
- const basename = path8.basename(dir);
4154
+ const basename = path10.basename(dir);
3301
4155
  if (EXCLUDE_DIRS.includes(basename)) return;
3302
4156
  try {
3303
4157
  const entries = readdirSync2(dir, { withFileTypes: true });
3304
4158
  for (const entry of entries) {
3305
4159
  if (files.length >= MAX_FILES) return;
3306
- const rel = path8.join(relative, entry.name);
4160
+ const rel = path10.join(relative, entry.name);
3307
4161
  if (entry.isDirectory()) {
3308
- walk(path8.join(dir, entry.name), rel);
4162
+ walk(path10.join(dir, entry.name), rel);
3309
4163
  } else if (entry.isFile()) {
3310
4164
  for (const pat of patterns) {
3311
4165
  if (matchGlob(rel, pat)) {
@@ -3337,7 +4191,7 @@ function matchGlob(filePath, pattern) {
3337
4191
  if (slashIdx !== -1) {
3338
4192
  const dir = pattern.slice(0, slashIdx);
3339
4193
  const ext2 = pattern.slice(slashIdx + 1).replace("*", "");
3340
- const fileDir = path8.dirname(filePath);
4194
+ const fileDir = path10.dirname(filePath);
3341
4195
  return fileDir === dir && filePath.endsWith(ext2);
3342
4196
  }
3343
4197
  const ext = pattern.replace("*", "");
@@ -3345,9 +4199,9 @@ function matchGlob(filePath, pattern) {
3345
4199
  }
3346
4200
  function buildSnippet(hit, projectRoot) {
3347
4201
  try {
3348
- const absPath = path8.join(projectRoot, hit.filePath);
3349
- if (!existsSync7(absPath)) return hit.matchLine;
3350
- const lines = readFileSync4(absPath, "utf8").split("\n");
4202
+ const absPath = path10.join(projectRoot, hit.filePath);
4203
+ if (!existsSync9(absPath)) return hit.matchLine;
4204
+ const lines = readFileSync5(absPath, "utf8").split("\n");
3351
4205
  const start = Math.max(0, hit.lineNumber - 3);
3352
4206
  const end = Math.min(lines.length, hit.lineNumber + 2);
3353
4207
  return lines.slice(start, end).join("\n").slice(0, 500);
@@ -4591,9 +5445,9 @@ init_database();
4591
5445
 
4592
5446
  // src/lib/active-agent.ts
4593
5447
  init_config();
4594
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, unlinkSync as unlinkSync3, readdirSync as readdirSync3 } from "fs";
5448
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, unlinkSync as unlinkSync3, readdirSync as readdirSync3 } from "fs";
4595
5449
  import { execSync as execSync5 } from "child_process";
4596
- import path9 from "path";
5450
+ import path11 from "path";
4597
5451
 
4598
5452
  // src/lib/session-key.ts
4599
5453
  import { execSync as execSync4 } from "child_process";
@@ -4659,7 +5513,7 @@ function getSessionKey() {
4659
5513
 
4660
5514
  // src/lib/active-agent.ts
4661
5515
  init_employees();
4662
- var CACHE_DIR = path9.join(EXE_AI_DIR, "session-cache");
5516
+ var CACHE_DIR = path11.join(EXE_AI_DIR, "session-cache");
4663
5517
  var STALE_MS = 24 * 60 * 60 * 1e3;
4664
5518
  function isNameWithOptionalInstance(candidate, baseName) {
4665
5519
  if (candidate === baseName) return true;
@@ -4704,12 +5558,12 @@ function resolveActiveAgentFromTmuxSession(sessionName) {
4704
5558
  return null;
4705
5559
  }
4706
5560
  function getMarkerPath() {
4707
- return path9.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
5561
+ return path11.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
4708
5562
  }
4709
5563
  function getActiveAgent() {
4710
5564
  try {
4711
5565
  const markerPath = getMarkerPath();
4712
- const raw = readFileSync5(markerPath, "utf8");
5566
+ const raw = readFileSync6(markerPath, "utf8");
4713
5567
  const data = JSON.parse(raw);
4714
5568
  if (data.agentId) {
4715
5569
  if (data.startedAt) {
@@ -4774,10 +5628,10 @@ process.stdin.on("end", async () => {
4774
5628
  }
4775
5629
  if (process.env.EXE_DEBUG_HOOKS) {
4776
5630
  try {
4777
- const os5 = await import("os");
5631
+ const os6 = await import("os");
4778
5632
  const fs = await import("fs");
4779
5633
  const nodePath = await import("path");
4780
- const debugPath = nodePath.default.join(os5.default.homedir(), ".exe-os", "logs", "hook-stdin-error-recall.log");
5634
+ const debugPath = nodePath.default.join(os6.default.homedir(), ".exe-os", "logs", "hook-stdin-error-recall.log");
4781
5635
  fs.mkdirSync(nodePath.default.dirname(debugPath), { recursive: true });
4782
5636
  const ts = (/* @__PURE__ */ new Date()).toISOString();
4783
5637
  const snippet = input.length > 2e3 ? input.slice(0, 2e3) + "...[truncated]" : input;