@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 path2 from "path";
32
70
  import os2 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 = path2.join(os2.homedir(), ".exe-os");
37
75
  const legacyDir = path2.join(os2.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 = path2.join(dir, "config.json");
103
- if (!existsSync(configPath)) {
141
+ if (!existsSync2(configPath)) {
104
142
  return { ...DEFAULT_CONFIG, dbPath: path2.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
  }
@@ -132,6 +171,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
132
171
  var init_config = __esm({
133
172
  "src/lib/config.ts"() {
134
173
  "use strict";
174
+ init_secure_files();
135
175
  EXE_AI_DIR = resolveDataDir();
136
176
  DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
137
177
  MODELS_DIR = path2.join(EXE_AI_DIR, "models");
@@ -210,7 +250,7 @@ var init_config = __esm({
210
250
 
211
251
  // src/lib/employees.ts
212
252
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
213
- import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
253
+ import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
214
254
  import { execSync } from "child_process";
215
255
  import path3 from "path";
216
256
  import os3 from "os";
@@ -231,7 +271,7 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
231
271
  return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
232
272
  }
233
273
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
234
- if (!existsSync2(employeesPath)) return [];
274
+ if (!existsSync3(employeesPath)) return [];
235
275
  try {
236
276
  return JSON.parse(readFileSync2(employeesPath, "utf-8"));
237
277
  } catch {
@@ -255,7 +295,7 @@ function isMultiInstance(agentName, employees) {
255
295
  if (!emp) return false;
256
296
  return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
257
297
  }
258
- var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
298
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR;
259
299
  var init_employees = __esm({
260
300
  "src/lib/employees.ts"() {
261
301
  "use strict";
@@ -264,6 +304,7 @@ var init_employees = __esm({
264
304
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
265
305
  COORDINATOR_ROLE = "COO";
266
306
  MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
307
+ IDENTITY_DIR = path3.join(EXE_AI_DIR, "identity");
267
308
  }
268
309
  });
269
310
 
@@ -274,14 +315,14 @@ __export(session_registry_exports, {
274
315
  pruneStaleSessions: () => pruneStaleSessions,
275
316
  registerSession: () => registerSession
276
317
  });
277
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync, existsSync as existsSync3 } from "fs";
318
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync4 } from "fs";
278
319
  import { execSync as execSync2 } from "child_process";
279
320
  import path4 from "path";
280
321
  import os4 from "os";
281
322
  function registerSession(entry) {
282
323
  const dir = path4.dirname(REGISTRY_PATH);
283
- if (!existsSync3(dir)) {
284
- mkdirSync(dir, { recursive: true });
324
+ if (!existsSync4(dir)) {
325
+ mkdirSync2(dir, { recursive: true });
285
326
  }
286
327
  const sessions = listSessions();
287
328
  const idx = sessions.findIndex((s) => s.windowName === entry.windowName);
@@ -613,10 +654,10 @@ var init_runtime_table = __esm({
613
654
  });
614
655
 
615
656
  // src/lib/agent-config.ts
616
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
657
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync5 } from "fs";
617
658
  import path5 from "path";
618
659
  function loadAgentConfig() {
619
- if (!existsSync4(AGENT_CONFIG_PATH)) return {};
660
+ if (!existsSync5(AGENT_CONFIG_PATH)) return {};
620
661
  try {
621
662
  return JSON.parse(readFileSync4(AGENT_CONFIG_PATH, "utf-8"));
622
663
  } catch {
@@ -637,6 +678,7 @@ var init_agent_config = __esm({
637
678
  "use strict";
638
679
  init_config();
639
680
  init_runtime_table();
681
+ init_secure_files();
640
682
  AGENT_CONFIG_PATH = path5.join(EXE_AI_DIR, "agent-config.json");
641
683
  DEFAULT_MODELS = {
642
684
  claude: "claude-opus-4",
@@ -655,16 +697,16 @@ __export(intercom_queue_exports, {
655
697
  queueIntercom: () => queueIntercom,
656
698
  readQueue: () => readQueue
657
699
  });
658
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
700
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
659
701
  import path6 from "path";
660
702
  import os5 from "os";
661
703
  function ensureDir() {
662
704
  const dir = path6.dirname(QUEUE_PATH);
663
- if (!existsSync5(dir)) mkdirSync3(dir, { recursive: true });
705
+ if (!existsSync6(dir)) mkdirSync3(dir, { recursive: true });
664
706
  }
665
707
  function readQueue() {
666
708
  try {
667
- if (!existsSync5(QUEUE_PATH)) return [];
709
+ if (!existsSync6(QUEUE_PATH)) return [];
668
710
  return JSON.parse(readFileSync5(QUEUE_PATH, "utf8"));
669
711
  } catch {
670
712
  return [];
@@ -827,13 +869,634 @@ var init_db_retry = __esm({
827
869
  }
828
870
  });
829
871
 
872
+ // src/lib/database-adapter.ts
873
+ import os6 from "os";
874
+ import path7 from "path";
875
+ import { createRequire } from "module";
876
+ import { pathToFileURL } from "url";
877
+ function quotedIdentifier(identifier) {
878
+ return `"${identifier.replace(/"/g, '""')}"`;
879
+ }
880
+ function unqualifiedTableName(name) {
881
+ const raw = name.trim().replace(/^"|"$/g, "");
882
+ const parts = raw.split(".");
883
+ return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
884
+ }
885
+ function stripTrailingSemicolon(sql) {
886
+ return sql.trim().replace(/;+\s*$/u, "");
887
+ }
888
+ function appendClause(sql, clause) {
889
+ const trimmed = stripTrailingSemicolon(sql);
890
+ const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
891
+ if (!returningMatch) {
892
+ return `${trimmed}${clause}`;
893
+ }
894
+ const idx = returningMatch.index;
895
+ return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
896
+ }
897
+ function normalizeStatement(stmt) {
898
+ if (typeof stmt === "string") {
899
+ return { kind: "positional", sql: stmt, args: [] };
900
+ }
901
+ const sql = stmt.sql;
902
+ if (Array.isArray(stmt.args) || stmt.args === void 0) {
903
+ return { kind: "positional", sql, args: stmt.args ?? [] };
904
+ }
905
+ return { kind: "named", sql, args: stmt.args };
906
+ }
907
+ function rewriteBooleanLiterals(sql) {
908
+ let out = sql;
909
+ for (const column of BOOLEAN_COLUMN_NAMES) {
910
+ const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
911
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
912
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
913
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
914
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
915
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
916
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
917
+ }
918
+ return out;
919
+ }
920
+ function rewriteInsertOrIgnore(sql) {
921
+ if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
922
+ return sql;
923
+ }
924
+ const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
925
+ return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
926
+ }
927
+ function rewriteInsertOrReplace(sql) {
928
+ const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
929
+ if (!match) {
930
+ return sql;
931
+ }
932
+ const rawTable = match[1];
933
+ const rawColumns = match[2];
934
+ const remainder = match[3];
935
+ const tableName = unqualifiedTableName(rawTable);
936
+ const conflictKeys = UPSERT_KEYS[tableName];
937
+ if (!conflictKeys?.length) {
938
+ return sql;
939
+ }
940
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
941
+ const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
942
+ const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
943
+ const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
944
+ return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
945
+ }
946
+ function rewriteSql(sql) {
947
+ let out = sql;
948
+ out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
949
+ out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
950
+ out = rewriteBooleanLiterals(out);
951
+ out = rewriteInsertOrReplace(out);
952
+ out = rewriteInsertOrIgnore(out);
953
+ return stripTrailingSemicolon(out);
954
+ }
955
+ function toBoolean(value) {
956
+ if (value === null || value === void 0) return value;
957
+ if (typeof value === "boolean") return value;
958
+ if (typeof value === "number") return value !== 0;
959
+ if (typeof value === "bigint") return value !== 0n;
960
+ if (typeof value === "string") {
961
+ const normalized = value.trim().toLowerCase();
962
+ if (normalized === "0" || normalized === "false") return false;
963
+ if (normalized === "1" || normalized === "true") return true;
964
+ }
965
+ return Boolean(value);
966
+ }
967
+ function countQuestionMarks(sql, end) {
968
+ let count = 0;
969
+ let inSingle = false;
970
+ let inDouble = false;
971
+ let inLineComment = false;
972
+ let inBlockComment = false;
973
+ for (let i = 0; i < end; i++) {
974
+ const ch = sql[i];
975
+ const next = sql[i + 1];
976
+ if (inLineComment) {
977
+ if (ch === "\n") inLineComment = false;
978
+ continue;
979
+ }
980
+ if (inBlockComment) {
981
+ if (ch === "*" && next === "/") {
982
+ inBlockComment = false;
983
+ i += 1;
984
+ }
985
+ continue;
986
+ }
987
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
988
+ inLineComment = true;
989
+ i += 1;
990
+ continue;
991
+ }
992
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
993
+ inBlockComment = true;
994
+ i += 1;
995
+ continue;
996
+ }
997
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
998
+ inSingle = !inSingle;
999
+ continue;
1000
+ }
1001
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
1002
+ inDouble = !inDouble;
1003
+ continue;
1004
+ }
1005
+ if (!inSingle && !inDouble && ch === "?") {
1006
+ count += 1;
1007
+ }
1008
+ }
1009
+ return count;
1010
+ }
1011
+ function findBooleanPlaceholderIndexes(sql) {
1012
+ const indexes = /* @__PURE__ */ new Set();
1013
+ for (const column of BOOLEAN_COLUMN_NAMES) {
1014
+ const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
1015
+ for (const match of sql.matchAll(pattern)) {
1016
+ const matchText = match[0];
1017
+ const qIndex = match.index + matchText.lastIndexOf("?");
1018
+ indexes.add(countQuestionMarks(sql, qIndex + 1));
1019
+ }
1020
+ }
1021
+ return indexes;
1022
+ }
1023
+ function coerceInsertBooleanArgs(sql, args) {
1024
+ const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
1025
+ if (!match) return;
1026
+ const rawTable = match[1];
1027
+ const rawColumns = match[2];
1028
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
1029
+ if (!boolColumns?.size) return;
1030
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
1031
+ for (const [index, column] of columns.entries()) {
1032
+ if (boolColumns.has(column) && index < args.length) {
1033
+ args[index] = toBoolean(args[index]);
1034
+ }
1035
+ }
1036
+ }
1037
+ function coerceUpdateBooleanArgs(sql, args) {
1038
+ const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
1039
+ if (!match) return;
1040
+ const rawTable = match[1];
1041
+ const setClause = match[2];
1042
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
1043
+ if (!boolColumns?.size) return;
1044
+ const assignments = setClause.split(",");
1045
+ let placeholderIndex = 0;
1046
+ for (const assignment of assignments) {
1047
+ if (!assignment.includes("?")) continue;
1048
+ placeholderIndex += 1;
1049
+ const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
1050
+ if (colMatch && boolColumns.has(colMatch[1])) {
1051
+ args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
1052
+ }
1053
+ }
1054
+ }
1055
+ function coerceBooleanArgs(sql, args) {
1056
+ const nextArgs = [...args];
1057
+ coerceInsertBooleanArgs(sql, nextArgs);
1058
+ coerceUpdateBooleanArgs(sql, nextArgs);
1059
+ const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
1060
+ for (const index of placeholderIndexes) {
1061
+ if (index > 0 && index <= nextArgs.length) {
1062
+ nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
1063
+ }
1064
+ }
1065
+ return nextArgs;
1066
+ }
1067
+ function convertQuestionMarksToDollarParams(sql) {
1068
+ let out = "";
1069
+ let placeholder = 0;
1070
+ let inSingle = false;
1071
+ let inDouble = false;
1072
+ let inLineComment = false;
1073
+ let inBlockComment = false;
1074
+ for (let i = 0; i < sql.length; i++) {
1075
+ const ch = sql[i];
1076
+ const next = sql[i + 1];
1077
+ if (inLineComment) {
1078
+ out += ch;
1079
+ if (ch === "\n") inLineComment = false;
1080
+ continue;
1081
+ }
1082
+ if (inBlockComment) {
1083
+ out += ch;
1084
+ if (ch === "*" && next === "/") {
1085
+ out += next;
1086
+ inBlockComment = false;
1087
+ i += 1;
1088
+ }
1089
+ continue;
1090
+ }
1091
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
1092
+ out += ch + next;
1093
+ inLineComment = true;
1094
+ i += 1;
1095
+ continue;
1096
+ }
1097
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
1098
+ out += ch + next;
1099
+ inBlockComment = true;
1100
+ i += 1;
1101
+ continue;
1102
+ }
1103
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
1104
+ inSingle = !inSingle;
1105
+ out += ch;
1106
+ continue;
1107
+ }
1108
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
1109
+ inDouble = !inDouble;
1110
+ out += ch;
1111
+ continue;
1112
+ }
1113
+ if (!inSingle && !inDouble && ch === "?") {
1114
+ placeholder += 1;
1115
+ out += `$${placeholder}`;
1116
+ continue;
1117
+ }
1118
+ out += ch;
1119
+ }
1120
+ return out;
1121
+ }
1122
+ function translateStatementForPostgres(stmt) {
1123
+ const normalized = normalizeStatement(stmt);
1124
+ if (normalized.kind === "named") {
1125
+ throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
1126
+ }
1127
+ const rewrittenSql = rewriteSql(normalized.sql);
1128
+ const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
1129
+ return {
1130
+ sql: convertQuestionMarksToDollarParams(rewrittenSql),
1131
+ args: coercedArgs
1132
+ };
1133
+ }
1134
+ function shouldBypassPostgres(stmt) {
1135
+ const normalized = normalizeStatement(stmt);
1136
+ if (normalized.kind === "named") {
1137
+ return true;
1138
+ }
1139
+ return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
1140
+ }
1141
+ function shouldFallbackOnError(error) {
1142
+ const message = error instanceof Error ? error.message : String(error);
1143
+ return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
1144
+ }
1145
+ function isReadQuery(sql) {
1146
+ const trimmed = sql.trimStart();
1147
+ return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
1148
+ }
1149
+ function buildRow(row, columns) {
1150
+ const values = columns.map((column) => row[column]);
1151
+ return Object.assign(values, row);
1152
+ }
1153
+ function buildResultSet(rows, rowsAffected = 0) {
1154
+ const columns = rows[0] ? Object.keys(rows[0]) : [];
1155
+ const resultRows = rows.map((row) => buildRow(row, columns));
1156
+ return {
1157
+ columns,
1158
+ columnTypes: columns.map(() => ""),
1159
+ rows: resultRows,
1160
+ rowsAffected,
1161
+ lastInsertRowid: void 0,
1162
+ toJSON() {
1163
+ return {
1164
+ columns,
1165
+ columnTypes: columns.map(() => ""),
1166
+ rows,
1167
+ rowsAffected,
1168
+ lastInsertRowid: void 0
1169
+ };
1170
+ }
1171
+ };
1172
+ }
1173
+ async function loadPrismaClient() {
1174
+ if (!prismaClientPromise) {
1175
+ prismaClientPromise = (async () => {
1176
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
1177
+ if (explicitPath) {
1178
+ const module2 = await import(pathToFileURL(explicitPath).href);
1179
+ const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
1180
+ if (!PrismaClient2) {
1181
+ throw new Error(`No PrismaClient export found at ${explicitPath}`);
1182
+ }
1183
+ return new PrismaClient2();
1184
+ }
1185
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path7.join(os6.homedir(), "exe-db");
1186
+ const requireFromExeDb = createRequire(path7.join(exeDbRoot, "package.json"));
1187
+ const prismaEntry = requireFromExeDb.resolve("@prisma/client");
1188
+ const module = await import(pathToFileURL(prismaEntry).href);
1189
+ const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
1190
+ if (!PrismaClient) {
1191
+ throw new Error(`No PrismaClient export found in ${prismaEntry}`);
1192
+ }
1193
+ return new PrismaClient();
1194
+ })();
1195
+ }
1196
+ return prismaClientPromise;
1197
+ }
1198
+ async function ensureCompatibilityViews(prisma) {
1199
+ if (!compatibilityBootstrapPromise) {
1200
+ compatibilityBootstrapPromise = (async () => {
1201
+ for (const mapping of VIEW_MAPPINGS) {
1202
+ const relation = mapping.source.replace(/"/g, "");
1203
+ const rows = await prisma.$queryRawUnsafe(
1204
+ "SELECT to_regclass($1) AS regclass",
1205
+ relation
1206
+ );
1207
+ if (!rows[0]?.regclass) {
1208
+ continue;
1209
+ }
1210
+ await prisma.$executeRawUnsafe(
1211
+ `CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
1212
+ );
1213
+ }
1214
+ })();
1215
+ }
1216
+ return compatibilityBootstrapPromise;
1217
+ }
1218
+ async function executeOnPrisma(executor, stmt) {
1219
+ const translated = translateStatementForPostgres(stmt);
1220
+ if (isReadQuery(translated.sql)) {
1221
+ const rows = await executor.$queryRawUnsafe(
1222
+ translated.sql,
1223
+ ...translated.args
1224
+ );
1225
+ return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
1226
+ }
1227
+ const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
1228
+ return buildResultSet([], rowsAffected);
1229
+ }
1230
+ function splitSqlStatements(sql) {
1231
+ const parts = [];
1232
+ let current = "";
1233
+ let inSingle = false;
1234
+ let inDouble = false;
1235
+ let inLineComment = false;
1236
+ let inBlockComment = false;
1237
+ for (let i = 0; i < sql.length; i++) {
1238
+ const ch = sql[i];
1239
+ const next = sql[i + 1];
1240
+ if (inLineComment) {
1241
+ current += ch;
1242
+ if (ch === "\n") inLineComment = false;
1243
+ continue;
1244
+ }
1245
+ if (inBlockComment) {
1246
+ current += ch;
1247
+ if (ch === "*" && next === "/") {
1248
+ current += next;
1249
+ inBlockComment = false;
1250
+ i += 1;
1251
+ }
1252
+ continue;
1253
+ }
1254
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
1255
+ current += ch + next;
1256
+ inLineComment = true;
1257
+ i += 1;
1258
+ continue;
1259
+ }
1260
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
1261
+ current += ch + next;
1262
+ inBlockComment = true;
1263
+ i += 1;
1264
+ continue;
1265
+ }
1266
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
1267
+ inSingle = !inSingle;
1268
+ current += ch;
1269
+ continue;
1270
+ }
1271
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
1272
+ inDouble = !inDouble;
1273
+ current += ch;
1274
+ continue;
1275
+ }
1276
+ if (!inSingle && !inDouble && ch === ";") {
1277
+ if (current.trim()) {
1278
+ parts.push(current.trim());
1279
+ }
1280
+ current = "";
1281
+ continue;
1282
+ }
1283
+ current += ch;
1284
+ }
1285
+ if (current.trim()) {
1286
+ parts.push(current.trim());
1287
+ }
1288
+ return parts;
1289
+ }
1290
+ async function createPrismaDbAdapter(fallbackClient) {
1291
+ const prisma = await loadPrismaClient();
1292
+ await ensureCompatibilityViews(prisma);
1293
+ let closed = false;
1294
+ let adapter;
1295
+ const fallbackExecute = async (stmt, error) => {
1296
+ if (!fallbackClient) {
1297
+ if (error) throw error;
1298
+ throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
1299
+ }
1300
+ if (error) {
1301
+ process.stderr.write(
1302
+ `[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
1303
+ `
1304
+ );
1305
+ }
1306
+ return fallbackClient.execute(stmt);
1307
+ };
1308
+ adapter = {
1309
+ async execute(stmt) {
1310
+ if (shouldBypassPostgres(stmt)) {
1311
+ return fallbackExecute(stmt);
1312
+ }
1313
+ try {
1314
+ return await executeOnPrisma(prisma, stmt);
1315
+ } catch (error) {
1316
+ if (shouldFallbackOnError(error)) {
1317
+ return fallbackExecute(stmt, error);
1318
+ }
1319
+ throw error;
1320
+ }
1321
+ },
1322
+ async batch(stmts, mode) {
1323
+ if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
1324
+ if (!fallbackClient) {
1325
+ throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
1326
+ }
1327
+ return fallbackClient.batch(stmts, mode);
1328
+ }
1329
+ try {
1330
+ if (prisma.$transaction) {
1331
+ return await prisma.$transaction(async (tx) => {
1332
+ const results2 = [];
1333
+ for (const stmt of stmts) {
1334
+ results2.push(await executeOnPrisma(tx, stmt));
1335
+ }
1336
+ return results2;
1337
+ });
1338
+ }
1339
+ const results = [];
1340
+ for (const stmt of stmts) {
1341
+ results.push(await executeOnPrisma(prisma, stmt));
1342
+ }
1343
+ return results;
1344
+ } catch (error) {
1345
+ if (fallbackClient && shouldFallbackOnError(error)) {
1346
+ process.stderr.write(
1347
+ `[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
1348
+ `
1349
+ );
1350
+ return fallbackClient.batch(stmts, mode);
1351
+ }
1352
+ throw error;
1353
+ }
1354
+ },
1355
+ async migrate(stmts) {
1356
+ if (fallbackClient) {
1357
+ return fallbackClient.migrate(stmts);
1358
+ }
1359
+ return adapter.batch(stmts, "deferred");
1360
+ },
1361
+ async transaction(mode) {
1362
+ if (!fallbackClient) {
1363
+ throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
1364
+ }
1365
+ return fallbackClient.transaction(mode);
1366
+ },
1367
+ async executeMultiple(sql) {
1368
+ if (fallbackClient && shouldBypassPostgres(sql)) {
1369
+ return fallbackClient.executeMultiple(sql);
1370
+ }
1371
+ for (const statement of splitSqlStatements(sql)) {
1372
+ await adapter.execute(statement);
1373
+ }
1374
+ },
1375
+ async sync() {
1376
+ if (fallbackClient) {
1377
+ return fallbackClient.sync();
1378
+ }
1379
+ return { frame_no: 0, frames_synced: 0 };
1380
+ },
1381
+ close() {
1382
+ closed = true;
1383
+ prismaClientPromise = null;
1384
+ compatibilityBootstrapPromise = null;
1385
+ void prisma.$disconnect?.();
1386
+ },
1387
+ get closed() {
1388
+ return closed;
1389
+ },
1390
+ get protocol() {
1391
+ return "prisma-postgres";
1392
+ }
1393
+ };
1394
+ return adapter;
1395
+ }
1396
+ var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
1397
+ var init_database_adapter = __esm({
1398
+ "src/lib/database-adapter.ts"() {
1399
+ "use strict";
1400
+ VIEW_MAPPINGS = [
1401
+ { view: "memories", source: "memory.memory_records" },
1402
+ { view: "tasks", source: "memory.tasks" },
1403
+ { view: "behaviors", source: "memory.behaviors" },
1404
+ { view: "entities", source: "memory.entities" },
1405
+ { view: "relationships", source: "memory.relationships" },
1406
+ { view: "entity_memories", source: "memory.entity_memories" },
1407
+ { view: "entity_aliases", source: "memory.entity_aliases" },
1408
+ { view: "notifications", source: "memory.notifications" },
1409
+ { view: "messages", source: "memory.messages" },
1410
+ { view: "users", source: "wiki.users" },
1411
+ { view: "workspaces", source: "wiki.workspaces" },
1412
+ { view: "workspace_users", source: "wiki.workspace_users" },
1413
+ { view: "documents", source: "wiki.workspace_documents" },
1414
+ { view: "chats", source: "wiki.workspace_chats" }
1415
+ ];
1416
+ UPSERT_KEYS = {
1417
+ memories: ["id"],
1418
+ tasks: ["id"],
1419
+ behaviors: ["id"],
1420
+ entities: ["id"],
1421
+ relationships: ["id"],
1422
+ entity_aliases: ["alias"],
1423
+ notifications: ["id"],
1424
+ messages: ["id"],
1425
+ users: ["id"],
1426
+ workspaces: ["id"],
1427
+ workspace_users: ["id"],
1428
+ documents: ["id"],
1429
+ chats: ["id"]
1430
+ };
1431
+ BOOLEAN_COLUMNS_BY_TABLE = {
1432
+ memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
1433
+ behaviors: /* @__PURE__ */ new Set(["active"]),
1434
+ notifications: /* @__PURE__ */ new Set(["read"]),
1435
+ users: /* @__PURE__ */ new Set(["has_personal_memory"])
1436
+ };
1437
+ BOOLEAN_COLUMN_NAMES = new Set(
1438
+ Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
1439
+ );
1440
+ IMMEDIATE_FALLBACK_PATTERNS = [
1441
+ /\bPRAGMA\b/i,
1442
+ /\bsqlite_master\b/i,
1443
+ /(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
1444
+ /\bMATCH\b/i,
1445
+ /\bvector_distance_cos\s*\(/i,
1446
+ /\bjson_extract\s*\(/i,
1447
+ /\bjulianday\s*\(/i,
1448
+ /\bstrftime\s*\(/i,
1449
+ /\blast_insert_rowid\s*\(/i
1450
+ ];
1451
+ prismaClientPromise = null;
1452
+ compatibilityBootstrapPromise = null;
1453
+ }
1454
+ });
1455
+
1456
+ // src/lib/daemon-auth.ts
1457
+ import crypto from "crypto";
1458
+ import path8 from "path";
1459
+ import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
1460
+ function normalizeToken(token) {
1461
+ if (!token) return null;
1462
+ const trimmed = token.trim();
1463
+ return trimmed.length > 0 ? trimmed : null;
1464
+ }
1465
+ function readDaemonToken() {
1466
+ try {
1467
+ if (!existsSync7(DAEMON_TOKEN_PATH)) return null;
1468
+ return normalizeToken(readFileSync6(DAEMON_TOKEN_PATH, "utf8"));
1469
+ } catch {
1470
+ return null;
1471
+ }
1472
+ }
1473
+ function ensureDaemonToken(seed) {
1474
+ const existing = readDaemonToken();
1475
+ if (existing) return existing;
1476
+ const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
1477
+ ensurePrivateDirSync(EXE_AI_DIR);
1478
+ writeFileSync5(DAEMON_TOKEN_PATH, `${token}
1479
+ `, "utf8");
1480
+ enforcePrivateFileSync(DAEMON_TOKEN_PATH);
1481
+ return token;
1482
+ }
1483
+ var DAEMON_TOKEN_PATH;
1484
+ var init_daemon_auth = __esm({
1485
+ "src/lib/daemon-auth.ts"() {
1486
+ "use strict";
1487
+ init_config();
1488
+ init_secure_files();
1489
+ DAEMON_TOKEN_PATH = path8.join(EXE_AI_DIR, "exed.token");
1490
+ }
1491
+ });
1492
+
830
1493
  // src/lib/exe-daemon-client.ts
831
1494
  import net from "net";
832
- import os6 from "os";
1495
+ import os7 from "os";
833
1496
  import { spawn } from "child_process";
834
1497
  import { randomUUID as randomUUID2 } from "crypto";
835
- import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync6, openSync, closeSync, statSync } from "fs";
836
- import path7 from "path";
1498
+ import { existsSync as existsSync8, unlinkSync as unlinkSync2, readFileSync as readFileSync7, openSync, closeSync, statSync } from "fs";
1499
+ import path9 from "path";
837
1500
  import { fileURLToPath } from "url";
838
1501
  function handleData(chunk) {
839
1502
  _buffer += chunk.toString();
@@ -861,9 +1524,9 @@ function handleData(chunk) {
861
1524
  }
862
1525
  }
863
1526
  function cleanupStaleFiles() {
864
- if (existsSync6(PID_PATH)) {
1527
+ if (existsSync8(PID_PATH)) {
865
1528
  try {
866
- const pid = parseInt(readFileSync6(PID_PATH, "utf8").trim(), 10);
1529
+ const pid = parseInt(readFileSync7(PID_PATH, "utf8").trim(), 10);
867
1530
  if (pid > 0) {
868
1531
  try {
869
1532
  process.kill(pid, 0);
@@ -884,17 +1547,17 @@ function cleanupStaleFiles() {
884
1547
  }
885
1548
  }
886
1549
  function findPackageRoot() {
887
- let dir = path7.dirname(fileURLToPath(import.meta.url));
888
- const { root } = path7.parse(dir);
1550
+ let dir = path9.dirname(fileURLToPath(import.meta.url));
1551
+ const { root } = path9.parse(dir);
889
1552
  while (dir !== root) {
890
- if (existsSync6(path7.join(dir, "package.json"))) return dir;
891
- dir = path7.dirname(dir);
1553
+ if (existsSync8(path9.join(dir, "package.json"))) return dir;
1554
+ dir = path9.dirname(dir);
892
1555
  }
893
1556
  return null;
894
1557
  }
895
1558
  function spawnDaemon() {
896
- const freeGB = os6.freemem() / (1024 * 1024 * 1024);
897
- const totalGB = os6.totalmem() / (1024 * 1024 * 1024);
1559
+ const freeGB = os7.freemem() / (1024 * 1024 * 1024);
1560
+ const totalGB = os7.totalmem() / (1024 * 1024 * 1024);
898
1561
  if (totalGB <= 8) {
899
1562
  process.stderr.write(
900
1563
  `[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
@@ -914,16 +1577,17 @@ function spawnDaemon() {
914
1577
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
915
1578
  return;
916
1579
  }
917
- const daemonPath = path7.join(pkgRoot, "dist", "lib", "exe-daemon.js");
918
- if (!existsSync6(daemonPath)) {
1580
+ const daemonPath = path9.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1581
+ if (!existsSync8(daemonPath)) {
919
1582
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
920
1583
  `);
921
1584
  return;
922
1585
  }
923
1586
  const resolvedPath = daemonPath;
1587
+ const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
924
1588
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
925
1589
  `);
926
- const logPath = path7.join(path7.dirname(SOCKET_PATH), "exed.log");
1590
+ const logPath = path9.join(path9.dirname(SOCKET_PATH), "exed.log");
927
1591
  let stderrFd = "ignore";
928
1592
  try {
929
1593
  stderrFd = openSync(logPath, "a");
@@ -941,7 +1605,8 @@ function spawnDaemon() {
941
1605
  TMUX_PANE: void 0,
942
1606
  // Prevents resolveExeSession() from scoping to one session
943
1607
  EXE_DAEMON_SOCK: SOCKET_PATH,
944
- EXE_DAEMON_PID: PID_PATH
1608
+ EXE_DAEMON_PID: PID_PATH,
1609
+ [DAEMON_TOKEN_ENV]: daemonToken
945
1610
  }
946
1611
  });
947
1612
  child.unref();
@@ -1048,13 +1713,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
1048
1713
  return;
1049
1714
  }
1050
1715
  const id = randomUUID2();
1716
+ const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
1051
1717
  const timer = setTimeout(() => {
1052
1718
  _pending.delete(id);
1053
1719
  resolve({ error: "Request timeout" });
1054
1720
  }, timeoutMs);
1055
1721
  _pending.set(id, { resolve, timer });
1056
1722
  try {
1057
- _socket.write(JSON.stringify({ id, ...payload }) + "\n");
1723
+ _socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
1058
1724
  } catch {
1059
1725
  clearTimeout(timer);
1060
1726
  _pending.delete(id);
@@ -1065,17 +1731,19 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
1065
1731
  function isClientConnected() {
1066
1732
  return _connected;
1067
1733
  }
1068
- var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
1734
+ 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;
1069
1735
  var init_exe_daemon_client = __esm({
1070
1736
  "src/lib/exe-daemon-client.ts"() {
1071
1737
  "use strict";
1072
1738
  init_config();
1073
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path7.join(EXE_AI_DIR, "exed.sock");
1074
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path7.join(EXE_AI_DIR, "exed.pid");
1075
- SPAWN_LOCK_PATH = path7.join(EXE_AI_DIR, "exed-spawn.lock");
1739
+ init_daemon_auth();
1740
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path9.join(EXE_AI_DIR, "exed.sock");
1741
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path9.join(EXE_AI_DIR, "exed.pid");
1742
+ SPAWN_LOCK_PATH = path9.join(EXE_AI_DIR, "exed-spawn.lock");
1076
1743
  SPAWN_LOCK_STALE_MS = 3e4;
1077
1744
  CONNECT_TIMEOUT_MS = 15e3;
1078
1745
  REQUEST_TIMEOUT_MS = 3e4;
1746
+ DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
1079
1747
  _socket = null;
1080
1748
  _connected = false;
1081
1749
  _buffer = "";
@@ -1154,7 +1822,7 @@ __export(db_daemon_client_exports, {
1154
1822
  createDaemonDbClient: () => createDaemonDbClient,
1155
1823
  initDaemonDbClient: () => initDaemonDbClient
1156
1824
  });
1157
- function normalizeStatement(stmt) {
1825
+ function normalizeStatement2(stmt) {
1158
1826
  if (typeof stmt === "string") {
1159
1827
  return { sql: stmt, args: [] };
1160
1828
  }
@@ -1178,7 +1846,7 @@ function createDaemonDbClient(fallbackClient) {
1178
1846
  if (!_useDaemon || !isClientConnected()) {
1179
1847
  return fallbackClient.execute(stmt);
1180
1848
  }
1181
- const { sql, args } = normalizeStatement(stmt);
1849
+ const { sql, args } = normalizeStatement2(stmt);
1182
1850
  const response = await sendDaemonRequest({
1183
1851
  type: "db-execute",
1184
1852
  sql,
@@ -1203,7 +1871,7 @@ function createDaemonDbClient(fallbackClient) {
1203
1871
  if (!_useDaemon || !isClientConnected()) {
1204
1872
  return fallbackClient.batch(stmts, mode);
1205
1873
  }
1206
- const statements = stmts.map(normalizeStatement);
1874
+ const statements = stmts.map(normalizeStatement2);
1207
1875
  const response = await sendDaemonRequest({
1208
1876
  type: "db-batch",
1209
1877
  statements,
@@ -1298,6 +1966,18 @@ __export(database_exports, {
1298
1966
  });
1299
1967
  import { createClient } from "@libsql/client";
1300
1968
  async function initDatabase(config) {
1969
+ if (_walCheckpointTimer) {
1970
+ clearInterval(_walCheckpointTimer);
1971
+ _walCheckpointTimer = null;
1972
+ }
1973
+ if (_daemonClient) {
1974
+ _daemonClient.close();
1975
+ _daemonClient = null;
1976
+ }
1977
+ if (_adapterClient && _adapterClient !== _resilientClient) {
1978
+ _adapterClient.close();
1979
+ }
1980
+ _adapterClient = null;
1301
1981
  if (_client) {
1302
1982
  _client.close();
1303
1983
  _client = null;
@@ -1311,6 +1991,7 @@ async function initDatabase(config) {
1311
1991
  }
1312
1992
  _client = createClient(opts);
1313
1993
  _resilientClient = wrapWithRetry(_client);
1994
+ _adapterClient = _resilientClient;
1314
1995
  _client.execute("PRAGMA busy_timeout = 30000").catch(() => {
1315
1996
  });
1316
1997
  _client.execute("PRAGMA journal_mode = WAL").catch(() => {
@@ -1321,14 +2002,20 @@ async function initDatabase(config) {
1321
2002
  });
1322
2003
  }, 3e4);
1323
2004
  _walCheckpointTimer.unref();
2005
+ if (process.env.DATABASE_URL) {
2006
+ _adapterClient = await createPrismaDbAdapter(_resilientClient);
2007
+ }
1324
2008
  }
1325
2009
  function isInitialized() {
1326
- return _client !== null;
2010
+ return _adapterClient !== null || _client !== null;
1327
2011
  }
1328
2012
  function getClient() {
1329
- if (!_resilientClient) {
2013
+ if (!_adapterClient) {
1330
2014
  throw new Error("Database client not initialized. Call initDatabase() first.");
1331
2015
  }
2016
+ if (process.env.DATABASE_URL) {
2017
+ return _adapterClient;
2018
+ }
1332
2019
  if (process.env.EXE_IS_DAEMON === "1") {
1333
2020
  return _resilientClient;
1334
2021
  }
@@ -1338,6 +2025,7 @@ function getClient() {
1338
2025
  return _resilientClient;
1339
2026
  }
1340
2027
  async function initDaemonClient() {
2028
+ if (process.env.DATABASE_URL) return;
1341
2029
  if (process.env.EXE_IS_DAEMON === "1") return;
1342
2030
  if (!_resilientClient) return;
1343
2031
  try {
@@ -1634,6 +2322,7 @@ async function ensureSchema() {
1634
2322
  project TEXT NOT NULL,
1635
2323
  summary TEXT NOT NULL,
1636
2324
  task_file TEXT,
2325
+ session_scope TEXT,
1637
2326
  read INTEGER NOT NULL DEFAULT 0,
1638
2327
  created_at TEXT NOT NULL
1639
2328
  );
@@ -1642,7 +2331,7 @@ async function ensureSchema() {
1642
2331
  ON notifications(read);
1643
2332
 
1644
2333
  CREATE INDEX IF NOT EXISTS idx_notifications_agent
1645
- ON notifications(agent_id);
2334
+ ON notifications(agent_id, session_scope);
1646
2335
 
1647
2336
  CREATE INDEX IF NOT EXISTS idx_notifications_task_file
1648
2337
  ON notifications(task_file);
@@ -1680,6 +2369,7 @@ async function ensureSchema() {
1680
2369
  target_agent TEXT NOT NULL,
1681
2370
  target_project TEXT,
1682
2371
  target_device TEXT NOT NULL DEFAULT 'local',
2372
+ session_scope TEXT,
1683
2373
  content TEXT NOT NULL,
1684
2374
  priority TEXT DEFAULT 'normal',
1685
2375
  status TEXT DEFAULT 'pending',
@@ -1693,10 +2383,31 @@ async function ensureSchema() {
1693
2383
  );
1694
2384
 
1695
2385
  CREATE INDEX IF NOT EXISTS idx_messages_target
1696
- ON messages(target_agent, status);
2386
+ ON messages(target_agent, session_scope, status);
1697
2387
 
1698
2388
  CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
1699
- ON messages(target_agent, from_agent, server_seq);
2389
+ ON messages(target_agent, session_scope, from_agent, server_seq);
2390
+ `);
2391
+ try {
2392
+ await client.execute({
2393
+ sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
2394
+ args: []
2395
+ });
2396
+ } catch {
2397
+ }
2398
+ try {
2399
+ await client.execute({
2400
+ sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
2401
+ args: []
2402
+ });
2403
+ } catch {
2404
+ }
2405
+ await client.executeMultiple(`
2406
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
2407
+ ON notifications(agent_id, session_scope, read, created_at);
2408
+
2409
+ CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
2410
+ ON messages(target_agent, session_scope, status, created_at);
1700
2411
  `);
1701
2412
  try {
1702
2413
  await client.execute({
@@ -2280,46 +2991,66 @@ async function ensureSchema() {
2280
2991
  } catch {
2281
2992
  }
2282
2993
  }
2994
+ try {
2995
+ await client.execute({
2996
+ sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
2997
+ args: []
2998
+ });
2999
+ } catch {
3000
+ }
2283
3001
  }
2284
3002
  async function disposeDatabase() {
3003
+ if (_walCheckpointTimer) {
3004
+ clearInterval(_walCheckpointTimer);
3005
+ _walCheckpointTimer = null;
3006
+ }
2285
3007
  if (_daemonClient) {
2286
3008
  _daemonClient.close();
2287
3009
  _daemonClient = null;
2288
3010
  }
3011
+ if (_adapterClient && _adapterClient !== _resilientClient) {
3012
+ _adapterClient.close();
3013
+ }
3014
+ _adapterClient = null;
2289
3015
  if (_client) {
2290
3016
  _client.close();
2291
3017
  _client = null;
2292
3018
  _resilientClient = null;
2293
3019
  }
2294
3020
  }
2295
- var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso, disposeTurso;
3021
+ var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
2296
3022
  var init_database = __esm({
2297
3023
  "src/lib/database.ts"() {
2298
3024
  "use strict";
2299
3025
  init_db_retry();
2300
3026
  init_employees();
3027
+ init_database_adapter();
2301
3028
  _client = null;
2302
3029
  _resilientClient = null;
2303
3030
  _walCheckpointTimer = null;
2304
3031
  _daemonClient = null;
3032
+ _adapterClient = null;
2305
3033
  initTurso = initDatabase;
2306
3034
  disposeTurso = disposeDatabase;
2307
3035
  }
2308
3036
  });
2309
3037
 
2310
3038
  // src/lib/license.ts
2311
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
3039
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync9, mkdirSync as mkdirSync4 } from "fs";
2312
3040
  import { randomUUID as randomUUID3 } from "crypto";
2313
- import path8 from "path";
3041
+ import { createRequire as createRequire2 } from "module";
3042
+ import { pathToFileURL as pathToFileURL2 } from "url";
3043
+ import os8 from "os";
3044
+ import path10 from "path";
2314
3045
  import { jwtVerify, importSPKI } from "jose";
2315
3046
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
2316
3047
  var init_license = __esm({
2317
3048
  "src/lib/license.ts"() {
2318
3049
  "use strict";
2319
3050
  init_config();
2320
- LICENSE_PATH = path8.join(EXE_AI_DIR, "license.key");
2321
- CACHE_PATH = path8.join(EXE_AI_DIR, "license-cache.json");
2322
- DEVICE_ID_PATH = path8.join(EXE_AI_DIR, "device-id");
3051
+ LICENSE_PATH = path10.join(EXE_AI_DIR, "license.key");
3052
+ CACHE_PATH = path10.join(EXE_AI_DIR, "license-cache.json");
3053
+ DEVICE_ID_PATH = path10.join(EXE_AI_DIR, "device-id");
2323
3054
  PLAN_LIMITS = {
2324
3055
  free: { devices: 1, employees: 1, memories: 5e3 },
2325
3056
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -2331,12 +3062,12 @@ var init_license = __esm({
2331
3062
  });
2332
3063
 
2333
3064
  // src/lib/plan-limits.ts
2334
- import { readFileSync as readFileSync8, existsSync as existsSync8 } from "fs";
2335
- import path9 from "path";
3065
+ import { readFileSync as readFileSync9, existsSync as existsSync10 } from "fs";
3066
+ import path11 from "path";
2336
3067
  function getLicenseSync() {
2337
3068
  try {
2338
- if (!existsSync8(CACHE_PATH2)) return freeLicense();
2339
- const raw = JSON.parse(readFileSync8(CACHE_PATH2, "utf8"));
3069
+ if (!existsSync10(CACHE_PATH2)) return freeLicense();
3070
+ const raw = JSON.parse(readFileSync9(CACHE_PATH2, "utf8"));
2340
3071
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
2341
3072
  const parts = raw.token.split(".");
2342
3073
  if (parts.length !== 3) return freeLicense();
@@ -2374,8 +3105,8 @@ function assertEmployeeLimitSync(rosterPath) {
2374
3105
  const filePath = rosterPath ?? EMPLOYEES_PATH;
2375
3106
  let count = 0;
2376
3107
  try {
2377
- if (existsSync8(filePath)) {
2378
- const raw = readFileSync8(filePath, "utf8");
3108
+ if (existsSync10(filePath)) {
3109
+ const raw = readFileSync9(filePath, "utf8");
2379
3110
  const employees = JSON.parse(raw);
2380
3111
  count = Array.isArray(employees) ? employees.length : 0;
2381
3112
  }
@@ -2404,29 +3135,69 @@ var init_plan_limits = __esm({
2404
3135
  this.name = "PlanLimitError";
2405
3136
  }
2406
3137
  };
2407
- CACHE_PATH2 = path9.join(EXE_AI_DIR, "license-cache.json");
3138
+ CACHE_PATH2 = path11.join(EXE_AI_DIR, "license-cache.json");
3139
+ }
3140
+ });
3141
+
3142
+ // src/lib/task-scope.ts
3143
+ var task_scope_exports = {};
3144
+ __export(task_scope_exports, {
3145
+ getCurrentSessionScope: () => getCurrentSessionScope,
3146
+ sessionScopeFilter: () => sessionScopeFilter,
3147
+ strictSessionScopeFilter: () => strictSessionScopeFilter
3148
+ });
3149
+ function getCurrentSessionScope() {
3150
+ try {
3151
+ return resolveExeSession();
3152
+ } catch {
3153
+ return null;
3154
+ }
3155
+ }
3156
+ function sessionScopeFilter(sessionScope, tableAlias) {
3157
+ const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
3158
+ if (!scope) return { sql: "", args: [] };
3159
+ const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
3160
+ return {
3161
+ sql: ` AND (${col} IS NULL OR ${col} = ?)`,
3162
+ args: [scope]
3163
+ };
3164
+ }
3165
+ function strictSessionScopeFilter(sessionScope, tableAlias) {
3166
+ const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
3167
+ if (!scope) return { sql: "", args: [] };
3168
+ const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
3169
+ return {
3170
+ sql: ` AND ${col} = ?`,
3171
+ args: [scope]
3172
+ };
3173
+ }
3174
+ var init_task_scope = __esm({
3175
+ "src/lib/task-scope.ts"() {
3176
+ "use strict";
3177
+ init_tmux_routing();
2408
3178
  }
2409
3179
  });
2410
3180
 
2411
3181
  // src/lib/notifications.ts
2412
- import crypto from "crypto";
2413
- import path10 from "path";
2414
- import os7 from "os";
3182
+ import crypto2 from "crypto";
3183
+ import path12 from "path";
3184
+ import os9 from "os";
2415
3185
  import {
2416
- readFileSync as readFileSync9,
3186
+ readFileSync as readFileSync10,
2417
3187
  readdirSync,
2418
3188
  unlinkSync as unlinkSync3,
2419
- existsSync as existsSync9,
3189
+ existsSync as existsSync11,
2420
3190
  rmdirSync
2421
3191
  } from "fs";
2422
3192
  async function writeNotification(notification) {
2423
3193
  try {
2424
3194
  const client = getClient();
2425
- const id = crypto.randomUUID();
3195
+ const id = crypto2.randomUUID();
2426
3196
  const now = (/* @__PURE__ */ new Date()).toISOString();
3197
+ const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
2427
3198
  await client.execute({
2428
- sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
2429
- VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
3199
+ sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
3200
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
2430
3201
  args: [
2431
3202
  id,
2432
3203
  notification.agentId,
@@ -2435,6 +3206,7 @@ async function writeNotification(notification) {
2435
3206
  notification.project,
2436
3207
  notification.summary,
2437
3208
  notification.taskFile ?? null,
3209
+ sessionScope,
2438
3210
  now
2439
3211
  ]
2440
3212
  });
@@ -2443,12 +3215,14 @@ async function writeNotification(notification) {
2443
3215
  `);
2444
3216
  }
2445
3217
  }
2446
- async function markAsReadByTaskFile(taskFile) {
3218
+ async function markAsReadByTaskFile(taskFile, sessionScope) {
2447
3219
  try {
2448
3220
  const client = getClient();
3221
+ const scope = strictSessionScopeFilter(sessionScope);
2449
3222
  await client.execute({
2450
- sql: "UPDATE notifications SET read = 1 WHERE task_file = ? AND read = 0",
2451
- args: [taskFile]
3223
+ sql: `UPDATE notifications SET read = 1
3224
+ WHERE task_file = ? AND read = 0${scope.sql}`,
3225
+ args: [taskFile, ...scope.args]
2452
3226
  });
2453
3227
  } catch {
2454
3228
  }
@@ -2457,11 +3231,12 @@ var init_notifications = __esm({
2457
3231
  "src/lib/notifications.ts"() {
2458
3232
  "use strict";
2459
3233
  init_database();
3234
+ init_task_scope();
2460
3235
  }
2461
3236
  });
2462
3237
 
2463
3238
  // src/lib/session-kill-telemetry.ts
2464
- import crypto2 from "crypto";
3239
+ import crypto3 from "crypto";
2465
3240
  async function recordSessionKill(input) {
2466
3241
  try {
2467
3242
  const client = getClient();
@@ -2471,7 +3246,7 @@ async function recordSessionKill(input) {
2471
3246
  ticks_idle, estimated_tokens_saved)
2472
3247
  VALUES (?, ?, ?, ?, ?, ?, ?)`,
2473
3248
  args: [
2474
- crypto2.randomUUID(),
3249
+ crypto3.randomUUID(),
2475
3250
  input.sessionName,
2476
3251
  input.agentId,
2477
3252
  (/* @__PURE__ */ new Date()).toISOString(),
@@ -2494,35 +3269,6 @@ var init_session_kill_telemetry = __esm({
2494
3269
  }
2495
3270
  });
2496
3271
 
2497
- // src/lib/task-scope.ts
2498
- var task_scope_exports = {};
2499
- __export(task_scope_exports, {
2500
- getCurrentSessionScope: () => getCurrentSessionScope,
2501
- sessionScopeFilter: () => sessionScopeFilter
2502
- });
2503
- function getCurrentSessionScope() {
2504
- try {
2505
- return resolveExeSession();
2506
- } catch {
2507
- return null;
2508
- }
2509
- }
2510
- function sessionScopeFilter(sessionScope, tableAlias) {
2511
- const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
2512
- if (!scope) return { sql: "", args: [] };
2513
- const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
2514
- return {
2515
- sql: ` AND (${col} IS NULL OR ${col} = ?)`,
2516
- args: [scope]
2517
- };
2518
- }
2519
- var init_task_scope = __esm({
2520
- "src/lib/task-scope.ts"() {
2521
- "use strict";
2522
- init_tmux_routing();
2523
- }
2524
- });
2525
-
2526
3272
  // src/lib/state-bus.ts
2527
3273
  var StateBus, orgBus;
2528
3274
  var init_state_bus = __esm({
@@ -2579,12 +3325,12 @@ var init_state_bus = __esm({
2579
3325
  });
2580
3326
 
2581
3327
  // src/lib/tasks-crud.ts
2582
- import crypto3 from "crypto";
2583
- import path11 from "path";
2584
- import os8 from "os";
3328
+ import crypto4 from "crypto";
3329
+ import path13 from "path";
3330
+ import os10 from "os";
2585
3331
  import { execSync as execSync5 } from "child_process";
2586
3332
  import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
2587
- import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
3333
+ import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
2588
3334
  async function writeCheckpoint(input) {
2589
3335
  const client = getClient();
2590
3336
  const row = await resolveTask(client, input.taskId);
@@ -2700,7 +3446,7 @@ async function resolveTask(client, identifier, scopeSession) {
2700
3446
  }
2701
3447
  async function createTaskCore(input) {
2702
3448
  const client = getClient();
2703
- const id = crypto3.randomUUID();
3449
+ const id = crypto4.randomUUID();
2704
3450
  const now = (/* @__PURE__ */ new Date()).toISOString();
2705
3451
  const slug = slugify(input.title);
2706
3452
  let earlySessionScope = null;
@@ -2759,8 +3505,8 @@ ${laneWarning}` : laneWarning;
2759
3505
  }
2760
3506
  if (input.baseDir) {
2761
3507
  try {
2762
- await mkdir3(path11.join(input.baseDir, "exe", "output"), { recursive: true });
2763
- await mkdir3(path11.join(input.baseDir, "exe", "research"), { recursive: true });
3508
+ await mkdir3(path13.join(input.baseDir, "exe", "output"), { recursive: true });
3509
+ await mkdir3(path13.join(input.baseDir, "exe", "research"), { recursive: true });
2764
3510
  await ensureArchitectureDoc(input.baseDir, input.projectName);
2765
3511
  await ensureGitignoreExe(input.baseDir);
2766
3512
  } catch {
@@ -2796,13 +3542,19 @@ ${laneWarning}` : laneWarning;
2796
3542
  });
2797
3543
  if (input.baseDir) {
2798
3544
  try {
2799
- const EXE_OS_DIR = path11.join(os8.homedir(), ".exe-os");
2800
- const mdPath = path11.join(EXE_OS_DIR, taskFile);
2801
- const mdDir = path11.dirname(mdPath);
2802
- if (!existsSync10(mdDir)) await mkdir3(mdDir, { recursive: true });
3545
+ const EXE_OS_DIR = path13.join(os10.homedir(), ".exe-os");
3546
+ const mdPath = path13.join(EXE_OS_DIR, taskFile);
3547
+ const mdDir = path13.dirname(mdPath);
3548
+ if (!existsSync12(mdDir)) await mkdir3(mdDir, { recursive: true });
2803
3549
  const reviewer = input.reviewer ?? input.assignedBy;
2804
3550
  const mdContent = `# ${input.title}
2805
3551
 
3552
+ ## MANDATORY: When done
3553
+
3554
+ You MUST call update_task with status "done" and a result summary when finished.
3555
+ If you skip this, your reviewer will not know you're done and your work won't be reviewed.
3556
+ Do NOT let a failed commit or any error prevent you from calling update_task(done).
3557
+
2806
3558
  **ID:** ${id}
2807
3559
  **Status:** ${initialStatus}
2808
3560
  **Priority:** ${input.priority}
@@ -2816,12 +3568,6 @@ ${laneWarning}` : laneWarning;
2816
3568
  ## Context
2817
3569
 
2818
3570
  ${input.context}
2819
-
2820
- ## MANDATORY: When done
2821
-
2822
- You MUST call update_task with status "done" and a result summary when finished.
2823
- If you skip this, your reviewer will not know you're done and your work won't be reviewed.
2824
- Do NOT let a failed commit or any error prevent you from calling update_task(done).
2825
3571
  `;
2826
3572
  await writeFile3(mdPath, mdContent, "utf-8");
2827
3573
  } catch (err) {
@@ -3070,7 +3816,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
3070
3816
  await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
3071
3817
  } catch {
3072
3818
  }
3073
- if (input.status === "done" || input.status === "cancelled") {
3819
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
3074
3820
  try {
3075
3821
  const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
3076
3822
  clearQueueForAgent2(String(row.assigned_to));
@@ -3099,9 +3845,9 @@ async function deleteTaskCore(taskId, _baseDir) {
3099
3845
  return { taskFile, assignedTo, assignedBy, taskSlug };
3100
3846
  }
3101
3847
  async function ensureArchitectureDoc(baseDir, projectName) {
3102
- const archPath = path11.join(baseDir, "exe", "ARCHITECTURE.md");
3848
+ const archPath = path13.join(baseDir, "exe", "ARCHITECTURE.md");
3103
3849
  try {
3104
- if (existsSync10(archPath)) return;
3850
+ if (existsSync12(archPath)) return;
3105
3851
  const template = [
3106
3852
  `# ${projectName} \u2014 System Architecture`,
3107
3853
  "",
@@ -3134,10 +3880,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
3134
3880
  }
3135
3881
  }
3136
3882
  async function ensureGitignoreExe(baseDir) {
3137
- const gitignorePath = path11.join(baseDir, ".gitignore");
3883
+ const gitignorePath = path13.join(baseDir, ".gitignore");
3138
3884
  try {
3139
- if (existsSync10(gitignorePath)) {
3140
- const content = readFileSync10(gitignorePath, "utf-8");
3885
+ if (existsSync12(gitignorePath)) {
3886
+ const content = readFileSync11(gitignorePath, "utf-8");
3141
3887
  if (/^\/?exe\/?$/m.test(content)) return;
3142
3888
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
3143
3889
  } else {
@@ -3168,58 +3914,42 @@ var init_tasks_crud = __esm({
3168
3914
  });
3169
3915
 
3170
3916
  // src/lib/tasks-review.ts
3171
- import path12 from "path";
3172
- import { existsSync as existsSync11, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
3917
+ import path14 from "path";
3918
+ import { existsSync as existsSync13, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
3173
3919
  async function countPendingReviews(sessionScope) {
3174
3920
  const client = getClient();
3175
- if (sessionScope) {
3176
- const result2 = await client.execute({
3177
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review' AND (session_scope = ? OR session_scope IS NULL)",
3178
- args: [sessionScope]
3179
- });
3180
- return Number(result2.rows[0]?.cnt) || 0;
3181
- }
3921
+ const scope = strictSessionScopeFilter(
3922
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
3923
+ );
3182
3924
  const result = await client.execute({
3183
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review'",
3184
- args: []
3925
+ sql: `SELECT COUNT(*) as cnt FROM tasks
3926
+ WHERE status = 'needs_review'${scope.sql}`,
3927
+ args: [...scope.args]
3185
3928
  });
3186
3929
  return Number(result.rows[0]?.cnt) || 0;
3187
3930
  }
3188
3931
  async function countNewPendingReviewsSince(sinceIso, sessionScope) {
3189
3932
  const client = getClient();
3190
- if (sessionScope) {
3191
- const result2 = await client.execute({
3192
- sql: `SELECT COUNT(*) as cnt FROM tasks
3193
- WHERE status = 'needs_review' AND updated_at > ?
3194
- AND session_scope = ?`,
3195
- args: [sinceIso, sessionScope]
3196
- });
3197
- return Number(result2.rows[0]?.cnt) || 0;
3198
- }
3933
+ const scope = strictSessionScopeFilter(
3934
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
3935
+ );
3199
3936
  const result = await client.execute({
3200
3937
  sql: `SELECT COUNT(*) as cnt FROM tasks
3201
- WHERE status = 'needs_review' AND updated_at > ?`,
3202
- args: [sinceIso]
3938
+ WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
3939
+ args: [sinceIso, ...scope.args]
3203
3940
  });
3204
3941
  return Number(result.rows[0]?.cnt) || 0;
3205
3942
  }
3206
3943
  async function listPendingReviews(limit, sessionScope) {
3207
3944
  const client = getClient();
3208
- if (sessionScope) {
3209
- const result2 = await client.execute({
3210
- sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
3211
- WHERE status = 'needs_review'
3212
- AND session_scope = ?
3213
- ORDER BY updated_at ASC LIMIT ?`,
3214
- args: [sessionScope, limit]
3215
- });
3216
- return result2.rows;
3217
- }
3945
+ const scope = strictSessionScopeFilter(
3946
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
3947
+ );
3218
3948
  const result = await client.execute({
3219
3949
  sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
3220
- WHERE status = 'needs_review'
3950
+ WHERE status = 'needs_review'${scope.sql}
3221
3951
  ORDER BY updated_at ASC LIMIT ?`,
3222
- args: [limit]
3952
+ args: [...scope.args, limit]
3223
3953
  });
3224
3954
  return result.rows;
3225
3955
  }
@@ -3231,7 +3961,7 @@ async function cleanupOrphanedReviews() {
3231
3961
  WHERE status IN ('open', 'needs_review', 'in_progress')
3232
3962
  AND assigned_by = 'system'
3233
3963
  AND title LIKE 'Review:%'
3234
- AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
3964
+ AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
3235
3965
  args: [now]
3236
3966
  });
3237
3967
  const r1b = await client.execute({
@@ -3350,11 +4080,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
3350
4080
  );
3351
4081
  }
3352
4082
  try {
3353
- const cacheDir = path12.join(EXE_AI_DIR, "session-cache");
3354
- if (existsSync11(cacheDir)) {
4083
+ const cacheDir = path14.join(EXE_AI_DIR, "session-cache");
4084
+ if (existsSync13(cacheDir)) {
3355
4085
  for (const f of readdirSync2(cacheDir)) {
3356
4086
  if (f.startsWith("review-notified-")) {
3357
- unlinkSync4(path12.join(cacheDir, f));
4087
+ unlinkSync4(path14.join(cacheDir, f));
3358
4088
  }
3359
4089
  }
3360
4090
  }
@@ -3371,11 +4101,12 @@ var init_tasks_review = __esm({
3371
4101
  init_tmux_routing();
3372
4102
  init_session_key();
3373
4103
  init_state_bus();
4104
+ init_task_scope();
3374
4105
  }
3375
4106
  });
3376
4107
 
3377
4108
  // src/lib/tasks-chain.ts
3378
- import path13 from "path";
4109
+ import path15 from "path";
3379
4110
  import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
3380
4111
  async function cascadeUnblock(taskId, baseDir, now) {
3381
4112
  const client = getClient();
@@ -3392,7 +4123,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
3392
4123
  });
3393
4124
  for (const ur of unblockedRows.rows) {
3394
4125
  try {
3395
- const ubFile = path13.join(baseDir, String(ur.task_file));
4126
+ const ubFile = path15.join(baseDir, String(ur.task_file));
3396
4127
  let ubContent = await readFile3(ubFile, "utf-8");
3397
4128
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
3398
4129
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -3427,7 +4158,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
3427
4158
  const scScope = sessionScopeFilter();
3428
4159
  const remaining = await client.execute({
3429
4160
  sql: `SELECT COUNT(*) as cnt FROM tasks
3430
- WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
4161
+ WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
3431
4162
  args: [parentTaskId, ...scScope.args]
3432
4163
  });
3433
4164
  const cnt = Number(remaining.rows[0]?.cnt ?? 1);
@@ -3461,7 +4192,7 @@ var init_tasks_chain = __esm({
3461
4192
 
3462
4193
  // src/lib/project-name.ts
3463
4194
  import { execSync as execSync6 } from "child_process";
3464
- import path14 from "path";
4195
+ import path16 from "path";
3465
4196
  function getProjectName(cwd) {
3466
4197
  const dir = cwd ?? process.cwd();
3467
4198
  if (_cached2 && _cachedCwd === dir) return _cached2;
@@ -3474,7 +4205,7 @@ function getProjectName(cwd) {
3474
4205
  timeout: 2e3,
3475
4206
  stdio: ["pipe", "pipe", "pipe"]
3476
4207
  }).trim();
3477
- repoRoot = path14.dirname(gitCommonDir);
4208
+ repoRoot = path16.dirname(gitCommonDir);
3478
4209
  } catch {
3479
4210
  repoRoot = execSync6("git rev-parse --show-toplevel", {
3480
4211
  cwd: dir,
@@ -3483,11 +4214,11 @@ function getProjectName(cwd) {
3483
4214
  stdio: ["pipe", "pipe", "pipe"]
3484
4215
  }).trim();
3485
4216
  }
3486
- _cached2 = path14.basename(repoRoot);
4217
+ _cached2 = path16.basename(repoRoot);
3487
4218
  _cachedCwd = dir;
3488
4219
  return _cached2;
3489
4220
  } catch {
3490
- _cached2 = path14.basename(dir);
4221
+ _cached2 = path16.basename(dir);
3491
4222
  _cachedCwd = dir;
3492
4223
  return _cached2;
3493
4224
  }
@@ -3637,10 +4368,10 @@ __export(behaviors_exports, {
3637
4368
  listBehaviorsByDomain: () => listBehaviorsByDomain,
3638
4369
  storeBehavior: () => storeBehavior
3639
4370
  });
3640
- import crypto4 from "crypto";
4371
+ import crypto5 from "crypto";
3641
4372
  async function storeBehavior(opts) {
3642
4373
  const client = getClient();
3643
- const id = crypto4.randomUUID();
4374
+ const id = crypto5.randomUUID();
3644
4375
  const now = (/* @__PURE__ */ new Date()).toISOString();
3645
4376
  await client.execute({
3646
4377
  sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
@@ -3724,7 +4455,7 @@ __export(skill_learning_exports, {
3724
4455
  storeTrajectory: () => storeTrajectory,
3725
4456
  sweepTrajectories: () => sweepTrajectories
3726
4457
  });
3727
- import crypto5 from "crypto";
4458
+ import crypto6 from "crypto";
3728
4459
  async function extractTrajectory(taskId, agentId) {
3729
4460
  const client = getClient();
3730
4461
  const result = await client.execute({
@@ -3753,11 +4484,11 @@ async function extractTrajectory(taskId, agentId) {
3753
4484
  return signature;
3754
4485
  }
3755
4486
  function hashSignature(signature) {
3756
- return crypto5.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
4487
+ return crypto6.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
3757
4488
  }
3758
4489
  async function storeTrajectory(opts) {
3759
4490
  const client = getClient();
3760
- const id = crypto5.randomUUID();
4491
+ const id = crypto6.randomUUID();
3761
4492
  const now = (/* @__PURE__ */ new Date()).toISOString();
3762
4493
  const signatureHash = hashSignature(opts.signature);
3763
4494
  await client.execute({
@@ -4022,8 +4753,8 @@ __export(tasks_exports, {
4022
4753
  updateTaskStatus: () => updateTaskStatus,
4023
4754
  writeCheckpoint: () => writeCheckpoint
4024
4755
  });
4025
- import path15 from "path";
4026
- import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
4756
+ import path17 from "path";
4757
+ import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
4027
4758
  async function createTask(input) {
4028
4759
  const result = await createTaskCore(input);
4029
4760
  if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -4042,12 +4773,12 @@ async function updateTask(input) {
4042
4773
  const { row, taskFile, now, taskId } = await updateTaskStatus(input);
4043
4774
  try {
4044
4775
  const agent = String(row.assigned_to);
4045
- const cacheDir = path15.join(EXE_AI_DIR, "session-cache");
4046
- const cachePath = path15.join(cacheDir, `current-task-${agent}.json`);
4776
+ const cacheDir = path17.join(EXE_AI_DIR, "session-cache");
4777
+ const cachePath = path17.join(cacheDir, `current-task-${agent}.json`);
4047
4778
  if (input.status === "in_progress") {
4048
4779
  mkdirSync5(cacheDir, { recursive: true });
4049
- writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
4050
- } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
4780
+ writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
4781
+ } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
4051
4782
  try {
4052
4783
  unlinkSync5(cachePath);
4053
4784
  } catch {
@@ -4055,10 +4786,10 @@ async function updateTask(input) {
4055
4786
  }
4056
4787
  } catch {
4057
4788
  }
4058
- if (input.status === "done") {
4789
+ if (input.status === "done" || input.status === "closed") {
4059
4790
  await cleanupReviewFile(row, taskFile, input.baseDir);
4060
4791
  }
4061
- if (input.status === "done" || input.status === "cancelled") {
4792
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
4062
4793
  try {
4063
4794
  const client = getClient();
4064
4795
  const taskTitle = String(row.title);
@@ -4074,7 +4805,7 @@ async function updateTask(input) {
4074
4805
  if (!isCoordinatorName(assignedAgent)) {
4075
4806
  try {
4076
4807
  const draftClient = getClient();
4077
- if (input.status === "done") {
4808
+ if (input.status === "done" || input.status === "closed") {
4078
4809
  await draftClient.execute({
4079
4810
  sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
4080
4811
  args: [assignedAgent]
@@ -4091,7 +4822,7 @@ async function updateTask(input) {
4091
4822
  try {
4092
4823
  const client = getClient();
4093
4824
  const cascaded = await client.execute({
4094
- sql: `UPDATE tasks SET status = 'done', updated_at = ?
4825
+ sql: `UPDATE tasks SET status = 'closed', updated_at = ?
4095
4826
  WHERE parent_task_id = ? AND status = 'needs_review'`,
4096
4827
  args: [now, taskId]
4097
4828
  });
@@ -4104,14 +4835,14 @@ async function updateTask(input) {
4104
4835
  } catch {
4105
4836
  }
4106
4837
  }
4107
- const isTerminal = input.status === "done" || input.status === "needs_review";
4838
+ const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
4108
4839
  if (isTerminal) {
4109
4840
  const isCoordinator = isCoordinatorName(String(row.assigned_to));
4110
4841
  if (!isCoordinator) {
4111
4842
  notifyTaskDone();
4112
4843
  }
4113
4844
  await markTaskNotificationsRead(taskFile);
4114
- if (input.status === "done") {
4845
+ if (input.status === "done" || input.status === "closed") {
4115
4846
  try {
4116
4847
  await cascadeUnblock(taskId, input.baseDir, now);
4117
4848
  } catch {
@@ -4131,7 +4862,7 @@ async function updateTask(input) {
4131
4862
  }
4132
4863
  }
4133
4864
  }
4134
- if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
4865
+ if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
4135
4866
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
4136
4867
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
4137
4868
  taskId,
@@ -4503,6 +5234,7 @@ __export(tmux_routing_exports, {
4503
5234
  isEmployeeAlive: () => isEmployeeAlive,
4504
5235
  isExeSession: () => isExeSession,
4505
5236
  isSessionBusy: () => isSessionBusy,
5237
+ notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
4506
5238
  notifyParentExe: () => notifyParentExe,
4507
5239
  parseParentExe: () => parseParentExe,
4508
5240
  registerParentExe: () => registerParentExe,
@@ -4513,13 +5245,13 @@ __export(tmux_routing_exports, {
4513
5245
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
4514
5246
  });
4515
5247
  import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
4516
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync12, appendFileSync, readdirSync as readdirSync3 } from "fs";
4517
- import path16 from "path";
4518
- import os9 from "os";
5248
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, mkdirSync as mkdirSync6, existsSync as existsSync14, appendFileSync, readdirSync as readdirSync3 } from "fs";
5249
+ import path18 from "path";
5250
+ import os11 from "os";
4519
5251
  import { fileURLToPath as fileURLToPath2 } from "url";
4520
5252
  import { unlinkSync as unlinkSync6 } from "fs";
4521
5253
  function spawnLockPath(sessionName) {
4522
- return path16.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
5254
+ return path18.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
4523
5255
  }
4524
5256
  function isProcessAlive(pid) {
4525
5257
  try {
@@ -4530,13 +5262,13 @@ function isProcessAlive(pid) {
4530
5262
  }
4531
5263
  }
4532
5264
  function acquireSpawnLock2(sessionName) {
4533
- if (!existsSync12(SPAWN_LOCK_DIR)) {
5265
+ if (!existsSync14(SPAWN_LOCK_DIR)) {
4534
5266
  mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
4535
5267
  }
4536
5268
  const lockFile = spawnLockPath(sessionName);
4537
- if (existsSync12(lockFile)) {
5269
+ if (existsSync14(lockFile)) {
4538
5270
  try {
4539
- const lock = JSON.parse(readFileSync11(lockFile, "utf8"));
5271
+ const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
4540
5272
  const age = Date.now() - lock.timestamp;
4541
5273
  if (isProcessAlive(lock.pid) && age < 6e4) {
4542
5274
  return false;
@@ -4544,7 +5276,7 @@ function acquireSpawnLock2(sessionName) {
4544
5276
  } catch {
4545
5277
  }
4546
5278
  }
4547
- writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
5279
+ writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
4548
5280
  return true;
4549
5281
  }
4550
5282
  function releaseSpawnLock2(sessionName) {
@@ -4556,13 +5288,13 @@ function releaseSpawnLock2(sessionName) {
4556
5288
  function resolveBehaviorsExporterScript() {
4557
5289
  try {
4558
5290
  const thisFile = fileURLToPath2(import.meta.url);
4559
- const scriptPath = path16.join(
4560
- path16.dirname(thisFile),
5291
+ const scriptPath = path18.join(
5292
+ path18.dirname(thisFile),
4561
5293
  "..",
4562
5294
  "bin",
4563
5295
  "exe-export-behaviors.js"
4564
5296
  );
4565
- return existsSync12(scriptPath) ? scriptPath : null;
5297
+ return existsSync14(scriptPath) ? scriptPath : null;
4566
5298
  } catch {
4567
5299
  return null;
4568
5300
  }
@@ -4628,12 +5360,12 @@ function extractRootExe(name) {
4628
5360
  return parts.length > 0 ? parts[parts.length - 1] : null;
4629
5361
  }
4630
5362
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
4631
- if (!existsSync12(SESSION_CACHE)) {
5363
+ if (!existsSync14(SESSION_CACHE)) {
4632
5364
  mkdirSync6(SESSION_CACHE, { recursive: true });
4633
5365
  }
4634
5366
  const rootExe = extractRootExe(parentExe) ?? parentExe;
4635
- const filePath = path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
4636
- writeFileSync7(filePath, JSON.stringify({
5367
+ const filePath = path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
5368
+ writeFileSync8(filePath, JSON.stringify({
4637
5369
  parentExe: rootExe,
4638
5370
  dispatchedBy: dispatchedBy || rootExe,
4639
5371
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -4641,7 +5373,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
4641
5373
  }
4642
5374
  function getParentExe(sessionKey) {
4643
5375
  try {
4644
- const data = JSON.parse(readFileSync11(path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
5376
+ const data = JSON.parse(readFileSync12(path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
4645
5377
  return data.parentExe || null;
4646
5378
  } catch {
4647
5379
  return null;
@@ -4649,8 +5381,8 @@ function getParentExe(sessionKey) {
4649
5381
  }
4650
5382
  function getDispatchedBy(sessionKey) {
4651
5383
  try {
4652
- const data = JSON.parse(readFileSync11(
4653
- path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
5384
+ const data = JSON.parse(readFileSync12(
5385
+ path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
4654
5386
  "utf8"
4655
5387
  ));
4656
5388
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -4720,8 +5452,8 @@ async function verifyPaneAtCapacity(sessionName) {
4720
5452
  }
4721
5453
  function readDebounceState() {
4722
5454
  try {
4723
- if (!existsSync12(DEBOUNCE_FILE)) return {};
4724
- const raw = JSON.parse(readFileSync11(DEBOUNCE_FILE, "utf8"));
5455
+ if (!existsSync14(DEBOUNCE_FILE)) return {};
5456
+ const raw = JSON.parse(readFileSync12(DEBOUNCE_FILE, "utf8"));
4725
5457
  const state = {};
4726
5458
  for (const [key, val] of Object.entries(raw)) {
4727
5459
  if (typeof val === "number") {
@@ -4737,8 +5469,8 @@ function readDebounceState() {
4737
5469
  }
4738
5470
  function writeDebounceState(state) {
4739
5471
  try {
4740
- if (!existsSync12(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
4741
- writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
5472
+ if (!existsSync14(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
5473
+ writeFileSync8(DEBOUNCE_FILE, JSON.stringify(state));
4742
5474
  } catch {
4743
5475
  }
4744
5476
  }
@@ -4836,8 +5568,8 @@ function sendIntercom(targetSession) {
4836
5568
  try {
4837
5569
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
4838
5570
  const agent = baseAgentName(rawAgent);
4839
- const markerPath = path16.join(SESSION_CACHE, `current-task-${agent}.json`);
4840
- if (existsSync12(markerPath)) {
5571
+ const markerPath = path18.join(SESSION_CACHE, `current-task-${agent}.json`);
5572
+ if (existsSync14(markerPath)) {
4841
5573
  logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
4842
5574
  return "debounced";
4843
5575
  }
@@ -4846,8 +5578,8 @@ function sendIntercom(targetSession) {
4846
5578
  try {
4847
5579
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
4848
5580
  const agent = baseAgentName(rawAgent);
4849
- const taskDir = path16.join(process.cwd(), "exe", agent);
4850
- if (existsSync12(taskDir)) {
5581
+ const taskDir = path18.join(process.cwd(), "exe", agent);
5582
+ if (existsSync14(taskDir)) {
4851
5583
  const files = readdirSync3(taskDir).filter(
4852
5584
  (f) => f.endsWith(".md") && f !== "DONE.txt"
4853
5585
  );
@@ -4907,6 +5639,21 @@ function notifyParentExe(sessionKey) {
4907
5639
  }
4908
5640
  return true;
4909
5641
  }
5642
+ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
5643
+ const transport = getTransport();
5644
+ try {
5645
+ const sessions = transport.listSessions();
5646
+ if (!sessions.includes(coordinatorSession)) return false;
5647
+ execSync7(
5648
+ `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
5649
+ { timeout: 3e3 }
5650
+ );
5651
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
5652
+ return true;
5653
+ } catch {
5654
+ return false;
5655
+ }
5656
+ }
4910
5657
  function ensureEmployee(employeeName, exeSession, projectDir, opts) {
4911
5658
  if (isCoordinatorName(employeeName)) {
4912
5659
  return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
@@ -4980,26 +5727,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4980
5727
  const transport = getTransport();
4981
5728
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
4982
5729
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
4983
- const logDir = path16.join(os9.homedir(), ".exe-os", "session-logs");
4984
- const logFile = path16.join(logDir, `${instanceLabel}-${Date.now()}.log`);
4985
- if (!existsSync12(logDir)) {
5730
+ const logDir = path18.join(os11.homedir(), ".exe-os", "session-logs");
5731
+ const logFile = path18.join(logDir, `${instanceLabel}-${Date.now()}.log`);
5732
+ if (!existsSync14(logDir)) {
4986
5733
  mkdirSync6(logDir, { recursive: true });
4987
5734
  }
4988
5735
  transport.kill(sessionName);
4989
5736
  let cleanupSuffix = "";
4990
5737
  try {
4991
5738
  const thisFile = fileURLToPath2(import.meta.url);
4992
- const cleanupScript = path16.join(path16.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
4993
- if (existsSync12(cleanupScript)) {
5739
+ const cleanupScript = path18.join(path18.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
5740
+ if (existsSync14(cleanupScript)) {
4994
5741
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
4995
5742
  }
4996
5743
  } catch {
4997
5744
  }
4998
5745
  try {
4999
- const claudeJsonPath = path16.join(os9.homedir(), ".claude.json");
5746
+ const claudeJsonPath = path18.join(os11.homedir(), ".claude.json");
5000
5747
  let claudeJson = {};
5001
5748
  try {
5002
- claudeJson = JSON.parse(readFileSync11(claudeJsonPath, "utf8"));
5749
+ claudeJson = JSON.parse(readFileSync12(claudeJsonPath, "utf8"));
5003
5750
  } catch {
5004
5751
  }
5005
5752
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -5007,17 +5754,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5007
5754
  const trustDir = opts?.cwd ?? projectDir;
5008
5755
  if (!projects[trustDir]) projects[trustDir] = {};
5009
5756
  projects[trustDir].hasTrustDialogAccepted = true;
5010
- writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
5757
+ writeFileSync8(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
5011
5758
  } catch {
5012
5759
  }
5013
5760
  try {
5014
- const settingsDir = path16.join(os9.homedir(), ".claude", "projects");
5761
+ const settingsDir = path18.join(os11.homedir(), ".claude", "projects");
5015
5762
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
5016
- const projSettingsDir = path16.join(settingsDir, normalizedKey);
5017
- const settingsPath = path16.join(projSettingsDir, "settings.json");
5763
+ const projSettingsDir = path18.join(settingsDir, normalizedKey);
5764
+ const settingsPath = path18.join(projSettingsDir, "settings.json");
5018
5765
  let settings = {};
5019
5766
  try {
5020
- settings = JSON.parse(readFileSync11(settingsPath, "utf8"));
5767
+ settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
5021
5768
  } catch {
5022
5769
  }
5023
5770
  const perms = settings.permissions ?? {};
@@ -5046,7 +5793,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5046
5793
  perms.allow = allow;
5047
5794
  settings.permissions = perms;
5048
5795
  mkdirSync6(projSettingsDir, { recursive: true });
5049
- writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + "\n");
5796
+ writeFileSync8(settingsPath, JSON.stringify(settings, null, 2) + "\n");
5050
5797
  }
5051
5798
  } catch {
5052
5799
  }
@@ -5061,8 +5808,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5061
5808
  let behaviorsFlag = "";
5062
5809
  let legacyFallbackWarned = false;
5063
5810
  if (!useExeAgent && !useBinSymlink) {
5064
- const identityPath = path16.join(
5065
- os9.homedir(),
5811
+ const identityPath = path18.join(
5812
+ os11.homedir(),
5066
5813
  ".exe-os",
5067
5814
  "identity",
5068
5815
  `${employeeName}.md`
@@ -5071,13 +5818,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5071
5818
  const hasAgentFlag = claudeSupportsAgentFlag();
5072
5819
  if (hasAgentFlag) {
5073
5820
  identityFlag = ` --agent ${employeeName}`;
5074
- } else if (existsSync12(identityPath)) {
5821
+ } else if (existsSync14(identityPath)) {
5075
5822
  identityFlag = ` --append-system-prompt-file ${identityPath}`;
5076
5823
  legacyFallbackWarned = true;
5077
5824
  }
5078
5825
  const behaviorsFile = exportBehaviorsSync(
5079
5826
  employeeName,
5080
- path16.basename(spawnCwd),
5827
+ path18.basename(spawnCwd),
5081
5828
  sessionName
5082
5829
  );
5083
5830
  if (behaviorsFile) {
@@ -5092,16 +5839,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5092
5839
  }
5093
5840
  let sessionContextFlag = "";
5094
5841
  try {
5095
- const ctxDir = path16.join(os9.homedir(), ".exe-os", "session-cache");
5842
+ const ctxDir = path18.join(os11.homedir(), ".exe-os", "session-cache");
5096
5843
  mkdirSync6(ctxDir, { recursive: true });
5097
- const ctxFile = path16.join(ctxDir, `session-context-${sessionName}.md`);
5844
+ const ctxFile = path18.join(ctxDir, `session-context-${sessionName}.md`);
5098
5845
  const ctxContent = [
5099
5846
  `## Session Context`,
5100
5847
  `You are running in tmux session: ${sessionName}.`,
5101
5848
  `Your parent coordinator session is ${exeSession}.`,
5102
5849
  `Your employees (if any) use the -${exeSession} suffix.`
5103
5850
  ].join("\n");
5104
- writeFileSync7(ctxFile, ctxContent);
5851
+ writeFileSync8(ctxFile, ctxContent);
5105
5852
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
5106
5853
  } catch {
5107
5854
  }
@@ -5178,8 +5925,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5178
5925
  transport.pipeLog(sessionName, logFile);
5179
5926
  try {
5180
5927
  const mySession = getMySession();
5181
- const dispatchInfo = path16.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
5182
- writeFileSync7(dispatchInfo, JSON.stringify({
5928
+ const dispatchInfo = path18.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
5929
+ writeFileSync8(dispatchInfo, JSON.stringify({
5183
5930
  dispatchedBy: mySession,
5184
5931
  rootExe: exeSession,
5185
5932
  provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
@@ -5253,15 +6000,15 @@ var init_tmux_routing = __esm({
5253
6000
  init_intercom_queue();
5254
6001
  init_plan_limits();
5255
6002
  init_employees();
5256
- SPAWN_LOCK_DIR = path16.join(os9.homedir(), ".exe-os", "spawn-locks");
5257
- SESSION_CACHE = path16.join(os9.homedir(), ".exe-os", "session-cache");
6003
+ SPAWN_LOCK_DIR = path18.join(os11.homedir(), ".exe-os", "spawn-locks");
6004
+ SESSION_CACHE = path18.join(os11.homedir(), ".exe-os", "session-cache");
5258
6005
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
5259
6006
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
5260
6007
  VERIFY_PANE_LINES = 200;
5261
6008
  INTERCOM_DEBOUNCE_MS = 3e4;
5262
6009
  CODEX_DEBOUNCE_MS = 12e4;
5263
- INTERCOM_LOG2 = path16.join(os9.homedir(), ".exe-os", "intercom.log");
5264
- DEBOUNCE_FILE = path16.join(SESSION_CACHE, "intercom-debounce.json");
6010
+ INTERCOM_LOG2 = path18.join(os11.homedir(), ".exe-os", "intercom.log");
6011
+ DEBOUNCE_FILE = path18.join(SESSION_CACHE, "intercom-debounce.json");
5265
6012
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
5266
6013
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
5267
6014
  }
@@ -5278,14 +6025,14 @@ var init_memory = __esm({
5278
6025
 
5279
6026
  // src/lib/keychain.ts
5280
6027
  import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
5281
- import { existsSync as existsSync13 } from "fs";
5282
- import path17 from "path";
5283
- import os10 from "os";
6028
+ import { existsSync as existsSync15 } from "fs";
6029
+ import path19 from "path";
6030
+ import os12 from "os";
5284
6031
  function getKeyDir() {
5285
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path17.join(os10.homedir(), ".exe-os");
6032
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path19.join(os12.homedir(), ".exe-os");
5286
6033
  }
5287
6034
  function getKeyPath() {
5288
- return path17.join(getKeyDir(), "master.key");
6035
+ return path19.join(getKeyDir(), "master.key");
5289
6036
  }
5290
6037
  async function tryKeytar() {
5291
6038
  try {
@@ -5306,9 +6053,9 @@ async function getMasterKey() {
5306
6053
  }
5307
6054
  }
5308
6055
  const keyPath = getKeyPath();
5309
- if (!existsSync13(keyPath)) {
6056
+ if (!existsSync15(keyPath)) {
5310
6057
  process.stderr.write(
5311
- `[keychain] Key not found at ${keyPath} (HOME=${os10.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
6058
+ `[keychain] Key not found at ${keyPath} (HOME=${os12.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
5312
6059
  `
5313
6060
  );
5314
6061
  return null;
@@ -5338,6 +6085,7 @@ var shard_manager_exports = {};
5338
6085
  __export(shard_manager_exports, {
5339
6086
  disposeShards: () => disposeShards,
5340
6087
  ensureShardSchema: () => ensureShardSchema,
6088
+ getOpenShardCount: () => getOpenShardCount,
5341
6089
  getReadyShardClient: () => getReadyShardClient,
5342
6090
  getShardClient: () => getShardClient,
5343
6091
  getShardsDir: () => getShardsDir,
@@ -5346,15 +6094,18 @@ __export(shard_manager_exports, {
5346
6094
  listShards: () => listShards,
5347
6095
  shardExists: () => shardExists
5348
6096
  });
5349
- import path18 from "path";
5350
- import { existsSync as existsSync14, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
6097
+ import path20 from "path";
6098
+ import { existsSync as existsSync16, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
5351
6099
  import { createClient as createClient2 } from "@libsql/client";
5352
6100
  function initShardManager(encryptionKey) {
5353
6101
  _encryptionKey = encryptionKey;
5354
- if (!existsSync14(SHARDS_DIR)) {
6102
+ if (!existsSync16(SHARDS_DIR)) {
5355
6103
  mkdirSync7(SHARDS_DIR, { recursive: true });
5356
6104
  }
5357
6105
  _shardingEnabled = true;
6106
+ if (_evictionTimer) clearInterval(_evictionTimer);
6107
+ _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
6108
+ _evictionTimer.unref();
5358
6109
  }
5359
6110
  function isShardingEnabled() {
5360
6111
  return _shardingEnabled;
@@ -5371,21 +6122,28 @@ function getShardClient(projectName) {
5371
6122
  throw new Error(`Invalid project name for shard: "${projectName}"`);
5372
6123
  }
5373
6124
  const cached = _shards.get(safeName);
5374
- if (cached) return cached;
5375
- const dbPath = path18.join(SHARDS_DIR, `${safeName}.db`);
6125
+ if (cached) {
6126
+ _shardLastAccess.set(safeName, Date.now());
6127
+ return cached;
6128
+ }
6129
+ while (_shards.size >= MAX_OPEN_SHARDS) {
6130
+ evictLRU();
6131
+ }
6132
+ const dbPath = path20.join(SHARDS_DIR, `${safeName}.db`);
5376
6133
  const client = createClient2({
5377
6134
  url: `file:${dbPath}`,
5378
6135
  encryptionKey: _encryptionKey
5379
6136
  });
5380
6137
  _shards.set(safeName, client);
6138
+ _shardLastAccess.set(safeName, Date.now());
5381
6139
  return client;
5382
6140
  }
5383
6141
  function shardExists(projectName) {
5384
6142
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
5385
- return existsSync14(path18.join(SHARDS_DIR, `${safeName}.db`));
6143
+ return existsSync16(path20.join(SHARDS_DIR, `${safeName}.db`));
5386
6144
  }
5387
6145
  function listShards() {
5388
- if (!existsSync14(SHARDS_DIR)) return [];
6146
+ if (!existsSync16(SHARDS_DIR)) return [];
5389
6147
  return readdirSync4(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
5390
6148
  }
5391
6149
  async function ensureShardSchema(client) {
@@ -5437,6 +6195,8 @@ async function ensureShardSchema(client) {
5437
6195
  for (const col of [
5438
6196
  "ALTER TABLE memories ADD COLUMN task_id TEXT",
5439
6197
  "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
6198
+ "ALTER TABLE memories ADD COLUMN author_device_id TEXT",
6199
+ "ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
5440
6200
  "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
5441
6201
  "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
5442
6202
  "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
@@ -5459,7 +6219,23 @@ async function ensureShardSchema(client) {
5459
6219
  // MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
5460
6220
  "ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
5461
6221
  "ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
5462
- "ALTER TABLE memories ADD COLUMN trajectory TEXT"
6222
+ "ALTER TABLE memories ADD COLUMN trajectory TEXT",
6223
+ // Metadata enrichment columns (must match database.ts)
6224
+ "ALTER TABLE memories ADD COLUMN intent TEXT",
6225
+ "ALTER TABLE memories ADD COLUMN outcome TEXT",
6226
+ "ALTER TABLE memories ADD COLUMN domain TEXT",
6227
+ "ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
6228
+ "ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
6229
+ "ALTER TABLE memories ADD COLUMN chain_position TEXT",
6230
+ "ALTER TABLE memories ADD COLUMN review_status TEXT",
6231
+ "ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
6232
+ "ALTER TABLE memories ADD COLUMN file_paths TEXT",
6233
+ "ALTER TABLE memories ADD COLUMN commit_hash TEXT",
6234
+ "ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
6235
+ "ALTER TABLE memories ADD COLUMN token_cost REAL",
6236
+ "ALTER TABLE memories ADD COLUMN audience TEXT",
6237
+ "ALTER TABLE memories ADD COLUMN language_type TEXT",
6238
+ "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
5463
6239
  ]) {
5464
6240
  try {
5465
6241
  await client.execute(col);
@@ -5558,21 +6334,69 @@ async function getReadyShardClient(projectName) {
5558
6334
  await ensureShardSchema(client);
5559
6335
  return client;
5560
6336
  }
6337
+ function evictLRU() {
6338
+ let oldest = null;
6339
+ let oldestTime = Infinity;
6340
+ for (const [name, time] of _shardLastAccess) {
6341
+ if (time < oldestTime) {
6342
+ oldestTime = time;
6343
+ oldest = name;
6344
+ }
6345
+ }
6346
+ if (oldest) {
6347
+ const client = _shards.get(oldest);
6348
+ if (client) {
6349
+ client.close();
6350
+ }
6351
+ _shards.delete(oldest);
6352
+ _shardLastAccess.delete(oldest);
6353
+ }
6354
+ }
6355
+ function evictIdleShards() {
6356
+ const now = Date.now();
6357
+ const toEvict = [];
6358
+ for (const [name, lastAccess] of _shardLastAccess) {
6359
+ if (now - lastAccess > SHARD_IDLE_MS) {
6360
+ toEvict.push(name);
6361
+ }
6362
+ }
6363
+ for (const name of toEvict) {
6364
+ const client = _shards.get(name);
6365
+ if (client) {
6366
+ client.close();
6367
+ }
6368
+ _shards.delete(name);
6369
+ _shardLastAccess.delete(name);
6370
+ }
6371
+ }
6372
+ function getOpenShardCount() {
6373
+ return _shards.size;
6374
+ }
5561
6375
  function disposeShards() {
6376
+ if (_evictionTimer) {
6377
+ clearInterval(_evictionTimer);
6378
+ _evictionTimer = null;
6379
+ }
5562
6380
  for (const [, client] of _shards) {
5563
6381
  client.close();
5564
6382
  }
5565
6383
  _shards.clear();
6384
+ _shardLastAccess.clear();
5566
6385
  _shardingEnabled = false;
5567
6386
  _encryptionKey = null;
5568
6387
  }
5569
- var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
6388
+ var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
5570
6389
  var init_shard_manager = __esm({
5571
6390
  "src/lib/shard-manager.ts"() {
5572
6391
  "use strict";
5573
6392
  init_config();
5574
- SHARDS_DIR = path18.join(EXE_AI_DIR, "shards");
6393
+ SHARDS_DIR = path20.join(EXE_AI_DIR, "shards");
6394
+ SHARD_IDLE_MS = 5 * 60 * 1e3;
6395
+ MAX_OPEN_SHARDS = 10;
6396
+ EVICTION_INTERVAL_MS = 60 * 1e3;
5575
6397
  _shards = /* @__PURE__ */ new Map();
6398
+ _shardLastAccess = /* @__PURE__ */ new Map();
6399
+ _evictionTimer = null;
5576
6400
  _encryptionKey = null;
5577
6401
  _shardingEnabled = false;
5578
6402
  }
@@ -6368,8 +7192,8 @@ function findContainingChunk(filePath, snippet) {
6368
7192
  try {
6369
7193
  const ext = filePath.split(".").pop()?.toLowerCase();
6370
7194
  if (ext !== "ts" && ext !== "tsx" && ext !== "js" && ext !== "jsx") return "";
6371
- const { readFileSync: readFileSync12 } = __require("fs");
6372
- const source = readFileSync12(filePath, "utf8");
7195
+ const { readFileSync: readFileSync13 } = __require("fs");
7196
+ const source = readFileSync13(filePath, "utf8");
6373
7197
  const lines = source.split("\n");
6374
7198
  const lowerSnippet = snippet.toLowerCase().slice(0, 80);
6375
7199
  let matchLine = -1;
@@ -6435,9 +7259,9 @@ function extractBash(input, response) {
6435
7259
  }
6436
7260
  function extractGrep(input, response) {
6437
7261
  const pattern = String(input.pattern ?? "");
6438
- const path19 = input.path ? String(input.path) : "";
7262
+ const path21 = input.path ? String(input.path) : "";
6439
7263
  const output = String(response.text ?? response.content ?? JSON.stringify(response).slice(0, MAX_OUTPUT));
6440
- return `Searched for "${pattern}"${path19 ? ` in ${path19}` : ""}
7264
+ return `Searched for "${pattern}"${path21 ? ` in ${path21}` : ""}
6441
7265
  ${output.slice(0, MAX_OUTPUT)}`;
6442
7266
  }
6443
7267
  function extractGlob(input, response) {
@@ -6540,7 +7364,7 @@ __export(error_detector_exports, {
6540
7364
  errorFingerprint: () => errorFingerprint,
6541
7365
  isExeOsError: () => isExeOsError
6542
7366
  });
6543
- import crypto6 from "crypto";
7367
+ import crypto7 from "crypto";
6544
7368
  function isRealStderr(stderr) {
6545
7369
  const lines = stderr.trim().split("\n");
6546
7370
  const meaningful = lines.filter(
@@ -6611,7 +7435,7 @@ function classifyError(errorText) {
6611
7435
  }
6612
7436
  function errorFingerprint(toolName, errorText) {
6613
7437
  const normalized = errorText.replace(/\d{4}-\d{2}-\d{2}T[\d:.]+Z/g, "TIMESTAMP").replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, "UUID").replace(/\/Users\/[^\s]+/g, "PATH").replace(/:\d+:\d+/g, ":LINE:COL").slice(0, 200);
6614
- return crypto6.createHash("sha256").update(`${toolName}:${normalized}`).digest("hex").slice(0, 16);
7438
+ return crypto7.createHash("sha256").update(`${toolName}:${normalized}`).digest("hex").slice(0, 16);
6615
7439
  }
6616
7440
  var ERROR_PATTERNS, FILE_CONTENT_TOOLS, STDERR_IGNORE_PATTERNS, USER_ERROR_PATTERNS, SYSTEM_BUG_PATTERNS;
6617
7441
  var init_error_detector = __esm({