@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
@@ -25,9 +25,47 @@ var __copyProps = (to, from, except, desc) => {
25
25
  };
26
26
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
27
27
 
28
+ // src/lib/secure-files.ts
29
+ import { chmodSync, existsSync, mkdirSync } from "fs";
30
+ import { chmod, mkdir } from "fs/promises";
31
+ async function ensurePrivateDir(dirPath) {
32
+ await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
33
+ try {
34
+ await chmod(dirPath, PRIVATE_DIR_MODE);
35
+ } catch {
36
+ }
37
+ }
38
+ function ensurePrivateDirSync(dirPath) {
39
+ mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
40
+ try {
41
+ chmodSync(dirPath, PRIVATE_DIR_MODE);
42
+ } catch {
43
+ }
44
+ }
45
+ async function enforcePrivateFile(filePath) {
46
+ try {
47
+ await chmod(filePath, PRIVATE_FILE_MODE);
48
+ } catch {
49
+ }
50
+ }
51
+ function enforcePrivateFileSync(filePath) {
52
+ try {
53
+ if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
54
+ } catch {
55
+ }
56
+ }
57
+ var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
58
+ var init_secure_files = __esm({
59
+ "src/lib/secure-files.ts"() {
60
+ "use strict";
61
+ PRIVATE_DIR_MODE = 448;
62
+ PRIVATE_FILE_MODE = 384;
63
+ }
64
+ });
65
+
28
66
  // src/lib/config.ts
29
- import { readFile, writeFile, mkdir, chmod } from "fs/promises";
30
- import { readFileSync, existsSync, renameSync } from "fs";
67
+ import { readFile, writeFile } from "fs/promises";
68
+ import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
31
69
  import path from "path";
32
70
  import os from "os";
33
71
  function resolveDataDir() {
@@ -35,7 +73,7 @@ function resolveDataDir() {
35
73
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
36
74
  const newDir = path.join(os.homedir(), ".exe-os");
37
75
  const legacyDir = path.join(os.homedir(), ".exe-mem");
38
- if (!existsSync(newDir) && existsSync(legacyDir)) {
76
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
39
77
  try {
40
78
  renameSync(legacyDir, newDir);
41
79
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -98,9 +136,9 @@ function normalizeAutoUpdate(raw) {
98
136
  }
99
137
  async function loadConfig() {
100
138
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
101
- await mkdir(dir, { recursive: true });
139
+ await ensurePrivateDir(dir);
102
140
  const configPath = path.join(dir, "config.json");
103
- if (!existsSync(configPath)) {
141
+ if (!existsSync2(configPath)) {
104
142
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
105
143
  }
106
144
  const raw = await readFile(configPath, "utf-8");
@@ -113,6 +151,7 @@ async function loadConfig() {
113
151
  `);
114
152
  try {
115
153
  await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
154
+ await enforcePrivateFile(configPath);
116
155
  } catch {
117
156
  }
118
157
  }
@@ -131,7 +170,7 @@ async function loadConfig() {
131
170
  function loadConfigSync() {
132
171
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
133
172
  const configPath = path.join(dir, "config.json");
134
- if (!existsSync(configPath)) {
173
+ if (!existsSync2(configPath)) {
135
174
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
136
175
  }
137
176
  try {
@@ -151,6 +190,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
151
190
  var init_config = __esm({
152
191
  "src/lib/config.ts"() {
153
192
  "use strict";
193
+ init_secure_files();
154
194
  EXE_AI_DIR = resolveDataDir();
155
195
  DB_PATH = path.join(EXE_AI_DIR, "memories.db");
156
196
  MODELS_DIR = path.join(EXE_AI_DIR, "models");
@@ -297,7 +337,7 @@ var init_session_key = __esm({
297
337
 
298
338
  // src/lib/employees.ts
299
339
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
300
- import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
340
+ import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
301
341
  import { execSync as execSync2 } from "child_process";
302
342
  import path2 from "path";
303
343
  import os2 from "os";
@@ -321,7 +361,7 @@ function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
321
361
  return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
322
362
  }
323
363
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
324
- if (!existsSync2(employeesPath)) return [];
364
+ if (!existsSync3(employeesPath)) return [];
325
365
  try {
326
366
  return JSON.parse(readFileSync2(employeesPath, "utf-8"));
327
367
  } catch {
@@ -331,7 +371,7 @@ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
331
371
  function getEmployee(employees, name) {
332
372
  return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
333
373
  }
334
- var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE;
374
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, IDENTITY_DIR;
335
375
  var init_employees = __esm({
336
376
  "src/lib/employees.ts"() {
337
377
  "use strict";
@@ -339,6 +379,7 @@ var init_employees = __esm({
339
379
  EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
340
380
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
341
381
  COORDINATOR_ROLE = "COO";
382
+ IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
342
383
  }
343
384
  });
344
385
 
@@ -516,7 +557,7 @@ var init_runtime_table = __esm({
516
557
  });
517
558
 
518
559
  // src/lib/agent-config.ts
519
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
560
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync4 } from "fs";
520
561
  import path5 from "path";
521
562
  var AGENT_CONFIG_PATH, DEFAULT_MODELS;
522
563
  var init_agent_config = __esm({
@@ -524,6 +565,7 @@ var init_agent_config = __esm({
524
565
  "use strict";
525
566
  init_config();
526
567
  init_runtime_table();
568
+ init_secure_files();
527
569
  AGENT_CONFIG_PATH = path5.join(EXE_AI_DIR, "agent-config.json");
528
570
  DEFAULT_MODELS = {
529
571
  claude: "claude-opus-4",
@@ -534,7 +576,7 @@ var init_agent_config = __esm({
534
576
  });
535
577
 
536
578
  // src/lib/intercom-queue.ts
537
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
579
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
538
580
  import path6 from "path";
539
581
  import os4 from "os";
540
582
  var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
@@ -602,13 +644,634 @@ var init_db_retry = __esm({
602
644
  }
603
645
  });
604
646
 
647
+ // src/lib/database-adapter.ts
648
+ import os5 from "os";
649
+ import path7 from "path";
650
+ import { createRequire } from "module";
651
+ import { pathToFileURL } from "url";
652
+ function quotedIdentifier(identifier) {
653
+ return `"${identifier.replace(/"/g, '""')}"`;
654
+ }
655
+ function unqualifiedTableName(name) {
656
+ const raw = name.trim().replace(/^"|"$/g, "");
657
+ const parts = raw.split(".");
658
+ return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
659
+ }
660
+ function stripTrailingSemicolon(sql) {
661
+ return sql.trim().replace(/;+\s*$/u, "");
662
+ }
663
+ function appendClause(sql, clause) {
664
+ const trimmed = stripTrailingSemicolon(sql);
665
+ const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
666
+ if (!returningMatch) {
667
+ return `${trimmed}${clause}`;
668
+ }
669
+ const idx = returningMatch.index;
670
+ return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
671
+ }
672
+ function normalizeStatement(stmt) {
673
+ if (typeof stmt === "string") {
674
+ return { kind: "positional", sql: stmt, args: [] };
675
+ }
676
+ const sql = stmt.sql;
677
+ if (Array.isArray(stmt.args) || stmt.args === void 0) {
678
+ return { kind: "positional", sql, args: stmt.args ?? [] };
679
+ }
680
+ return { kind: "named", sql, args: stmt.args };
681
+ }
682
+ function rewriteBooleanLiterals(sql) {
683
+ let out = sql;
684
+ for (const column of BOOLEAN_COLUMN_NAMES) {
685
+ const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
686
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
687
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
688
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
689
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
690
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
691
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
692
+ }
693
+ return out;
694
+ }
695
+ function rewriteInsertOrIgnore(sql) {
696
+ if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
697
+ return sql;
698
+ }
699
+ const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
700
+ return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
701
+ }
702
+ function rewriteInsertOrReplace(sql) {
703
+ const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
704
+ if (!match) {
705
+ return sql;
706
+ }
707
+ const rawTable = match[1];
708
+ const rawColumns = match[2];
709
+ const remainder = match[3];
710
+ const tableName = unqualifiedTableName(rawTable);
711
+ const conflictKeys = UPSERT_KEYS[tableName];
712
+ if (!conflictKeys?.length) {
713
+ return sql;
714
+ }
715
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
716
+ const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
717
+ const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
718
+ const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
719
+ return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
720
+ }
721
+ function rewriteSql(sql) {
722
+ let out = sql;
723
+ out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
724
+ out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
725
+ out = rewriteBooleanLiterals(out);
726
+ out = rewriteInsertOrReplace(out);
727
+ out = rewriteInsertOrIgnore(out);
728
+ return stripTrailingSemicolon(out);
729
+ }
730
+ function toBoolean(value) {
731
+ if (value === null || value === void 0) return value;
732
+ if (typeof value === "boolean") return value;
733
+ if (typeof value === "number") return value !== 0;
734
+ if (typeof value === "bigint") return value !== 0n;
735
+ if (typeof value === "string") {
736
+ const normalized = value.trim().toLowerCase();
737
+ if (normalized === "0" || normalized === "false") return false;
738
+ if (normalized === "1" || normalized === "true") return true;
739
+ }
740
+ return Boolean(value);
741
+ }
742
+ function countQuestionMarks(sql, end) {
743
+ let count = 0;
744
+ let inSingle = false;
745
+ let inDouble = false;
746
+ let inLineComment = false;
747
+ let inBlockComment = false;
748
+ for (let i = 0; i < end; i++) {
749
+ const ch = sql[i];
750
+ const next = sql[i + 1];
751
+ if (inLineComment) {
752
+ if (ch === "\n") inLineComment = false;
753
+ continue;
754
+ }
755
+ if (inBlockComment) {
756
+ if (ch === "*" && next === "/") {
757
+ inBlockComment = false;
758
+ i += 1;
759
+ }
760
+ continue;
761
+ }
762
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
763
+ inLineComment = true;
764
+ i += 1;
765
+ continue;
766
+ }
767
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
768
+ inBlockComment = true;
769
+ i += 1;
770
+ continue;
771
+ }
772
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
773
+ inSingle = !inSingle;
774
+ continue;
775
+ }
776
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
777
+ inDouble = !inDouble;
778
+ continue;
779
+ }
780
+ if (!inSingle && !inDouble && ch === "?") {
781
+ count += 1;
782
+ }
783
+ }
784
+ return count;
785
+ }
786
+ function findBooleanPlaceholderIndexes(sql) {
787
+ const indexes = /* @__PURE__ */ new Set();
788
+ for (const column of BOOLEAN_COLUMN_NAMES) {
789
+ const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
790
+ for (const match of sql.matchAll(pattern)) {
791
+ const matchText = match[0];
792
+ const qIndex = match.index + matchText.lastIndexOf("?");
793
+ indexes.add(countQuestionMarks(sql, qIndex + 1));
794
+ }
795
+ }
796
+ return indexes;
797
+ }
798
+ function coerceInsertBooleanArgs(sql, args) {
799
+ const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
800
+ if (!match) return;
801
+ const rawTable = match[1];
802
+ const rawColumns = match[2];
803
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
804
+ if (!boolColumns?.size) return;
805
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
806
+ for (const [index, column] of columns.entries()) {
807
+ if (boolColumns.has(column) && index < args.length) {
808
+ args[index] = toBoolean(args[index]);
809
+ }
810
+ }
811
+ }
812
+ function coerceUpdateBooleanArgs(sql, args) {
813
+ const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
814
+ if (!match) return;
815
+ const rawTable = match[1];
816
+ const setClause = match[2];
817
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
818
+ if (!boolColumns?.size) return;
819
+ const assignments = setClause.split(",");
820
+ let placeholderIndex = 0;
821
+ for (const assignment of assignments) {
822
+ if (!assignment.includes("?")) continue;
823
+ placeholderIndex += 1;
824
+ const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
825
+ if (colMatch && boolColumns.has(colMatch[1])) {
826
+ args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
827
+ }
828
+ }
829
+ }
830
+ function coerceBooleanArgs(sql, args) {
831
+ const nextArgs = [...args];
832
+ coerceInsertBooleanArgs(sql, nextArgs);
833
+ coerceUpdateBooleanArgs(sql, nextArgs);
834
+ const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
835
+ for (const index of placeholderIndexes) {
836
+ if (index > 0 && index <= nextArgs.length) {
837
+ nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
838
+ }
839
+ }
840
+ return nextArgs;
841
+ }
842
+ function convertQuestionMarksToDollarParams(sql) {
843
+ let out = "";
844
+ let placeholder = 0;
845
+ let inSingle = false;
846
+ let inDouble = false;
847
+ let inLineComment = false;
848
+ let inBlockComment = false;
849
+ for (let i = 0; i < sql.length; i++) {
850
+ const ch = sql[i];
851
+ const next = sql[i + 1];
852
+ if (inLineComment) {
853
+ out += ch;
854
+ if (ch === "\n") inLineComment = false;
855
+ continue;
856
+ }
857
+ if (inBlockComment) {
858
+ out += ch;
859
+ if (ch === "*" && next === "/") {
860
+ out += next;
861
+ inBlockComment = false;
862
+ i += 1;
863
+ }
864
+ continue;
865
+ }
866
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
867
+ out += ch + next;
868
+ inLineComment = true;
869
+ i += 1;
870
+ continue;
871
+ }
872
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
873
+ out += ch + next;
874
+ inBlockComment = true;
875
+ i += 1;
876
+ continue;
877
+ }
878
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
879
+ inSingle = !inSingle;
880
+ out += ch;
881
+ continue;
882
+ }
883
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
884
+ inDouble = !inDouble;
885
+ out += ch;
886
+ continue;
887
+ }
888
+ if (!inSingle && !inDouble && ch === "?") {
889
+ placeholder += 1;
890
+ out += `$${placeholder}`;
891
+ continue;
892
+ }
893
+ out += ch;
894
+ }
895
+ return out;
896
+ }
897
+ function translateStatementForPostgres(stmt) {
898
+ const normalized = normalizeStatement(stmt);
899
+ if (normalized.kind === "named") {
900
+ throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
901
+ }
902
+ const rewrittenSql = rewriteSql(normalized.sql);
903
+ const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
904
+ return {
905
+ sql: convertQuestionMarksToDollarParams(rewrittenSql),
906
+ args: coercedArgs
907
+ };
908
+ }
909
+ function shouldBypassPostgres(stmt) {
910
+ const normalized = normalizeStatement(stmt);
911
+ if (normalized.kind === "named") {
912
+ return true;
913
+ }
914
+ return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
915
+ }
916
+ function shouldFallbackOnError(error) {
917
+ const message = error instanceof Error ? error.message : String(error);
918
+ return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
919
+ }
920
+ function isReadQuery(sql) {
921
+ const trimmed = sql.trimStart();
922
+ return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
923
+ }
924
+ function buildRow(row, columns) {
925
+ const values = columns.map((column) => row[column]);
926
+ return Object.assign(values, row);
927
+ }
928
+ function buildResultSet(rows, rowsAffected = 0) {
929
+ const columns = rows[0] ? Object.keys(rows[0]) : [];
930
+ const resultRows = rows.map((row) => buildRow(row, columns));
931
+ return {
932
+ columns,
933
+ columnTypes: columns.map(() => ""),
934
+ rows: resultRows,
935
+ rowsAffected,
936
+ lastInsertRowid: void 0,
937
+ toJSON() {
938
+ return {
939
+ columns,
940
+ columnTypes: columns.map(() => ""),
941
+ rows,
942
+ rowsAffected,
943
+ lastInsertRowid: void 0
944
+ };
945
+ }
946
+ };
947
+ }
948
+ async function loadPrismaClient() {
949
+ if (!prismaClientPromise) {
950
+ prismaClientPromise = (async () => {
951
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
952
+ if (explicitPath) {
953
+ const module2 = await import(pathToFileURL(explicitPath).href);
954
+ const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
955
+ if (!PrismaClient2) {
956
+ throw new Error(`No PrismaClient export found at ${explicitPath}`);
957
+ }
958
+ return new PrismaClient2();
959
+ }
960
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path7.join(os5.homedir(), "exe-db");
961
+ const requireFromExeDb = createRequire(path7.join(exeDbRoot, "package.json"));
962
+ const prismaEntry = requireFromExeDb.resolve("@prisma/client");
963
+ const module = await import(pathToFileURL(prismaEntry).href);
964
+ const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
965
+ if (!PrismaClient) {
966
+ throw new Error(`No PrismaClient export found in ${prismaEntry}`);
967
+ }
968
+ return new PrismaClient();
969
+ })();
970
+ }
971
+ return prismaClientPromise;
972
+ }
973
+ async function ensureCompatibilityViews(prisma) {
974
+ if (!compatibilityBootstrapPromise) {
975
+ compatibilityBootstrapPromise = (async () => {
976
+ for (const mapping of VIEW_MAPPINGS) {
977
+ const relation = mapping.source.replace(/"/g, "");
978
+ const rows = await prisma.$queryRawUnsafe(
979
+ "SELECT to_regclass($1) AS regclass",
980
+ relation
981
+ );
982
+ if (!rows[0]?.regclass) {
983
+ continue;
984
+ }
985
+ await prisma.$executeRawUnsafe(
986
+ `CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
987
+ );
988
+ }
989
+ })();
990
+ }
991
+ return compatibilityBootstrapPromise;
992
+ }
993
+ async function executeOnPrisma(executor, stmt) {
994
+ const translated = translateStatementForPostgres(stmt);
995
+ if (isReadQuery(translated.sql)) {
996
+ const rows = await executor.$queryRawUnsafe(
997
+ translated.sql,
998
+ ...translated.args
999
+ );
1000
+ return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
1001
+ }
1002
+ const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
1003
+ return buildResultSet([], rowsAffected);
1004
+ }
1005
+ function splitSqlStatements(sql) {
1006
+ const parts = [];
1007
+ let current = "";
1008
+ let inSingle = false;
1009
+ let inDouble = false;
1010
+ let inLineComment = false;
1011
+ let inBlockComment = false;
1012
+ for (let i = 0; i < sql.length; i++) {
1013
+ const ch = sql[i];
1014
+ const next = sql[i + 1];
1015
+ if (inLineComment) {
1016
+ current += ch;
1017
+ if (ch === "\n") inLineComment = false;
1018
+ continue;
1019
+ }
1020
+ if (inBlockComment) {
1021
+ current += ch;
1022
+ if (ch === "*" && next === "/") {
1023
+ current += next;
1024
+ inBlockComment = false;
1025
+ i += 1;
1026
+ }
1027
+ continue;
1028
+ }
1029
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
1030
+ current += ch + next;
1031
+ inLineComment = true;
1032
+ i += 1;
1033
+ continue;
1034
+ }
1035
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
1036
+ current += ch + next;
1037
+ inBlockComment = true;
1038
+ i += 1;
1039
+ continue;
1040
+ }
1041
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
1042
+ inSingle = !inSingle;
1043
+ current += ch;
1044
+ continue;
1045
+ }
1046
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
1047
+ inDouble = !inDouble;
1048
+ current += ch;
1049
+ continue;
1050
+ }
1051
+ if (!inSingle && !inDouble && ch === ";") {
1052
+ if (current.trim()) {
1053
+ parts.push(current.trim());
1054
+ }
1055
+ current = "";
1056
+ continue;
1057
+ }
1058
+ current += ch;
1059
+ }
1060
+ if (current.trim()) {
1061
+ parts.push(current.trim());
1062
+ }
1063
+ return parts;
1064
+ }
1065
+ async function createPrismaDbAdapter(fallbackClient) {
1066
+ const prisma = await loadPrismaClient();
1067
+ await ensureCompatibilityViews(prisma);
1068
+ let closed = false;
1069
+ let adapter;
1070
+ const fallbackExecute = async (stmt, error) => {
1071
+ if (!fallbackClient) {
1072
+ if (error) throw error;
1073
+ throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
1074
+ }
1075
+ if (error) {
1076
+ process.stderr.write(
1077
+ `[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
1078
+ `
1079
+ );
1080
+ }
1081
+ return fallbackClient.execute(stmt);
1082
+ };
1083
+ adapter = {
1084
+ async execute(stmt) {
1085
+ if (shouldBypassPostgres(stmt)) {
1086
+ return fallbackExecute(stmt);
1087
+ }
1088
+ try {
1089
+ return await executeOnPrisma(prisma, stmt);
1090
+ } catch (error) {
1091
+ if (shouldFallbackOnError(error)) {
1092
+ return fallbackExecute(stmt, error);
1093
+ }
1094
+ throw error;
1095
+ }
1096
+ },
1097
+ async batch(stmts, mode) {
1098
+ if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
1099
+ if (!fallbackClient) {
1100
+ throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
1101
+ }
1102
+ return fallbackClient.batch(stmts, mode);
1103
+ }
1104
+ try {
1105
+ if (prisma.$transaction) {
1106
+ return await prisma.$transaction(async (tx) => {
1107
+ const results2 = [];
1108
+ for (const stmt of stmts) {
1109
+ results2.push(await executeOnPrisma(tx, stmt));
1110
+ }
1111
+ return results2;
1112
+ });
1113
+ }
1114
+ const results = [];
1115
+ for (const stmt of stmts) {
1116
+ results.push(await executeOnPrisma(prisma, stmt));
1117
+ }
1118
+ return results;
1119
+ } catch (error) {
1120
+ if (fallbackClient && shouldFallbackOnError(error)) {
1121
+ process.stderr.write(
1122
+ `[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
1123
+ `
1124
+ );
1125
+ return fallbackClient.batch(stmts, mode);
1126
+ }
1127
+ throw error;
1128
+ }
1129
+ },
1130
+ async migrate(stmts) {
1131
+ if (fallbackClient) {
1132
+ return fallbackClient.migrate(stmts);
1133
+ }
1134
+ return adapter.batch(stmts, "deferred");
1135
+ },
1136
+ async transaction(mode) {
1137
+ if (!fallbackClient) {
1138
+ throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
1139
+ }
1140
+ return fallbackClient.transaction(mode);
1141
+ },
1142
+ async executeMultiple(sql) {
1143
+ if (fallbackClient && shouldBypassPostgres(sql)) {
1144
+ return fallbackClient.executeMultiple(sql);
1145
+ }
1146
+ for (const statement of splitSqlStatements(sql)) {
1147
+ await adapter.execute(statement);
1148
+ }
1149
+ },
1150
+ async sync() {
1151
+ if (fallbackClient) {
1152
+ return fallbackClient.sync();
1153
+ }
1154
+ return { frame_no: 0, frames_synced: 0 };
1155
+ },
1156
+ close() {
1157
+ closed = true;
1158
+ prismaClientPromise = null;
1159
+ compatibilityBootstrapPromise = null;
1160
+ void prisma.$disconnect?.();
1161
+ },
1162
+ get closed() {
1163
+ return closed;
1164
+ },
1165
+ get protocol() {
1166
+ return "prisma-postgres";
1167
+ }
1168
+ };
1169
+ return adapter;
1170
+ }
1171
+ var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
1172
+ var init_database_adapter = __esm({
1173
+ "src/lib/database-adapter.ts"() {
1174
+ "use strict";
1175
+ VIEW_MAPPINGS = [
1176
+ { view: "memories", source: "memory.memory_records" },
1177
+ { view: "tasks", source: "memory.tasks" },
1178
+ { view: "behaviors", source: "memory.behaviors" },
1179
+ { view: "entities", source: "memory.entities" },
1180
+ { view: "relationships", source: "memory.relationships" },
1181
+ { view: "entity_memories", source: "memory.entity_memories" },
1182
+ { view: "entity_aliases", source: "memory.entity_aliases" },
1183
+ { view: "notifications", source: "memory.notifications" },
1184
+ { view: "messages", source: "memory.messages" },
1185
+ { view: "users", source: "wiki.users" },
1186
+ { view: "workspaces", source: "wiki.workspaces" },
1187
+ { view: "workspace_users", source: "wiki.workspace_users" },
1188
+ { view: "documents", source: "wiki.workspace_documents" },
1189
+ { view: "chats", source: "wiki.workspace_chats" }
1190
+ ];
1191
+ UPSERT_KEYS = {
1192
+ memories: ["id"],
1193
+ tasks: ["id"],
1194
+ behaviors: ["id"],
1195
+ entities: ["id"],
1196
+ relationships: ["id"],
1197
+ entity_aliases: ["alias"],
1198
+ notifications: ["id"],
1199
+ messages: ["id"],
1200
+ users: ["id"],
1201
+ workspaces: ["id"],
1202
+ workspace_users: ["id"],
1203
+ documents: ["id"],
1204
+ chats: ["id"]
1205
+ };
1206
+ BOOLEAN_COLUMNS_BY_TABLE = {
1207
+ memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
1208
+ behaviors: /* @__PURE__ */ new Set(["active"]),
1209
+ notifications: /* @__PURE__ */ new Set(["read"]),
1210
+ users: /* @__PURE__ */ new Set(["has_personal_memory"])
1211
+ };
1212
+ BOOLEAN_COLUMN_NAMES = new Set(
1213
+ Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
1214
+ );
1215
+ IMMEDIATE_FALLBACK_PATTERNS = [
1216
+ /\bPRAGMA\b/i,
1217
+ /\bsqlite_master\b/i,
1218
+ /(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
1219
+ /\bMATCH\b/i,
1220
+ /\bvector_distance_cos\s*\(/i,
1221
+ /\bjson_extract\s*\(/i,
1222
+ /\bjulianday\s*\(/i,
1223
+ /\bstrftime\s*\(/i,
1224
+ /\blast_insert_rowid\s*\(/i
1225
+ ];
1226
+ prismaClientPromise = null;
1227
+ compatibilityBootstrapPromise = null;
1228
+ }
1229
+ });
1230
+
1231
+ // src/lib/daemon-auth.ts
1232
+ import crypto from "crypto";
1233
+ import path8 from "path";
1234
+ import { existsSync as existsSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
1235
+ function normalizeToken(token) {
1236
+ if (!token) return null;
1237
+ const trimmed = token.trim();
1238
+ return trimmed.length > 0 ? trimmed : null;
1239
+ }
1240
+ function readDaemonToken() {
1241
+ try {
1242
+ if (!existsSync6(DAEMON_TOKEN_PATH)) return null;
1243
+ return normalizeToken(readFileSync6(DAEMON_TOKEN_PATH, "utf8"));
1244
+ } catch {
1245
+ return null;
1246
+ }
1247
+ }
1248
+ function ensureDaemonToken(seed) {
1249
+ const existing = readDaemonToken();
1250
+ if (existing) return existing;
1251
+ const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
1252
+ ensurePrivateDirSync(EXE_AI_DIR);
1253
+ writeFileSync5(DAEMON_TOKEN_PATH, `${token}
1254
+ `, "utf8");
1255
+ enforcePrivateFileSync(DAEMON_TOKEN_PATH);
1256
+ return token;
1257
+ }
1258
+ var DAEMON_TOKEN_PATH;
1259
+ var init_daemon_auth = __esm({
1260
+ "src/lib/daemon-auth.ts"() {
1261
+ "use strict";
1262
+ init_config();
1263
+ init_secure_files();
1264
+ DAEMON_TOKEN_PATH = path8.join(EXE_AI_DIR, "exed.token");
1265
+ }
1266
+ });
1267
+
605
1268
  // src/lib/exe-daemon-client.ts
606
1269
  import net from "net";
607
- import os5 from "os";
1270
+ import os6 from "os";
608
1271
  import { spawn } from "child_process";
609
1272
  import { randomUUID } from "crypto";
610
- import { existsSync as existsSync5, unlinkSync as unlinkSync3, readFileSync as readFileSync6, openSync, closeSync, statSync } from "fs";
611
- import path7 from "path";
1273
+ import { existsSync as existsSync7, unlinkSync as unlinkSync3, readFileSync as readFileSync7, openSync, closeSync, statSync } from "fs";
1274
+ import path9 from "path";
612
1275
  import { fileURLToPath } from "url";
613
1276
  function handleData(chunk) {
614
1277
  _buffer += chunk.toString();
@@ -636,9 +1299,9 @@ function handleData(chunk) {
636
1299
  }
637
1300
  }
638
1301
  function cleanupStaleFiles() {
639
- if (existsSync5(PID_PATH)) {
1302
+ if (existsSync7(PID_PATH)) {
640
1303
  try {
641
- const pid = parseInt(readFileSync6(PID_PATH, "utf8").trim(), 10);
1304
+ const pid = parseInt(readFileSync7(PID_PATH, "utf8").trim(), 10);
642
1305
  if (pid > 0) {
643
1306
  try {
644
1307
  process.kill(pid, 0);
@@ -659,17 +1322,17 @@ function cleanupStaleFiles() {
659
1322
  }
660
1323
  }
661
1324
  function findPackageRoot() {
662
- let dir = path7.dirname(fileURLToPath(import.meta.url));
663
- const { root } = path7.parse(dir);
1325
+ let dir = path9.dirname(fileURLToPath(import.meta.url));
1326
+ const { root } = path9.parse(dir);
664
1327
  while (dir !== root) {
665
- if (existsSync5(path7.join(dir, "package.json"))) return dir;
666
- dir = path7.dirname(dir);
1328
+ if (existsSync7(path9.join(dir, "package.json"))) return dir;
1329
+ dir = path9.dirname(dir);
667
1330
  }
668
1331
  return null;
669
1332
  }
670
1333
  function spawnDaemon() {
671
- const freeGB = os5.freemem() / (1024 * 1024 * 1024);
672
- const totalGB = os5.totalmem() / (1024 * 1024 * 1024);
1334
+ const freeGB = os6.freemem() / (1024 * 1024 * 1024);
1335
+ const totalGB = os6.totalmem() / (1024 * 1024 * 1024);
673
1336
  if (totalGB <= 8) {
674
1337
  process.stderr.write(
675
1338
  `[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
@@ -689,16 +1352,17 @@ function spawnDaemon() {
689
1352
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
690
1353
  return;
691
1354
  }
692
- const daemonPath = path7.join(pkgRoot, "dist", "lib", "exe-daemon.js");
693
- if (!existsSync5(daemonPath)) {
1355
+ const daemonPath = path9.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1356
+ if (!existsSync7(daemonPath)) {
694
1357
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
695
1358
  `);
696
1359
  return;
697
1360
  }
698
1361
  const resolvedPath = daemonPath;
1362
+ const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
699
1363
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
700
1364
  `);
701
- const logPath = path7.join(path7.dirname(SOCKET_PATH), "exed.log");
1365
+ const logPath = path9.join(path9.dirname(SOCKET_PATH), "exed.log");
702
1366
  let stderrFd = "ignore";
703
1367
  try {
704
1368
  stderrFd = openSync(logPath, "a");
@@ -716,7 +1380,8 @@ function spawnDaemon() {
716
1380
  TMUX_PANE: void 0,
717
1381
  // Prevents resolveExeSession() from scoping to one session
718
1382
  EXE_DAEMON_SOCK: SOCKET_PATH,
719
- EXE_DAEMON_PID: PID_PATH
1383
+ EXE_DAEMON_PID: PID_PATH,
1384
+ [DAEMON_TOKEN_ENV]: daemonToken
720
1385
  }
721
1386
  });
722
1387
  child.unref();
@@ -823,13 +1488,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
823
1488
  return;
824
1489
  }
825
1490
  const id = randomUUID();
1491
+ const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
826
1492
  const timer = setTimeout(() => {
827
1493
  _pending.delete(id);
828
1494
  resolve({ error: "Request timeout" });
829
1495
  }, timeoutMs);
830
1496
  _pending.set(id, { resolve, timer });
831
1497
  try {
832
- _socket.write(JSON.stringify({ id, ...payload }) + "\n");
1498
+ _socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
833
1499
  } catch {
834
1500
  clearTimeout(timer);
835
1501
  _pending.delete(id);
@@ -840,17 +1506,19 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
840
1506
  function isClientConnected() {
841
1507
  return _connected;
842
1508
  }
843
- var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
1509
+ 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;
844
1510
  var init_exe_daemon_client = __esm({
845
1511
  "src/lib/exe-daemon-client.ts"() {
846
1512
  "use strict";
847
1513
  init_config();
848
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path7.join(EXE_AI_DIR, "exed.sock");
849
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path7.join(EXE_AI_DIR, "exed.pid");
850
- SPAWN_LOCK_PATH = path7.join(EXE_AI_DIR, "exed-spawn.lock");
1514
+ init_daemon_auth();
1515
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path9.join(EXE_AI_DIR, "exed.sock");
1516
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path9.join(EXE_AI_DIR, "exed.pid");
1517
+ SPAWN_LOCK_PATH = path9.join(EXE_AI_DIR, "exed-spawn.lock");
851
1518
  SPAWN_LOCK_STALE_MS = 3e4;
852
1519
  CONNECT_TIMEOUT_MS = 15e3;
853
1520
  REQUEST_TIMEOUT_MS = 3e4;
1521
+ DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
854
1522
  _socket = null;
855
1523
  _connected = false;
856
1524
  _buffer = "";
@@ -929,7 +1597,7 @@ __export(db_daemon_client_exports, {
929
1597
  createDaemonDbClient: () => createDaemonDbClient,
930
1598
  initDaemonDbClient: () => initDaemonDbClient
931
1599
  });
932
- function normalizeStatement(stmt) {
1600
+ function normalizeStatement2(stmt) {
933
1601
  if (typeof stmt === "string") {
934
1602
  return { sql: stmt, args: [] };
935
1603
  }
@@ -953,7 +1621,7 @@ function createDaemonDbClient(fallbackClient) {
953
1621
  if (!_useDaemon || !isClientConnected()) {
954
1622
  return fallbackClient.execute(stmt);
955
1623
  }
956
- const { sql, args } = normalizeStatement(stmt);
1624
+ const { sql, args } = normalizeStatement2(stmt);
957
1625
  const response = await sendDaemonRequest({
958
1626
  type: "db-execute",
959
1627
  sql,
@@ -978,7 +1646,7 @@ function createDaemonDbClient(fallbackClient) {
978
1646
  if (!_useDaemon || !isClientConnected()) {
979
1647
  return fallbackClient.batch(stmts, mode);
980
1648
  }
981
- const statements = stmts.map(normalizeStatement);
1649
+ const statements = stmts.map(normalizeStatement2);
982
1650
  const response = await sendDaemonRequest({
983
1651
  type: "db-batch",
984
1652
  statements,
@@ -1073,6 +1741,18 @@ __export(database_exports, {
1073
1741
  });
1074
1742
  import { createClient } from "@libsql/client";
1075
1743
  async function initDatabase(config) {
1744
+ if (_walCheckpointTimer) {
1745
+ clearInterval(_walCheckpointTimer);
1746
+ _walCheckpointTimer = null;
1747
+ }
1748
+ if (_daemonClient) {
1749
+ _daemonClient.close();
1750
+ _daemonClient = null;
1751
+ }
1752
+ if (_adapterClient && _adapterClient !== _resilientClient) {
1753
+ _adapterClient.close();
1754
+ }
1755
+ _adapterClient = null;
1076
1756
  if (_client) {
1077
1757
  _client.close();
1078
1758
  _client = null;
@@ -1086,6 +1766,7 @@ async function initDatabase(config) {
1086
1766
  }
1087
1767
  _client = createClient(opts);
1088
1768
  _resilientClient = wrapWithRetry(_client);
1769
+ _adapterClient = _resilientClient;
1089
1770
  _client.execute("PRAGMA busy_timeout = 30000").catch(() => {
1090
1771
  });
1091
1772
  _client.execute("PRAGMA journal_mode = WAL").catch(() => {
@@ -1096,14 +1777,20 @@ async function initDatabase(config) {
1096
1777
  });
1097
1778
  }, 3e4);
1098
1779
  _walCheckpointTimer.unref();
1780
+ if (process.env.DATABASE_URL) {
1781
+ _adapterClient = await createPrismaDbAdapter(_resilientClient);
1782
+ }
1099
1783
  }
1100
1784
  function isInitialized() {
1101
- return _client !== null;
1785
+ return _adapterClient !== null || _client !== null;
1102
1786
  }
1103
1787
  function getClient() {
1104
- if (!_resilientClient) {
1788
+ if (!_adapterClient) {
1105
1789
  throw new Error("Database client not initialized. Call initDatabase() first.");
1106
1790
  }
1791
+ if (process.env.DATABASE_URL) {
1792
+ return _adapterClient;
1793
+ }
1107
1794
  if (process.env.EXE_IS_DAEMON === "1") {
1108
1795
  return _resilientClient;
1109
1796
  }
@@ -1113,6 +1800,7 @@ function getClient() {
1113
1800
  return _resilientClient;
1114
1801
  }
1115
1802
  async function initDaemonClient() {
1803
+ if (process.env.DATABASE_URL) return;
1116
1804
  if (process.env.EXE_IS_DAEMON === "1") return;
1117
1805
  if (!_resilientClient) return;
1118
1806
  try {
@@ -1409,6 +2097,7 @@ async function ensureSchema() {
1409
2097
  project TEXT NOT NULL,
1410
2098
  summary TEXT NOT NULL,
1411
2099
  task_file TEXT,
2100
+ session_scope TEXT,
1412
2101
  read INTEGER NOT NULL DEFAULT 0,
1413
2102
  created_at TEXT NOT NULL
1414
2103
  );
@@ -1417,7 +2106,7 @@ async function ensureSchema() {
1417
2106
  ON notifications(read);
1418
2107
 
1419
2108
  CREATE INDEX IF NOT EXISTS idx_notifications_agent
1420
- ON notifications(agent_id);
2109
+ ON notifications(agent_id, session_scope);
1421
2110
 
1422
2111
  CREATE INDEX IF NOT EXISTS idx_notifications_task_file
1423
2112
  ON notifications(task_file);
@@ -1455,6 +2144,7 @@ async function ensureSchema() {
1455
2144
  target_agent TEXT NOT NULL,
1456
2145
  target_project TEXT,
1457
2146
  target_device TEXT NOT NULL DEFAULT 'local',
2147
+ session_scope TEXT,
1458
2148
  content TEXT NOT NULL,
1459
2149
  priority TEXT DEFAULT 'normal',
1460
2150
  status TEXT DEFAULT 'pending',
@@ -1468,10 +2158,31 @@ async function ensureSchema() {
1468
2158
  );
1469
2159
 
1470
2160
  CREATE INDEX IF NOT EXISTS idx_messages_target
1471
- ON messages(target_agent, status);
2161
+ ON messages(target_agent, session_scope, status);
1472
2162
 
1473
2163
  CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
1474
- ON messages(target_agent, from_agent, server_seq);
2164
+ ON messages(target_agent, session_scope, from_agent, server_seq);
2165
+ `);
2166
+ try {
2167
+ await client.execute({
2168
+ sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
2169
+ args: []
2170
+ });
2171
+ } catch {
2172
+ }
2173
+ try {
2174
+ await client.execute({
2175
+ sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
2176
+ args: []
2177
+ });
2178
+ } catch {
2179
+ }
2180
+ await client.executeMultiple(`
2181
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
2182
+ ON notifications(agent_id, session_scope, read, created_at);
2183
+
2184
+ CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
2185
+ ON messages(target_agent, session_scope, status, created_at);
1475
2186
  `);
1476
2187
  try {
1477
2188
  await client.execute({
@@ -2055,52 +2766,72 @@ async function ensureSchema() {
2055
2766
  } catch {
2056
2767
  }
2057
2768
  }
2769
+ try {
2770
+ await client.execute({
2771
+ sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
2772
+ args: []
2773
+ });
2774
+ } catch {
2775
+ }
2058
2776
  }
2059
2777
  async function disposeDatabase() {
2778
+ if (_walCheckpointTimer) {
2779
+ clearInterval(_walCheckpointTimer);
2780
+ _walCheckpointTimer = null;
2781
+ }
2060
2782
  if (_daemonClient) {
2061
2783
  _daemonClient.close();
2062
2784
  _daemonClient = null;
2063
2785
  }
2786
+ if (_adapterClient && _adapterClient !== _resilientClient) {
2787
+ _adapterClient.close();
2788
+ }
2789
+ _adapterClient = null;
2064
2790
  if (_client) {
2065
2791
  _client.close();
2066
2792
  _client = null;
2067
2793
  _resilientClient = null;
2068
2794
  }
2069
2795
  }
2070
- var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso, disposeTurso;
2796
+ var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
2071
2797
  var init_database = __esm({
2072
2798
  "src/lib/database.ts"() {
2073
2799
  "use strict";
2074
2800
  init_db_retry();
2075
2801
  init_employees();
2802
+ init_database_adapter();
2076
2803
  _client = null;
2077
2804
  _resilientClient = null;
2078
2805
  _walCheckpointTimer = null;
2079
2806
  _daemonClient = null;
2807
+ _adapterClient = null;
2080
2808
  initTurso = initDatabase;
2081
2809
  disposeTurso = disposeDatabase;
2082
2810
  }
2083
2811
  });
2084
2812
 
2085
2813
  // src/lib/license.ts
2086
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
2814
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync8, mkdirSync as mkdirSync4 } from "fs";
2087
2815
  import { randomUUID as randomUUID2 } from "crypto";
2088
- import path8 from "path";
2816
+ import { createRequire as createRequire2 } from "module";
2817
+ import { pathToFileURL as pathToFileURL2 } from "url";
2818
+ import os7 from "os";
2819
+ import path10 from "path";
2089
2820
  import { jwtVerify, importSPKI } from "jose";
2090
2821
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH;
2091
2822
  var init_license = __esm({
2092
2823
  "src/lib/license.ts"() {
2093
2824
  "use strict";
2094
2825
  init_config();
2095
- LICENSE_PATH = path8.join(EXE_AI_DIR, "license.key");
2096
- CACHE_PATH = path8.join(EXE_AI_DIR, "license-cache.json");
2097
- DEVICE_ID_PATH = path8.join(EXE_AI_DIR, "device-id");
2826
+ LICENSE_PATH = path10.join(EXE_AI_DIR, "license.key");
2827
+ CACHE_PATH = path10.join(EXE_AI_DIR, "license-cache.json");
2828
+ DEVICE_ID_PATH = path10.join(EXE_AI_DIR, "device-id");
2098
2829
  }
2099
2830
  });
2100
2831
 
2101
2832
  // src/lib/plan-limits.ts
2102
- import { readFileSync as readFileSync8, existsSync as existsSync7 } from "fs";
2103
- import path9 from "path";
2833
+ import { readFileSync as readFileSync9, existsSync as existsSync9 } from "fs";
2834
+ import path11 from "path";
2104
2835
  var CACHE_PATH2;
2105
2836
  var init_plan_limits = __esm({
2106
2837
  "src/lib/plan-limits.ts"() {
@@ -2109,14 +2840,14 @@ var init_plan_limits = __esm({
2109
2840
  init_employees();
2110
2841
  init_license();
2111
2842
  init_config();
2112
- CACHE_PATH2 = path9.join(EXE_AI_DIR, "license-cache.json");
2843
+ CACHE_PATH2 = path11.join(EXE_AI_DIR, "license-cache.json");
2113
2844
  }
2114
2845
  });
2115
2846
 
2116
2847
  // src/lib/tmux-routing.ts
2117
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, existsSync as existsSync8, appendFileSync, readdirSync as readdirSync2 } from "fs";
2118
- import path10 from "path";
2119
- import os6 from "os";
2848
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, mkdirSync as mkdirSync5, existsSync as existsSync10, appendFileSync, readdirSync as readdirSync2 } from "fs";
2849
+ import path12 from "path";
2850
+ import os8 from "os";
2120
2851
  import { fileURLToPath as fileURLToPath2 } from "url";
2121
2852
  function getMySession() {
2122
2853
  return getTransport().getMySession();
@@ -2129,7 +2860,7 @@ function extractRootExe(name) {
2129
2860
  }
2130
2861
  function getParentExe(sessionKey) {
2131
2862
  try {
2132
- const data = JSON.parse(readFileSync9(path10.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
2863
+ const data = JSON.parse(readFileSync10(path12.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
2133
2864
  return data.parentExe || null;
2134
2865
  } catch {
2135
2866
  return null;
@@ -2172,10 +2903,10 @@ var init_tmux_routing = __esm({
2172
2903
  init_intercom_queue();
2173
2904
  init_plan_limits();
2174
2905
  init_employees();
2175
- SPAWN_LOCK_DIR = path10.join(os6.homedir(), ".exe-os", "spawn-locks");
2176
- SESSION_CACHE = path10.join(os6.homedir(), ".exe-os", "session-cache");
2177
- INTERCOM_LOG2 = path10.join(os6.homedir(), ".exe-os", "intercom.log");
2178
- DEBOUNCE_FILE = path10.join(SESSION_CACHE, "intercom-debounce.json");
2906
+ SPAWN_LOCK_DIR = path12.join(os8.homedir(), ".exe-os", "spawn-locks");
2907
+ SESSION_CACHE = path12.join(os8.homedir(), ".exe-os", "session-cache");
2908
+ INTERCOM_LOG2 = path12.join(os8.homedir(), ".exe-os", "intercom.log");
2909
+ DEBOUNCE_FILE = path12.join(SESSION_CACHE, "intercom-debounce.json");
2179
2910
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
2180
2911
  }
2181
2912
  });
@@ -2215,14 +2946,14 @@ var init_memory = __esm({
2215
2946
 
2216
2947
  // src/lib/keychain.ts
2217
2948
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
2218
- import { existsSync as existsSync9 } from "fs";
2219
- import path11 from "path";
2220
- import os7 from "os";
2949
+ import { existsSync as existsSync11 } from "fs";
2950
+ import path13 from "path";
2951
+ import os9 from "os";
2221
2952
  function getKeyDir() {
2222
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path11.join(os7.homedir(), ".exe-os");
2953
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path13.join(os9.homedir(), ".exe-os");
2223
2954
  }
2224
2955
  function getKeyPath() {
2225
- return path11.join(getKeyDir(), "master.key");
2956
+ return path13.join(getKeyDir(), "master.key");
2226
2957
  }
2227
2958
  async function tryKeytar() {
2228
2959
  try {
@@ -2243,9 +2974,9 @@ async function getMasterKey() {
2243
2974
  }
2244
2975
  }
2245
2976
  const keyPath = getKeyPath();
2246
- if (!existsSync9(keyPath)) {
2977
+ if (!existsSync11(keyPath)) {
2247
2978
  process.stderr.write(
2248
- `[keychain] Key not found at ${keyPath} (HOME=${os7.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
2979
+ `[keychain] Key not found at ${keyPath} (HOME=${os9.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
2249
2980
  `
2250
2981
  );
2251
2982
  return null;
@@ -2330,6 +3061,7 @@ var shard_manager_exports = {};
2330
3061
  __export(shard_manager_exports, {
2331
3062
  disposeShards: () => disposeShards,
2332
3063
  ensureShardSchema: () => ensureShardSchema,
3064
+ getOpenShardCount: () => getOpenShardCount,
2333
3065
  getReadyShardClient: () => getReadyShardClient,
2334
3066
  getShardClient: () => getShardClient,
2335
3067
  getShardsDir: () => getShardsDir,
@@ -2338,15 +3070,18 @@ __export(shard_manager_exports, {
2338
3070
  listShards: () => listShards,
2339
3071
  shardExists: () => shardExists
2340
3072
  });
2341
- import path12 from "path";
2342
- import { existsSync as existsSync10, mkdirSync as mkdirSync6, readdirSync as readdirSync3 } from "fs";
3073
+ import path14 from "path";
3074
+ import { existsSync as existsSync12, mkdirSync as mkdirSync6, readdirSync as readdirSync3 } from "fs";
2343
3075
  import { createClient as createClient2 } from "@libsql/client";
2344
3076
  function initShardManager(encryptionKey) {
2345
3077
  _encryptionKey = encryptionKey;
2346
- if (!existsSync10(SHARDS_DIR)) {
3078
+ if (!existsSync12(SHARDS_DIR)) {
2347
3079
  mkdirSync6(SHARDS_DIR, { recursive: true });
2348
3080
  }
2349
3081
  _shardingEnabled = true;
3082
+ if (_evictionTimer) clearInterval(_evictionTimer);
3083
+ _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
3084
+ _evictionTimer.unref();
2350
3085
  }
2351
3086
  function isShardingEnabled() {
2352
3087
  return _shardingEnabled;
@@ -2363,21 +3098,28 @@ function getShardClient(projectName) {
2363
3098
  throw new Error(`Invalid project name for shard: "${projectName}"`);
2364
3099
  }
2365
3100
  const cached = _shards.get(safeName);
2366
- if (cached) return cached;
2367
- const dbPath = path12.join(SHARDS_DIR, `${safeName}.db`);
3101
+ if (cached) {
3102
+ _shardLastAccess.set(safeName, Date.now());
3103
+ return cached;
3104
+ }
3105
+ while (_shards.size >= MAX_OPEN_SHARDS) {
3106
+ evictLRU();
3107
+ }
3108
+ const dbPath = path14.join(SHARDS_DIR, `${safeName}.db`);
2368
3109
  const client = createClient2({
2369
3110
  url: `file:${dbPath}`,
2370
3111
  encryptionKey: _encryptionKey
2371
3112
  });
2372
3113
  _shards.set(safeName, client);
3114
+ _shardLastAccess.set(safeName, Date.now());
2373
3115
  return client;
2374
3116
  }
2375
3117
  function shardExists(projectName) {
2376
3118
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
2377
- return existsSync10(path12.join(SHARDS_DIR, `${safeName}.db`));
3119
+ return existsSync12(path14.join(SHARDS_DIR, `${safeName}.db`));
2378
3120
  }
2379
3121
  function listShards() {
2380
- if (!existsSync10(SHARDS_DIR)) return [];
3122
+ if (!existsSync12(SHARDS_DIR)) return [];
2381
3123
  return readdirSync3(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
2382
3124
  }
2383
3125
  async function ensureShardSchema(client) {
@@ -2429,6 +3171,8 @@ async function ensureShardSchema(client) {
2429
3171
  for (const col of [
2430
3172
  "ALTER TABLE memories ADD COLUMN task_id TEXT",
2431
3173
  "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
3174
+ "ALTER TABLE memories ADD COLUMN author_device_id TEXT",
3175
+ "ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
2432
3176
  "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
2433
3177
  "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
2434
3178
  "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
@@ -2451,7 +3195,23 @@ async function ensureShardSchema(client) {
2451
3195
  // MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
2452
3196
  "ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
2453
3197
  "ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
2454
- "ALTER TABLE memories ADD COLUMN trajectory TEXT"
3198
+ "ALTER TABLE memories ADD COLUMN trajectory TEXT",
3199
+ // Metadata enrichment columns (must match database.ts)
3200
+ "ALTER TABLE memories ADD COLUMN intent TEXT",
3201
+ "ALTER TABLE memories ADD COLUMN outcome TEXT",
3202
+ "ALTER TABLE memories ADD COLUMN domain TEXT",
3203
+ "ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
3204
+ "ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
3205
+ "ALTER TABLE memories ADD COLUMN chain_position TEXT",
3206
+ "ALTER TABLE memories ADD COLUMN review_status TEXT",
3207
+ "ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
3208
+ "ALTER TABLE memories ADD COLUMN file_paths TEXT",
3209
+ "ALTER TABLE memories ADD COLUMN commit_hash TEXT",
3210
+ "ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
3211
+ "ALTER TABLE memories ADD COLUMN token_cost REAL",
3212
+ "ALTER TABLE memories ADD COLUMN audience TEXT",
3213
+ "ALTER TABLE memories ADD COLUMN language_type TEXT",
3214
+ "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
2455
3215
  ]) {
2456
3216
  try {
2457
3217
  await client.execute(col);
@@ -2550,21 +3310,69 @@ async function getReadyShardClient(projectName) {
2550
3310
  await ensureShardSchema(client);
2551
3311
  return client;
2552
3312
  }
3313
+ function evictLRU() {
3314
+ let oldest = null;
3315
+ let oldestTime = Infinity;
3316
+ for (const [name, time] of _shardLastAccess) {
3317
+ if (time < oldestTime) {
3318
+ oldestTime = time;
3319
+ oldest = name;
3320
+ }
3321
+ }
3322
+ if (oldest) {
3323
+ const client = _shards.get(oldest);
3324
+ if (client) {
3325
+ client.close();
3326
+ }
3327
+ _shards.delete(oldest);
3328
+ _shardLastAccess.delete(oldest);
3329
+ }
3330
+ }
3331
+ function evictIdleShards() {
3332
+ const now = Date.now();
3333
+ const toEvict = [];
3334
+ for (const [name, lastAccess] of _shardLastAccess) {
3335
+ if (now - lastAccess > SHARD_IDLE_MS) {
3336
+ toEvict.push(name);
3337
+ }
3338
+ }
3339
+ for (const name of toEvict) {
3340
+ const client = _shards.get(name);
3341
+ if (client) {
3342
+ client.close();
3343
+ }
3344
+ _shards.delete(name);
3345
+ _shardLastAccess.delete(name);
3346
+ }
3347
+ }
3348
+ function getOpenShardCount() {
3349
+ return _shards.size;
3350
+ }
2553
3351
  function disposeShards() {
3352
+ if (_evictionTimer) {
3353
+ clearInterval(_evictionTimer);
3354
+ _evictionTimer = null;
3355
+ }
2554
3356
  for (const [, client] of _shards) {
2555
3357
  client.close();
2556
3358
  }
2557
3359
  _shards.clear();
3360
+ _shardLastAccess.clear();
2558
3361
  _shardingEnabled = false;
2559
3362
  _encryptionKey = null;
2560
3363
  }
2561
- var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
3364
+ var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
2562
3365
  var init_shard_manager = __esm({
2563
3366
  "src/lib/shard-manager.ts"() {
2564
3367
  "use strict";
2565
3368
  init_config();
2566
- SHARDS_DIR = path12.join(EXE_AI_DIR, "shards");
3369
+ SHARDS_DIR = path14.join(EXE_AI_DIR, "shards");
3370
+ SHARD_IDLE_MS = 5 * 60 * 1e3;
3371
+ MAX_OPEN_SHARDS = 10;
3372
+ EVICTION_INTERVAL_MS = 60 * 1e3;
2567
3373
  _shards = /* @__PURE__ */ new Map();
3374
+ _shardLastAccess = /* @__PURE__ */ new Map();
3375
+ _evictionTimer = null;
2568
3376
  _encryptionKey = null;
2569
3377
  _shardingEnabled = false;
2570
3378
  }
@@ -3331,15 +4139,15 @@ var init_store = __esm({
3331
4139
  init_config();
3332
4140
  init_config();
3333
4141
  import { spawn as spawn2 } from "child_process";
3334
- import { existsSync as existsSync11, openSync as openSync2, closeSync as closeSync2 } from "fs";
3335
- import path13 from "path";
4142
+ import { existsSync as existsSync13, openSync as openSync2, closeSync as closeSync2 } from "fs";
4143
+ import path15 from "path";
3336
4144
  import { fileURLToPath as fileURLToPath3 } from "url";
3337
4145
 
3338
4146
  // src/lib/active-agent.ts
3339
4147
  init_config();
3340
4148
  init_session_key();
3341
4149
  init_employees();
3342
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync, unlinkSync as unlinkSync2, readdirSync } from "fs";
4150
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2, readdirSync } from "fs";
3343
4151
  import { execSync as execSync3 } from "child_process";
3344
4152
  import path3 from "path";
3345
4153
  var CACHE_DIR = path3.join(EXE_AI_DIR, "session-cache");
@@ -3442,7 +4250,7 @@ if (!process.env.AGENT_ID) {
3442
4250
  if (!loadConfigSync().autoIngestion) {
3443
4251
  process.exit(0);
3444
4252
  }
3445
- var WORKER_LOG_PATH = path13.join(EXE_AI_DIR, "workers.log");
4253
+ var WORKER_LOG_PATH = path15.join(EXE_AI_DIR, "workers.log");
3446
4254
  function openWorkerLog() {
3447
4255
  try {
3448
4256
  return openSync2(WORKER_LOG_PATH, "a");
@@ -3450,6 +4258,25 @@ function openWorkerLog() {
3450
4258
  return "ignore";
3451
4259
  }
3452
4260
  }
4261
+ function spawnDetachedWorker(workerPath, env, cwd) {
4262
+ if (!existsSync13(workerPath)) {
4263
+ process.stderr.write(`[stop] WARN: worker not found at ${workerPath}
4264
+ `);
4265
+ return;
4266
+ }
4267
+ const stderrFd = openWorkerLog();
4268
+ const worker = spawn2(process.execPath, [workerPath], {
4269
+ cwd,
4270
+ detached: true,
4271
+ stdio: ["ignore", "ignore", stderrFd],
4272
+ env
4273
+ });
4274
+ worker.unref();
4275
+ if (typeof stderrFd === "number") try {
4276
+ closeSync2(stderrFd);
4277
+ } catch {
4278
+ }
4279
+ }
3453
4280
  var MIN_LENGTH = 100;
3454
4281
  var timeout = setTimeout(() => {
3455
4282
  process.exit(0);
@@ -3465,22 +4292,36 @@ process.stdin.on("end", () => {
3465
4292
  try {
3466
4293
  if (process.env.EXE_DEBUG_HOOKS || process.env.EXE_RUNTIME === "codex") {
3467
4294
  try {
3468
- const debugPath = path13.join(EXE_AI_DIR, "logs", "hook-stdin-stop.log");
3469
- const { mkdirSync: mkdirSync7, writeFileSync: writeFileSync7 } = __require("fs");
3470
- mkdirSync7(path13.dirname(debugPath), { recursive: true });
4295
+ const debugPath = path15.join(EXE_AI_DIR, "logs", "hook-stdin-stop.log");
4296
+ const { mkdirSync: mkdirSync7, writeFileSync: writeFileSync8 } = __require("fs");
4297
+ mkdirSync7(path15.dirname(debugPath), { recursive: true });
3471
4298
  const ts = (/* @__PURE__ */ new Date()).toISOString();
3472
4299
  const snippet = input.length > 500 ? input.slice(0, 500) + "...[truncated]" : input;
3473
- writeFileSync7(debugPath, `[${ts}] len=${input.length} ${snippet}
4300
+ writeFileSync8(debugPath, `[${ts}] len=${input.length} ${snippet}
3474
4301
  `, { flag: "a" });
3475
4302
  } catch {
3476
4303
  }
3477
4304
  }
3478
4305
  const data = JSON.parse(input);
3479
- const message = data.last_assistant_message;
4306
+ const agent = getActiveAgent();
4307
+ const message = data.last_assistant_message ?? "";
4308
+ const cwd = data.cwd ?? process.cwd();
4309
+ if (process.env.EXE_RUNTIME === "codex" && !canCoordinate(agent.agentId, agent.agentRole)) {
4310
+ const codexFinalizerPath = path15.resolve(
4311
+ path15.dirname(fileURLToPath3(import.meta.url)),
4312
+ "codex-stop-task-finalizer.js"
4313
+ );
4314
+ spawnDetachedWorker(codexFinalizerPath, {
4315
+ ...process.env,
4316
+ AGENT_ID: agent.agentId,
4317
+ AGENT_ROLE: agent.agentRole,
4318
+ EXE_RESPONSE_TEXT: message.slice(0, 5e3),
4319
+ EXE_SESSION_ID: data.session_id
4320
+ }, cwd);
4321
+ }
3480
4322
  if (!message || message.length < MIN_LENGTH) {
3481
4323
  process.exit(0);
3482
4324
  }
3483
- const agent = getActiveAgent();
3484
4325
  const CAPACITY_SIGNALS = /context[- ]?full|hit capacity|conversation is too long|maximum context length|context window.*(?:limit|exceed|full)/i;
3485
4326
  if (!canCoordinate(agent.agentId, agent.agentRole) && CAPACITY_SIGNALS.test(message)) {
3486
4327
  Promise.resolve().then(() => (init_store(), store_exports)).then(({ initStore: initStore2 }) => initStore2()).then(() => Promise.all([
@@ -3505,9 +4346,9 @@ process.stdin.on("end", () => {
3505
4346
  "",
3506
4347
  `Last response fragment: ${message.slice(0, 500)}`
3507
4348
  ].join("\n");
3508
- const crypto = await import("crypto");
4349
+ const crypto2 = await import("crypto");
3509
4350
  await writeMemory2({
3510
- id: crypto.randomUUID(),
4351
+ id: crypto2.randomUUID(),
3511
4352
  agent_id: agent.agentId,
3512
4353
  agent_role: agent.agentRole,
3513
4354
  session_id: data.session_id,
@@ -3546,32 +4387,17 @@ process.stdin.on("end", () => {
3546
4387
  }).catch(() => {
3547
4388
  });
3548
4389
  }
3549
- const workerPath = path13.resolve(
3550
- path13.dirname(fileURLToPath3(import.meta.url)),
4390
+ const workerPath = path15.resolve(
4391
+ path15.dirname(fileURLToPath3(import.meta.url)),
3551
4392
  "response-ingest-worker.js"
3552
4393
  );
3553
- if (!existsSync11(workerPath)) {
3554
- process.stderr.write(`[stop] WARN: response-ingest-worker not found at ${workerPath}
3555
- `);
3556
- process.exit(0);
3557
- }
3558
- const stderrFd = openWorkerLog();
3559
- const worker = spawn2(process.execPath, [workerPath], {
3560
- detached: true,
3561
- stdio: ["ignore", "ignore", stderrFd],
3562
- env: {
3563
- ...process.env,
3564
- AGENT_ID: agent.agentId,
3565
- AGENT_ROLE: agent.agentRole,
3566
- EXE_RESPONSE_TEXT: message.slice(0, 5e3),
3567
- EXE_SESSION_ID: data.session_id
3568
- }
3569
- });
3570
- worker.unref();
3571
- if (typeof stderrFd === "number") try {
3572
- closeSync2(stderrFd);
3573
- } catch {
3574
- }
4394
+ spawnDetachedWorker(workerPath, {
4395
+ ...process.env,
4396
+ AGENT_ID: agent.agentId,
4397
+ AGENT_ROLE: agent.agentRole,
4398
+ EXE_RESPONSE_TEXT: message.slice(0, 5e3),
4399
+ EXE_SESSION_ID: data.session_id
4400
+ }, cwd);
3575
4401
  } catch {
3576
4402
  }
3577
4403
  process.exit(0);