@askexenow/exe-os 0.9.7 → 0.9.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/dist/bin/backfill-conversations.js +953 -105
  2. package/dist/bin/backfill-responses.js +952 -104
  3. package/dist/bin/backfill-vectors.js +956 -108
  4. package/dist/bin/cleanup-stale-review-tasks.js +802 -58
  5. package/dist/bin/cli.js +2292 -1070
  6. package/dist/bin/exe-agent-config.js +157 -101
  7. package/dist/bin/exe-agent.js +55 -29
  8. package/dist/bin/exe-assign.js +940 -92
  9. package/dist/bin/exe-boot.js +1424 -442
  10. package/dist/bin/exe-call.js +240 -141
  11. package/dist/bin/exe-cloud.js +198 -70
  12. package/dist/bin/exe-dispatch.js +951 -192
  13. package/dist/bin/exe-doctor.js +791 -51
  14. package/dist/bin/exe-export-behaviors.js +790 -42
  15. package/dist/bin/exe-forget.js +771 -31
  16. package/dist/bin/exe-gateway.js +1592 -521
  17. package/dist/bin/exe-heartbeat.js +850 -109
  18. package/dist/bin/exe-kill.js +783 -35
  19. package/dist/bin/exe-launch-agent.js +1030 -107
  20. package/dist/bin/exe-link.js +916 -110
  21. package/dist/bin/exe-new-employee.js +526 -217
  22. package/dist/bin/exe-pending-messages.js +1046 -62
  23. package/dist/bin/exe-pending-notifications.js +1318 -111
  24. package/dist/bin/exe-pending-reviews.js +1040 -72
  25. package/dist/bin/exe-rename.js +772 -59
  26. package/dist/bin/exe-review.js +772 -32
  27. package/dist/bin/exe-search.js +982 -128
  28. package/dist/bin/exe-session-cleanup.js +1180 -306
  29. package/dist/bin/exe-settings.js +185 -105
  30. package/dist/bin/exe-start-codex.js +886 -132
  31. package/dist/bin/exe-start-opencode.js +873 -119
  32. package/dist/bin/exe-status.js +803 -59
  33. package/dist/bin/exe-team.js +772 -32
  34. package/dist/bin/git-sweep.js +1046 -223
  35. package/dist/bin/graph-backfill.js +779 -31
  36. package/dist/bin/graph-export.js +785 -37
  37. package/dist/bin/install.js +632 -200
  38. package/dist/bin/scan-tasks.js +1055 -232
  39. package/dist/bin/setup.js +1419 -320
  40. package/dist/bin/shard-migrate.js +783 -35
  41. package/dist/bin/update.js +138 -49
  42. package/dist/bin/wiki-sync.js +782 -34
  43. package/dist/gateway/index.js +1444 -449
  44. package/dist/hooks/bug-report-worker.js +1141 -269
  45. package/dist/hooks/codex-stop-task-finalizer.js +4678 -0
  46. package/dist/hooks/commit-complete.js +1044 -221
  47. package/dist/hooks/error-recall.js +989 -135
  48. package/dist/hooks/exe-heartbeat-hook.js +99 -75
  49. package/dist/hooks/ingest-worker.js +4176 -3226
  50. package/dist/hooks/ingest.js +920 -168
  51. package/dist/hooks/instructions-loaded.js +874 -70
  52. package/dist/hooks/notification.js +860 -56
  53. package/dist/hooks/post-compact.js +881 -73
  54. package/dist/hooks/pre-compact.js +1050 -227
  55. package/dist/hooks/pre-tool-use.js +1084 -159
  56. package/dist/hooks/prompt-ingest-worker.js +1089 -164
  57. package/dist/hooks/prompt-submit.js +1469 -515
  58. package/dist/hooks/response-ingest-worker.js +1104 -179
  59. package/dist/hooks/session-end.js +1085 -251
  60. package/dist/hooks/session-start.js +1241 -231
  61. package/dist/hooks/stop.js +935 -109
  62. package/dist/hooks/subagent-stop.js +881 -73
  63. package/dist/hooks/summary-worker.js +1323 -307
  64. package/dist/index.js +1449 -452
  65. package/dist/lib/agent-config.js +28 -6
  66. package/dist/lib/cloud-sync.js +909 -115
  67. package/dist/lib/config.js +30 -10
  68. package/dist/lib/consolidation.js +42 -9
  69. package/dist/lib/database.js +739 -33
  70. package/dist/lib/db-daemon-client.js +73 -19
  71. package/dist/lib/db.js +2359 -0
  72. package/dist/lib/device-registry.js +760 -47
  73. package/dist/lib/embedder.js +201 -73
  74. package/dist/lib/employee-templates.js +30 -4
  75. package/dist/lib/employees.js +290 -86
  76. package/dist/lib/exe-daemon-client.js +187 -83
  77. package/dist/lib/exe-daemon.js +1696 -616
  78. package/dist/lib/hybrid-search.js +982 -128
  79. package/dist/lib/identity.js +43 -13
  80. package/dist/lib/license.js +133 -48
  81. package/dist/lib/messaging.js +167 -80
  82. package/dist/lib/reminders.js +35 -5
  83. package/dist/lib/schedules.js +772 -32
  84. package/dist/lib/skill-learning.js +54 -7
  85. package/dist/lib/store.js +779 -31
  86. package/dist/lib/task-router.js +94 -73
  87. package/dist/lib/tasks.js +298 -225
  88. package/dist/lib/tmux-routing.js +246 -172
  89. package/dist/lib/token-spend.js +52 -14
  90. package/dist/mcp/server.js +2893 -850
  91. package/dist/mcp/tools/complete-reminder.js +35 -5
  92. package/dist/mcp/tools/create-reminder.js +35 -5
  93. package/dist/mcp/tools/create-task.js +507 -323
  94. package/dist/mcp/tools/deactivate-behavior.js +40 -10
  95. package/dist/mcp/tools/list-reminders.js +35 -5
  96. package/dist/mcp/tools/list-tasks.js +277 -104
  97. package/dist/mcp/tools/send-message.js +129 -56
  98. package/dist/mcp/tools/update-task.js +1864 -188
  99. package/dist/runtime/index.js +1083 -259
  100. package/dist/tui/App.js +1501 -434
  101. package/package.json +3 -2
@@ -25,9 +25,47 @@ var __copyProps = (to, from, except, desc) => {
25
25
  };
26
26
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
27
27
 
28
+ // src/lib/secure-files.ts
29
+ import { chmodSync, existsSync, mkdirSync } from "fs";
30
+ import { chmod, mkdir } from "fs/promises";
31
+ async function ensurePrivateDir(dirPath) {
32
+ await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
33
+ try {
34
+ await chmod(dirPath, PRIVATE_DIR_MODE);
35
+ } catch {
36
+ }
37
+ }
38
+ function ensurePrivateDirSync(dirPath) {
39
+ mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
40
+ try {
41
+ chmodSync(dirPath, PRIVATE_DIR_MODE);
42
+ } catch {
43
+ }
44
+ }
45
+ async function enforcePrivateFile(filePath) {
46
+ try {
47
+ await chmod(filePath, PRIVATE_FILE_MODE);
48
+ } catch {
49
+ }
50
+ }
51
+ function enforcePrivateFileSync(filePath) {
52
+ try {
53
+ if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
54
+ } catch {
55
+ }
56
+ }
57
+ var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
58
+ var init_secure_files = __esm({
59
+ "src/lib/secure-files.ts"() {
60
+ "use strict";
61
+ PRIVATE_DIR_MODE = 448;
62
+ PRIVATE_FILE_MODE = 384;
63
+ }
64
+ });
65
+
28
66
  // src/lib/config.ts
29
- import { readFile, writeFile, mkdir, chmod } from "fs/promises";
30
- import { readFileSync, existsSync, renameSync } from "fs";
67
+ import { readFile, writeFile } from "fs/promises";
68
+ import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
31
69
  import path from "path";
32
70
  import os from "os";
33
71
  function resolveDataDir() {
@@ -35,7 +73,7 @@ function resolveDataDir() {
35
73
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
36
74
  const newDir = path.join(os.homedir(), ".exe-os");
37
75
  const legacyDir = path.join(os.homedir(), ".exe-mem");
38
- if (!existsSync(newDir) && existsSync(legacyDir)) {
76
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
39
77
  try {
40
78
  renameSync(legacyDir, newDir);
41
79
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -98,9 +136,9 @@ function normalizeAutoUpdate(raw) {
98
136
  }
99
137
  async function loadConfig() {
100
138
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
101
- await mkdir(dir, { recursive: true });
139
+ await ensurePrivateDir(dir);
102
140
  const configPath = path.join(dir, "config.json");
103
- if (!existsSync(configPath)) {
141
+ if (!existsSync2(configPath)) {
104
142
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
105
143
  }
106
144
  const raw = await readFile(configPath, "utf-8");
@@ -113,6 +151,7 @@ async function loadConfig() {
113
151
  `);
114
152
  try {
115
153
  await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
154
+ await enforcePrivateFile(configPath);
116
155
  } catch {
117
156
  }
118
157
  }
@@ -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 = path.join(EXE_AI_DIR, "memories.db");
137
177
  MODELS_DIR = path.join(EXE_AI_DIR, "models");
@@ -278,7 +318,7 @@ var init_session_key = __esm({
278
318
 
279
319
  // src/lib/employees.ts
280
320
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
281
- import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
321
+ import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
282
322
  import { execSync as execSync2 } from "child_process";
283
323
  import path2 from "path";
284
324
  import os2 from "os";
@@ -302,7 +342,7 @@ function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
302
342
  return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
303
343
  }
304
344
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
305
- if (!existsSync2(employeesPath)) return [];
345
+ if (!existsSync3(employeesPath)) return [];
306
346
  try {
307
347
  return JSON.parse(readFileSync2(employeesPath, "utf-8"));
308
348
  } catch {
@@ -326,7 +366,7 @@ function isMultiInstance(agentName, employees) {
326
366
  if (!emp) return false;
327
367
  return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
328
368
  }
329
- var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
369
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR;
330
370
  var init_employees = __esm({
331
371
  "src/lib/employees.ts"() {
332
372
  "use strict";
@@ -335,17 +375,18 @@ var init_employees = __esm({
335
375
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
336
376
  COORDINATOR_ROLE = "COO";
337
377
  MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
378
+ IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
338
379
  }
339
380
  });
340
381
 
341
382
  // src/lib/session-registry.ts
342
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as existsSync3 } from "fs";
383
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync4 } from "fs";
343
384
  import path4 from "path";
344
385
  import os3 from "os";
345
386
  function registerSession(entry) {
346
387
  const dir = path4.dirname(REGISTRY_PATH);
347
- if (!existsSync3(dir)) {
348
- mkdirSync2(dir, { recursive: true });
388
+ if (!existsSync4(dir)) {
389
+ mkdirSync3(dir, { recursive: true });
349
390
  }
350
391
  const sessions = listSessions();
351
392
  const idx = sessions.findIndex((s) => s.windowName === entry.windowName);
@@ -580,10 +621,10 @@ var init_runtime_table = __esm({
580
621
  });
581
622
 
582
623
  // src/lib/agent-config.ts
583
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
624
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync5 } from "fs";
584
625
  import path5 from "path";
585
626
  function loadAgentConfig() {
586
- if (!existsSync4(AGENT_CONFIG_PATH)) return {};
627
+ if (!existsSync5(AGENT_CONFIG_PATH)) return {};
587
628
  try {
588
629
  return JSON.parse(readFileSync5(AGENT_CONFIG_PATH, "utf-8"));
589
630
  } catch {
@@ -604,6 +645,7 @@ var init_agent_config = __esm({
604
645
  "use strict";
605
646
  init_config();
606
647
  init_runtime_table();
648
+ init_secure_files();
607
649
  AGENT_CONFIG_PATH = path5.join(EXE_AI_DIR, "agent-config.json");
608
650
  DEFAULT_MODELS = {
609
651
  claude: "claude-opus-4",
@@ -622,16 +664,16 @@ __export(intercom_queue_exports, {
622
664
  queueIntercom: () => queueIntercom,
623
665
  readQueue: () => readQueue
624
666
  });
625
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, renameSync as renameSync3, existsSync as existsSync5, mkdirSync as mkdirSync4 } from "fs";
667
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, renameSync as renameSync3, existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
626
668
  import path6 from "path";
627
669
  import os4 from "os";
628
670
  function ensureDir() {
629
671
  const dir = path6.dirname(QUEUE_PATH);
630
- if (!existsSync5(dir)) mkdirSync4(dir, { recursive: true });
672
+ if (!existsSync6(dir)) mkdirSync4(dir, { recursive: true });
631
673
  }
632
674
  function readQueue() {
633
675
  try {
634
- if (!existsSync5(QUEUE_PATH)) return [];
676
+ if (!existsSync6(QUEUE_PATH)) return [];
635
677
  return JSON.parse(readFileSync6(QUEUE_PATH, "utf8"));
636
678
  } catch {
637
679
  return [];
@@ -794,13 +836,634 @@ var init_db_retry = __esm({
794
836
  }
795
837
  });
796
838
 
839
+ // src/lib/database-adapter.ts
840
+ import os5 from "os";
841
+ import path7 from "path";
842
+ import { createRequire } from "module";
843
+ import { pathToFileURL } from "url";
844
+ function quotedIdentifier(identifier) {
845
+ return `"${identifier.replace(/"/g, '""')}"`;
846
+ }
847
+ function unqualifiedTableName(name) {
848
+ const raw = name.trim().replace(/^"|"$/g, "");
849
+ const parts = raw.split(".");
850
+ return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
851
+ }
852
+ function stripTrailingSemicolon(sql) {
853
+ return sql.trim().replace(/;+\s*$/u, "");
854
+ }
855
+ function appendClause(sql, clause) {
856
+ const trimmed = stripTrailingSemicolon(sql);
857
+ const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
858
+ if (!returningMatch) {
859
+ return `${trimmed}${clause}`;
860
+ }
861
+ const idx = returningMatch.index;
862
+ return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
863
+ }
864
+ function normalizeStatement(stmt) {
865
+ if (typeof stmt === "string") {
866
+ return { kind: "positional", sql: stmt, args: [] };
867
+ }
868
+ const sql = stmt.sql;
869
+ if (Array.isArray(stmt.args) || stmt.args === void 0) {
870
+ return { kind: "positional", sql, args: stmt.args ?? [] };
871
+ }
872
+ return { kind: "named", sql, args: stmt.args };
873
+ }
874
+ function rewriteBooleanLiterals(sql) {
875
+ let out = sql;
876
+ for (const column of BOOLEAN_COLUMN_NAMES) {
877
+ const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
878
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
879
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
880
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
881
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
882
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
883
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
884
+ }
885
+ return out;
886
+ }
887
+ function rewriteInsertOrIgnore(sql) {
888
+ if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
889
+ return sql;
890
+ }
891
+ const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
892
+ return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
893
+ }
894
+ function rewriteInsertOrReplace(sql) {
895
+ const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
896
+ if (!match) {
897
+ return sql;
898
+ }
899
+ const rawTable = match[1];
900
+ const rawColumns = match[2];
901
+ const remainder = match[3];
902
+ const tableName = unqualifiedTableName(rawTable);
903
+ const conflictKeys = UPSERT_KEYS[tableName];
904
+ if (!conflictKeys?.length) {
905
+ return sql;
906
+ }
907
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
908
+ const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
909
+ const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
910
+ const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
911
+ return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
912
+ }
913
+ function rewriteSql(sql) {
914
+ let out = sql;
915
+ out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
916
+ out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
917
+ out = rewriteBooleanLiterals(out);
918
+ out = rewriteInsertOrReplace(out);
919
+ out = rewriteInsertOrIgnore(out);
920
+ return stripTrailingSemicolon(out);
921
+ }
922
+ function toBoolean(value) {
923
+ if (value === null || value === void 0) return value;
924
+ if (typeof value === "boolean") return value;
925
+ if (typeof value === "number") return value !== 0;
926
+ if (typeof value === "bigint") return value !== 0n;
927
+ if (typeof value === "string") {
928
+ const normalized = value.trim().toLowerCase();
929
+ if (normalized === "0" || normalized === "false") return false;
930
+ if (normalized === "1" || normalized === "true") return true;
931
+ }
932
+ return Boolean(value);
933
+ }
934
+ function countQuestionMarks(sql, end) {
935
+ let count = 0;
936
+ let inSingle = false;
937
+ let inDouble = false;
938
+ let inLineComment = false;
939
+ let inBlockComment = false;
940
+ for (let i = 0; i < end; i++) {
941
+ const ch = sql[i];
942
+ const next = sql[i + 1];
943
+ if (inLineComment) {
944
+ if (ch === "\n") inLineComment = false;
945
+ continue;
946
+ }
947
+ if (inBlockComment) {
948
+ if (ch === "*" && next === "/") {
949
+ inBlockComment = false;
950
+ i += 1;
951
+ }
952
+ continue;
953
+ }
954
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
955
+ inLineComment = true;
956
+ i += 1;
957
+ continue;
958
+ }
959
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
960
+ inBlockComment = true;
961
+ i += 1;
962
+ continue;
963
+ }
964
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
965
+ inSingle = !inSingle;
966
+ continue;
967
+ }
968
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
969
+ inDouble = !inDouble;
970
+ continue;
971
+ }
972
+ if (!inSingle && !inDouble && ch === "?") {
973
+ count += 1;
974
+ }
975
+ }
976
+ return count;
977
+ }
978
+ function findBooleanPlaceholderIndexes(sql) {
979
+ const indexes = /* @__PURE__ */ new Set();
980
+ for (const column of BOOLEAN_COLUMN_NAMES) {
981
+ const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
982
+ for (const match of sql.matchAll(pattern)) {
983
+ const matchText = match[0];
984
+ const qIndex = match.index + matchText.lastIndexOf("?");
985
+ indexes.add(countQuestionMarks(sql, qIndex + 1));
986
+ }
987
+ }
988
+ return indexes;
989
+ }
990
+ function coerceInsertBooleanArgs(sql, args) {
991
+ const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
992
+ if (!match) return;
993
+ const rawTable = match[1];
994
+ const rawColumns = match[2];
995
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
996
+ if (!boolColumns?.size) return;
997
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
998
+ for (const [index, column] of columns.entries()) {
999
+ if (boolColumns.has(column) && index < args.length) {
1000
+ args[index] = toBoolean(args[index]);
1001
+ }
1002
+ }
1003
+ }
1004
+ function coerceUpdateBooleanArgs(sql, args) {
1005
+ const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
1006
+ if (!match) return;
1007
+ const rawTable = match[1];
1008
+ const setClause = match[2];
1009
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
1010
+ if (!boolColumns?.size) return;
1011
+ const assignments = setClause.split(",");
1012
+ let placeholderIndex = 0;
1013
+ for (const assignment of assignments) {
1014
+ if (!assignment.includes("?")) continue;
1015
+ placeholderIndex += 1;
1016
+ const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
1017
+ if (colMatch && boolColumns.has(colMatch[1])) {
1018
+ args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
1019
+ }
1020
+ }
1021
+ }
1022
+ function coerceBooleanArgs(sql, args) {
1023
+ const nextArgs = [...args];
1024
+ coerceInsertBooleanArgs(sql, nextArgs);
1025
+ coerceUpdateBooleanArgs(sql, nextArgs);
1026
+ const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
1027
+ for (const index of placeholderIndexes) {
1028
+ if (index > 0 && index <= nextArgs.length) {
1029
+ nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
1030
+ }
1031
+ }
1032
+ return nextArgs;
1033
+ }
1034
+ function convertQuestionMarksToDollarParams(sql) {
1035
+ let out = "";
1036
+ let placeholder = 0;
1037
+ let inSingle = false;
1038
+ let inDouble = false;
1039
+ let inLineComment = false;
1040
+ let inBlockComment = false;
1041
+ for (let i = 0; i < sql.length; i++) {
1042
+ const ch = sql[i];
1043
+ const next = sql[i + 1];
1044
+ if (inLineComment) {
1045
+ out += ch;
1046
+ if (ch === "\n") inLineComment = false;
1047
+ continue;
1048
+ }
1049
+ if (inBlockComment) {
1050
+ out += ch;
1051
+ if (ch === "*" && next === "/") {
1052
+ out += next;
1053
+ inBlockComment = false;
1054
+ i += 1;
1055
+ }
1056
+ continue;
1057
+ }
1058
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
1059
+ out += ch + next;
1060
+ inLineComment = true;
1061
+ i += 1;
1062
+ continue;
1063
+ }
1064
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
1065
+ out += ch + next;
1066
+ inBlockComment = true;
1067
+ i += 1;
1068
+ continue;
1069
+ }
1070
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
1071
+ inSingle = !inSingle;
1072
+ out += ch;
1073
+ continue;
1074
+ }
1075
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
1076
+ inDouble = !inDouble;
1077
+ out += ch;
1078
+ continue;
1079
+ }
1080
+ if (!inSingle && !inDouble && ch === "?") {
1081
+ placeholder += 1;
1082
+ out += `$${placeholder}`;
1083
+ continue;
1084
+ }
1085
+ out += ch;
1086
+ }
1087
+ return out;
1088
+ }
1089
+ function translateStatementForPostgres(stmt) {
1090
+ const normalized = normalizeStatement(stmt);
1091
+ if (normalized.kind === "named") {
1092
+ throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
1093
+ }
1094
+ const rewrittenSql = rewriteSql(normalized.sql);
1095
+ const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
1096
+ return {
1097
+ sql: convertQuestionMarksToDollarParams(rewrittenSql),
1098
+ args: coercedArgs
1099
+ };
1100
+ }
1101
+ function shouldBypassPostgres(stmt) {
1102
+ const normalized = normalizeStatement(stmt);
1103
+ if (normalized.kind === "named") {
1104
+ return true;
1105
+ }
1106
+ return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
1107
+ }
1108
+ function shouldFallbackOnError(error) {
1109
+ const message = error instanceof Error ? error.message : String(error);
1110
+ return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
1111
+ }
1112
+ function isReadQuery(sql) {
1113
+ const trimmed = sql.trimStart();
1114
+ return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
1115
+ }
1116
+ function buildRow(row, columns) {
1117
+ const values = columns.map((column) => row[column]);
1118
+ return Object.assign(values, row);
1119
+ }
1120
+ function buildResultSet(rows, rowsAffected = 0) {
1121
+ const columns = rows[0] ? Object.keys(rows[0]) : [];
1122
+ const resultRows = rows.map((row) => buildRow(row, columns));
1123
+ return {
1124
+ columns,
1125
+ columnTypes: columns.map(() => ""),
1126
+ rows: resultRows,
1127
+ rowsAffected,
1128
+ lastInsertRowid: void 0,
1129
+ toJSON() {
1130
+ return {
1131
+ columns,
1132
+ columnTypes: columns.map(() => ""),
1133
+ rows,
1134
+ rowsAffected,
1135
+ lastInsertRowid: void 0
1136
+ };
1137
+ }
1138
+ };
1139
+ }
1140
+ async function loadPrismaClient() {
1141
+ if (!prismaClientPromise) {
1142
+ prismaClientPromise = (async () => {
1143
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
1144
+ if (explicitPath) {
1145
+ const module2 = await import(pathToFileURL(explicitPath).href);
1146
+ const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
1147
+ if (!PrismaClient2) {
1148
+ throw new Error(`No PrismaClient export found at ${explicitPath}`);
1149
+ }
1150
+ return new PrismaClient2();
1151
+ }
1152
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path7.join(os5.homedir(), "exe-db");
1153
+ const requireFromExeDb = createRequire(path7.join(exeDbRoot, "package.json"));
1154
+ const prismaEntry = requireFromExeDb.resolve("@prisma/client");
1155
+ const module = await import(pathToFileURL(prismaEntry).href);
1156
+ const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
1157
+ if (!PrismaClient) {
1158
+ throw new Error(`No PrismaClient export found in ${prismaEntry}`);
1159
+ }
1160
+ return new PrismaClient();
1161
+ })();
1162
+ }
1163
+ return prismaClientPromise;
1164
+ }
1165
+ async function ensureCompatibilityViews(prisma) {
1166
+ if (!compatibilityBootstrapPromise) {
1167
+ compatibilityBootstrapPromise = (async () => {
1168
+ for (const mapping of VIEW_MAPPINGS) {
1169
+ const relation = mapping.source.replace(/"/g, "");
1170
+ const rows = await prisma.$queryRawUnsafe(
1171
+ "SELECT to_regclass($1) AS regclass",
1172
+ relation
1173
+ );
1174
+ if (!rows[0]?.regclass) {
1175
+ continue;
1176
+ }
1177
+ await prisma.$executeRawUnsafe(
1178
+ `CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
1179
+ );
1180
+ }
1181
+ })();
1182
+ }
1183
+ return compatibilityBootstrapPromise;
1184
+ }
1185
+ async function executeOnPrisma(executor, stmt) {
1186
+ const translated = translateStatementForPostgres(stmt);
1187
+ if (isReadQuery(translated.sql)) {
1188
+ const rows = await executor.$queryRawUnsafe(
1189
+ translated.sql,
1190
+ ...translated.args
1191
+ );
1192
+ return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
1193
+ }
1194
+ const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
1195
+ return buildResultSet([], rowsAffected);
1196
+ }
1197
+ function splitSqlStatements(sql) {
1198
+ const parts = [];
1199
+ let current = "";
1200
+ let inSingle = false;
1201
+ let inDouble = false;
1202
+ let inLineComment = false;
1203
+ let inBlockComment = false;
1204
+ for (let i = 0; i < sql.length; i++) {
1205
+ const ch = sql[i];
1206
+ const next = sql[i + 1];
1207
+ if (inLineComment) {
1208
+ current += ch;
1209
+ if (ch === "\n") inLineComment = false;
1210
+ continue;
1211
+ }
1212
+ if (inBlockComment) {
1213
+ current += ch;
1214
+ if (ch === "*" && next === "/") {
1215
+ current += next;
1216
+ inBlockComment = false;
1217
+ i += 1;
1218
+ }
1219
+ continue;
1220
+ }
1221
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
1222
+ current += ch + next;
1223
+ inLineComment = true;
1224
+ i += 1;
1225
+ continue;
1226
+ }
1227
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
1228
+ current += ch + next;
1229
+ inBlockComment = true;
1230
+ i += 1;
1231
+ continue;
1232
+ }
1233
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
1234
+ inSingle = !inSingle;
1235
+ current += ch;
1236
+ continue;
1237
+ }
1238
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
1239
+ inDouble = !inDouble;
1240
+ current += ch;
1241
+ continue;
1242
+ }
1243
+ if (!inSingle && !inDouble && ch === ";") {
1244
+ if (current.trim()) {
1245
+ parts.push(current.trim());
1246
+ }
1247
+ current = "";
1248
+ continue;
1249
+ }
1250
+ current += ch;
1251
+ }
1252
+ if (current.trim()) {
1253
+ parts.push(current.trim());
1254
+ }
1255
+ return parts;
1256
+ }
1257
+ async function createPrismaDbAdapter(fallbackClient) {
1258
+ const prisma = await loadPrismaClient();
1259
+ await ensureCompatibilityViews(prisma);
1260
+ let closed = false;
1261
+ let adapter;
1262
+ const fallbackExecute = async (stmt, error) => {
1263
+ if (!fallbackClient) {
1264
+ if (error) throw error;
1265
+ throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
1266
+ }
1267
+ if (error) {
1268
+ process.stderr.write(
1269
+ `[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
1270
+ `
1271
+ );
1272
+ }
1273
+ return fallbackClient.execute(stmt);
1274
+ };
1275
+ adapter = {
1276
+ async execute(stmt) {
1277
+ if (shouldBypassPostgres(stmt)) {
1278
+ return fallbackExecute(stmt);
1279
+ }
1280
+ try {
1281
+ return await executeOnPrisma(prisma, stmt);
1282
+ } catch (error) {
1283
+ if (shouldFallbackOnError(error)) {
1284
+ return fallbackExecute(stmt, error);
1285
+ }
1286
+ throw error;
1287
+ }
1288
+ },
1289
+ async batch(stmts, mode) {
1290
+ if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
1291
+ if (!fallbackClient) {
1292
+ throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
1293
+ }
1294
+ return fallbackClient.batch(stmts, mode);
1295
+ }
1296
+ try {
1297
+ if (prisma.$transaction) {
1298
+ return await prisma.$transaction(async (tx) => {
1299
+ const results2 = [];
1300
+ for (const stmt of stmts) {
1301
+ results2.push(await executeOnPrisma(tx, stmt));
1302
+ }
1303
+ return results2;
1304
+ });
1305
+ }
1306
+ const results = [];
1307
+ for (const stmt of stmts) {
1308
+ results.push(await executeOnPrisma(prisma, stmt));
1309
+ }
1310
+ return results;
1311
+ } catch (error) {
1312
+ if (fallbackClient && shouldFallbackOnError(error)) {
1313
+ process.stderr.write(
1314
+ `[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
1315
+ `
1316
+ );
1317
+ return fallbackClient.batch(stmts, mode);
1318
+ }
1319
+ throw error;
1320
+ }
1321
+ },
1322
+ async migrate(stmts) {
1323
+ if (fallbackClient) {
1324
+ return fallbackClient.migrate(stmts);
1325
+ }
1326
+ return adapter.batch(stmts, "deferred");
1327
+ },
1328
+ async transaction(mode) {
1329
+ if (!fallbackClient) {
1330
+ throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
1331
+ }
1332
+ return fallbackClient.transaction(mode);
1333
+ },
1334
+ async executeMultiple(sql) {
1335
+ if (fallbackClient && shouldBypassPostgres(sql)) {
1336
+ return fallbackClient.executeMultiple(sql);
1337
+ }
1338
+ for (const statement of splitSqlStatements(sql)) {
1339
+ await adapter.execute(statement);
1340
+ }
1341
+ },
1342
+ async sync() {
1343
+ if (fallbackClient) {
1344
+ return fallbackClient.sync();
1345
+ }
1346
+ return { frame_no: 0, frames_synced: 0 };
1347
+ },
1348
+ close() {
1349
+ closed = true;
1350
+ prismaClientPromise = null;
1351
+ compatibilityBootstrapPromise = null;
1352
+ void prisma.$disconnect?.();
1353
+ },
1354
+ get closed() {
1355
+ return closed;
1356
+ },
1357
+ get protocol() {
1358
+ return "prisma-postgres";
1359
+ }
1360
+ };
1361
+ return adapter;
1362
+ }
1363
+ var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
1364
+ var init_database_adapter = __esm({
1365
+ "src/lib/database-adapter.ts"() {
1366
+ "use strict";
1367
+ VIEW_MAPPINGS = [
1368
+ { view: "memories", source: "memory.memory_records" },
1369
+ { view: "tasks", source: "memory.tasks" },
1370
+ { view: "behaviors", source: "memory.behaviors" },
1371
+ { view: "entities", source: "memory.entities" },
1372
+ { view: "relationships", source: "memory.relationships" },
1373
+ { view: "entity_memories", source: "memory.entity_memories" },
1374
+ { view: "entity_aliases", source: "memory.entity_aliases" },
1375
+ { view: "notifications", source: "memory.notifications" },
1376
+ { view: "messages", source: "memory.messages" },
1377
+ { view: "users", source: "wiki.users" },
1378
+ { view: "workspaces", source: "wiki.workspaces" },
1379
+ { view: "workspace_users", source: "wiki.workspace_users" },
1380
+ { view: "documents", source: "wiki.workspace_documents" },
1381
+ { view: "chats", source: "wiki.workspace_chats" }
1382
+ ];
1383
+ UPSERT_KEYS = {
1384
+ memories: ["id"],
1385
+ tasks: ["id"],
1386
+ behaviors: ["id"],
1387
+ entities: ["id"],
1388
+ relationships: ["id"],
1389
+ entity_aliases: ["alias"],
1390
+ notifications: ["id"],
1391
+ messages: ["id"],
1392
+ users: ["id"],
1393
+ workspaces: ["id"],
1394
+ workspace_users: ["id"],
1395
+ documents: ["id"],
1396
+ chats: ["id"]
1397
+ };
1398
+ BOOLEAN_COLUMNS_BY_TABLE = {
1399
+ memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
1400
+ behaviors: /* @__PURE__ */ new Set(["active"]),
1401
+ notifications: /* @__PURE__ */ new Set(["read"]),
1402
+ users: /* @__PURE__ */ new Set(["has_personal_memory"])
1403
+ };
1404
+ BOOLEAN_COLUMN_NAMES = new Set(
1405
+ Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
1406
+ );
1407
+ IMMEDIATE_FALLBACK_PATTERNS = [
1408
+ /\bPRAGMA\b/i,
1409
+ /\bsqlite_master\b/i,
1410
+ /(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
1411
+ /\bMATCH\b/i,
1412
+ /\bvector_distance_cos\s*\(/i,
1413
+ /\bjson_extract\s*\(/i,
1414
+ /\bjulianday\s*\(/i,
1415
+ /\bstrftime\s*\(/i,
1416
+ /\blast_insert_rowid\s*\(/i
1417
+ ];
1418
+ prismaClientPromise = null;
1419
+ compatibilityBootstrapPromise = null;
1420
+ }
1421
+ });
1422
+
1423
+ // src/lib/daemon-auth.ts
1424
+ import crypto from "crypto";
1425
+ import path8 from "path";
1426
+ import { existsSync as existsSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
1427
+ function normalizeToken(token) {
1428
+ if (!token) return null;
1429
+ const trimmed = token.trim();
1430
+ return trimmed.length > 0 ? trimmed : null;
1431
+ }
1432
+ function readDaemonToken() {
1433
+ try {
1434
+ if (!existsSync7(DAEMON_TOKEN_PATH)) return null;
1435
+ return normalizeToken(readFileSync7(DAEMON_TOKEN_PATH, "utf8"));
1436
+ } catch {
1437
+ return null;
1438
+ }
1439
+ }
1440
+ function ensureDaemonToken(seed) {
1441
+ const existing = readDaemonToken();
1442
+ if (existing) return existing;
1443
+ const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
1444
+ ensurePrivateDirSync(EXE_AI_DIR);
1445
+ writeFileSync6(DAEMON_TOKEN_PATH, `${token}
1446
+ `, "utf8");
1447
+ enforcePrivateFileSync(DAEMON_TOKEN_PATH);
1448
+ return token;
1449
+ }
1450
+ var DAEMON_TOKEN_PATH;
1451
+ var init_daemon_auth = __esm({
1452
+ "src/lib/daemon-auth.ts"() {
1453
+ "use strict";
1454
+ init_config();
1455
+ init_secure_files();
1456
+ DAEMON_TOKEN_PATH = path8.join(EXE_AI_DIR, "exed.token");
1457
+ }
1458
+ });
1459
+
797
1460
  // src/lib/exe-daemon-client.ts
798
1461
  import net from "net";
799
- import os5 from "os";
1462
+ import os6 from "os";
800
1463
  import { spawn } from "child_process";
801
1464
  import { randomUUID } from "crypto";
802
- import { existsSync as existsSync6, unlinkSync as unlinkSync3, readFileSync as readFileSync7, openSync, closeSync, statSync } from "fs";
803
- import path7 from "path";
1465
+ import { existsSync as existsSync8, unlinkSync as unlinkSync3, readFileSync as readFileSync8, openSync, closeSync, statSync } from "fs";
1466
+ import path9 from "path";
804
1467
  import { fileURLToPath } from "url";
805
1468
  function handleData(chunk) {
806
1469
  _buffer += chunk.toString();
@@ -828,9 +1491,9 @@ function handleData(chunk) {
828
1491
  }
829
1492
  }
830
1493
  function cleanupStaleFiles() {
831
- if (existsSync6(PID_PATH)) {
1494
+ if (existsSync8(PID_PATH)) {
832
1495
  try {
833
- const pid = parseInt(readFileSync7(PID_PATH, "utf8").trim(), 10);
1496
+ const pid = parseInt(readFileSync8(PID_PATH, "utf8").trim(), 10);
834
1497
  if (pid > 0) {
835
1498
  try {
836
1499
  process.kill(pid, 0);
@@ -851,17 +1514,17 @@ function cleanupStaleFiles() {
851
1514
  }
852
1515
  }
853
1516
  function findPackageRoot() {
854
- let dir = path7.dirname(fileURLToPath(import.meta.url));
855
- const { root } = path7.parse(dir);
1517
+ let dir = path9.dirname(fileURLToPath(import.meta.url));
1518
+ const { root } = path9.parse(dir);
856
1519
  while (dir !== root) {
857
- if (existsSync6(path7.join(dir, "package.json"))) return dir;
858
- dir = path7.dirname(dir);
1520
+ if (existsSync8(path9.join(dir, "package.json"))) return dir;
1521
+ dir = path9.dirname(dir);
859
1522
  }
860
1523
  return null;
861
1524
  }
862
1525
  function spawnDaemon() {
863
- const freeGB = os5.freemem() / (1024 * 1024 * 1024);
864
- const totalGB = os5.totalmem() / (1024 * 1024 * 1024);
1526
+ const freeGB = os6.freemem() / (1024 * 1024 * 1024);
1527
+ const totalGB = os6.totalmem() / (1024 * 1024 * 1024);
865
1528
  if (totalGB <= 8) {
866
1529
  process.stderr.write(
867
1530
  `[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
@@ -881,16 +1544,17 @@ function spawnDaemon() {
881
1544
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
882
1545
  return;
883
1546
  }
884
- const daemonPath = path7.join(pkgRoot, "dist", "lib", "exe-daemon.js");
885
- if (!existsSync6(daemonPath)) {
1547
+ const daemonPath = path9.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1548
+ if (!existsSync8(daemonPath)) {
886
1549
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
887
1550
  `);
888
1551
  return;
889
1552
  }
890
1553
  const resolvedPath = daemonPath;
1554
+ const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
891
1555
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
892
1556
  `);
893
- const logPath = path7.join(path7.dirname(SOCKET_PATH), "exed.log");
1557
+ const logPath = path9.join(path9.dirname(SOCKET_PATH), "exed.log");
894
1558
  let stderrFd = "ignore";
895
1559
  try {
896
1560
  stderrFd = openSync(logPath, "a");
@@ -908,7 +1572,8 @@ function spawnDaemon() {
908
1572
  TMUX_PANE: void 0,
909
1573
  // Prevents resolveExeSession() from scoping to one session
910
1574
  EXE_DAEMON_SOCK: SOCKET_PATH,
911
- EXE_DAEMON_PID: PID_PATH
1575
+ EXE_DAEMON_PID: PID_PATH,
1576
+ [DAEMON_TOKEN_ENV]: daemonToken
912
1577
  }
913
1578
  });
914
1579
  child.unref();
@@ -1015,13 +1680,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
1015
1680
  return;
1016
1681
  }
1017
1682
  const id = randomUUID();
1683
+ const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
1018
1684
  const timer = setTimeout(() => {
1019
1685
  _pending.delete(id);
1020
1686
  resolve({ error: "Request timeout" });
1021
1687
  }, timeoutMs);
1022
1688
  _pending.set(id, { resolve, timer });
1023
1689
  try {
1024
- _socket.write(JSON.stringify({ id, ...payload }) + "\n");
1690
+ _socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
1025
1691
  } catch {
1026
1692
  clearTimeout(timer);
1027
1693
  _pending.delete(id);
@@ -1032,17 +1698,19 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
1032
1698
  function isClientConnected() {
1033
1699
  return _connected;
1034
1700
  }
1035
- var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
1701
+ 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;
1036
1702
  var init_exe_daemon_client = __esm({
1037
1703
  "src/lib/exe-daemon-client.ts"() {
1038
1704
  "use strict";
1039
1705
  init_config();
1040
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path7.join(EXE_AI_DIR, "exed.sock");
1041
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path7.join(EXE_AI_DIR, "exed.pid");
1042
- SPAWN_LOCK_PATH = path7.join(EXE_AI_DIR, "exed-spawn.lock");
1706
+ init_daemon_auth();
1707
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path9.join(EXE_AI_DIR, "exed.sock");
1708
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path9.join(EXE_AI_DIR, "exed.pid");
1709
+ SPAWN_LOCK_PATH = path9.join(EXE_AI_DIR, "exed-spawn.lock");
1043
1710
  SPAWN_LOCK_STALE_MS = 3e4;
1044
1711
  CONNECT_TIMEOUT_MS = 15e3;
1045
1712
  REQUEST_TIMEOUT_MS = 3e4;
1713
+ DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
1046
1714
  _socket = null;
1047
1715
  _connected = false;
1048
1716
  _buffer = "";
@@ -1121,7 +1789,7 @@ __export(db_daemon_client_exports, {
1121
1789
  createDaemonDbClient: () => createDaemonDbClient,
1122
1790
  initDaemonDbClient: () => initDaemonDbClient
1123
1791
  });
1124
- function normalizeStatement(stmt) {
1792
+ function normalizeStatement2(stmt) {
1125
1793
  if (typeof stmt === "string") {
1126
1794
  return { sql: stmt, args: [] };
1127
1795
  }
@@ -1145,7 +1813,7 @@ function createDaemonDbClient(fallbackClient) {
1145
1813
  if (!_useDaemon || !isClientConnected()) {
1146
1814
  return fallbackClient.execute(stmt);
1147
1815
  }
1148
- const { sql, args } = normalizeStatement(stmt);
1816
+ const { sql, args } = normalizeStatement2(stmt);
1149
1817
  const response = await sendDaemonRequest({
1150
1818
  type: "db-execute",
1151
1819
  sql,
@@ -1170,7 +1838,7 @@ function createDaemonDbClient(fallbackClient) {
1170
1838
  if (!_useDaemon || !isClientConnected()) {
1171
1839
  return fallbackClient.batch(stmts, mode);
1172
1840
  }
1173
- const statements = stmts.map(normalizeStatement);
1841
+ const statements = stmts.map(normalizeStatement2);
1174
1842
  const response = await sendDaemonRequest({
1175
1843
  type: "db-batch",
1176
1844
  statements,
@@ -1265,6 +1933,18 @@ __export(database_exports, {
1265
1933
  });
1266
1934
  import { createClient } from "@libsql/client";
1267
1935
  async function initDatabase(config) {
1936
+ if (_walCheckpointTimer) {
1937
+ clearInterval(_walCheckpointTimer);
1938
+ _walCheckpointTimer = null;
1939
+ }
1940
+ if (_daemonClient) {
1941
+ _daemonClient.close();
1942
+ _daemonClient = null;
1943
+ }
1944
+ if (_adapterClient && _adapterClient !== _resilientClient) {
1945
+ _adapterClient.close();
1946
+ }
1947
+ _adapterClient = null;
1268
1948
  if (_client) {
1269
1949
  _client.close();
1270
1950
  _client = null;
@@ -1278,6 +1958,7 @@ async function initDatabase(config) {
1278
1958
  }
1279
1959
  _client = createClient(opts);
1280
1960
  _resilientClient = wrapWithRetry(_client);
1961
+ _adapterClient = _resilientClient;
1281
1962
  _client.execute("PRAGMA busy_timeout = 30000").catch(() => {
1282
1963
  });
1283
1964
  _client.execute("PRAGMA journal_mode = WAL").catch(() => {
@@ -1288,14 +1969,20 @@ async function initDatabase(config) {
1288
1969
  });
1289
1970
  }, 3e4);
1290
1971
  _walCheckpointTimer.unref();
1972
+ if (process.env.DATABASE_URL) {
1973
+ _adapterClient = await createPrismaDbAdapter(_resilientClient);
1974
+ }
1291
1975
  }
1292
1976
  function isInitialized() {
1293
- return _client !== null;
1977
+ return _adapterClient !== null || _client !== null;
1294
1978
  }
1295
1979
  function getClient() {
1296
- if (!_resilientClient) {
1980
+ if (!_adapterClient) {
1297
1981
  throw new Error("Database client not initialized. Call initDatabase() first.");
1298
1982
  }
1983
+ if (process.env.DATABASE_URL) {
1984
+ return _adapterClient;
1985
+ }
1299
1986
  if (process.env.EXE_IS_DAEMON === "1") {
1300
1987
  return _resilientClient;
1301
1988
  }
@@ -1305,6 +1992,7 @@ function getClient() {
1305
1992
  return _resilientClient;
1306
1993
  }
1307
1994
  async function initDaemonClient() {
1995
+ if (process.env.DATABASE_URL) return;
1308
1996
  if (process.env.EXE_IS_DAEMON === "1") return;
1309
1997
  if (!_resilientClient) return;
1310
1998
  try {
@@ -1601,6 +2289,7 @@ async function ensureSchema() {
1601
2289
  project TEXT NOT NULL,
1602
2290
  summary TEXT NOT NULL,
1603
2291
  task_file TEXT,
2292
+ session_scope TEXT,
1604
2293
  read INTEGER NOT NULL DEFAULT 0,
1605
2294
  created_at TEXT NOT NULL
1606
2295
  );
@@ -1609,7 +2298,7 @@ async function ensureSchema() {
1609
2298
  ON notifications(read);
1610
2299
 
1611
2300
  CREATE INDEX IF NOT EXISTS idx_notifications_agent
1612
- ON notifications(agent_id);
2301
+ ON notifications(agent_id, session_scope);
1613
2302
 
1614
2303
  CREATE INDEX IF NOT EXISTS idx_notifications_task_file
1615
2304
  ON notifications(task_file);
@@ -1647,6 +2336,7 @@ async function ensureSchema() {
1647
2336
  target_agent TEXT NOT NULL,
1648
2337
  target_project TEXT,
1649
2338
  target_device TEXT NOT NULL DEFAULT 'local',
2339
+ session_scope TEXT,
1650
2340
  content TEXT NOT NULL,
1651
2341
  priority TEXT DEFAULT 'normal',
1652
2342
  status TEXT DEFAULT 'pending',
@@ -1660,10 +2350,31 @@ async function ensureSchema() {
1660
2350
  );
1661
2351
 
1662
2352
  CREATE INDEX IF NOT EXISTS idx_messages_target
1663
- ON messages(target_agent, status);
2353
+ ON messages(target_agent, session_scope, status);
1664
2354
 
1665
2355
  CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
1666
- ON messages(target_agent, from_agent, server_seq);
2356
+ ON messages(target_agent, session_scope, from_agent, server_seq);
2357
+ `);
2358
+ try {
2359
+ await client.execute({
2360
+ sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
2361
+ args: []
2362
+ });
2363
+ } catch {
2364
+ }
2365
+ try {
2366
+ await client.execute({
2367
+ sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
2368
+ args: []
2369
+ });
2370
+ } catch {
2371
+ }
2372
+ await client.executeMultiple(`
2373
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
2374
+ ON notifications(agent_id, session_scope, read, created_at);
2375
+
2376
+ CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
2377
+ ON messages(target_agent, session_scope, status, created_at);
1667
2378
  `);
1668
2379
  try {
1669
2380
  await client.execute({
@@ -2247,46 +2958,66 @@ async function ensureSchema() {
2247
2958
  } catch {
2248
2959
  }
2249
2960
  }
2961
+ try {
2962
+ await client.execute({
2963
+ sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
2964
+ args: []
2965
+ });
2966
+ } catch {
2967
+ }
2250
2968
  }
2251
2969
  async function disposeDatabase() {
2970
+ if (_walCheckpointTimer) {
2971
+ clearInterval(_walCheckpointTimer);
2972
+ _walCheckpointTimer = null;
2973
+ }
2252
2974
  if (_daemonClient) {
2253
2975
  _daemonClient.close();
2254
2976
  _daemonClient = null;
2255
2977
  }
2978
+ if (_adapterClient && _adapterClient !== _resilientClient) {
2979
+ _adapterClient.close();
2980
+ }
2981
+ _adapterClient = null;
2256
2982
  if (_client) {
2257
2983
  _client.close();
2258
2984
  _client = null;
2259
2985
  _resilientClient = null;
2260
2986
  }
2261
2987
  }
2262
- var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso, disposeTurso;
2988
+ var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
2263
2989
  var init_database = __esm({
2264
2990
  "src/lib/database.ts"() {
2265
2991
  "use strict";
2266
2992
  init_db_retry();
2267
2993
  init_employees();
2994
+ init_database_adapter();
2268
2995
  _client = null;
2269
2996
  _resilientClient = null;
2270
2997
  _walCheckpointTimer = null;
2271
2998
  _daemonClient = null;
2999
+ _adapterClient = null;
2272
3000
  initTurso = initDatabase;
2273
3001
  disposeTurso = disposeDatabase;
2274
3002
  }
2275
3003
  });
2276
3004
 
2277
3005
  // src/lib/license.ts
2278
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync7, mkdirSync as mkdirSync5 } from "fs";
3006
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync7, existsSync as existsSync9, mkdirSync as mkdirSync5 } from "fs";
2279
3007
  import { randomUUID as randomUUID2 } from "crypto";
2280
- import path8 from "path";
3008
+ import { createRequire as createRequire2 } from "module";
3009
+ import { pathToFileURL as pathToFileURL2 } from "url";
3010
+ import os7 from "os";
3011
+ import path10 from "path";
2281
3012
  import { jwtVerify, importSPKI } from "jose";
2282
3013
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
2283
3014
  var init_license = __esm({
2284
3015
  "src/lib/license.ts"() {
2285
3016
  "use strict";
2286
3017
  init_config();
2287
- LICENSE_PATH = path8.join(EXE_AI_DIR, "license.key");
2288
- CACHE_PATH = path8.join(EXE_AI_DIR, "license-cache.json");
2289
- DEVICE_ID_PATH = path8.join(EXE_AI_DIR, "device-id");
3018
+ LICENSE_PATH = path10.join(EXE_AI_DIR, "license.key");
3019
+ CACHE_PATH = path10.join(EXE_AI_DIR, "license-cache.json");
3020
+ DEVICE_ID_PATH = path10.join(EXE_AI_DIR, "device-id");
2290
3021
  PLAN_LIMITS = {
2291
3022
  free: { devices: 1, employees: 1, memories: 5e3 },
2292
3023
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -2298,12 +3029,12 @@ var init_license = __esm({
2298
3029
  });
2299
3030
 
2300
3031
  // src/lib/plan-limits.ts
2301
- import { readFileSync as readFileSync9, existsSync as existsSync8 } from "fs";
2302
- import path9 from "path";
3032
+ import { readFileSync as readFileSync10, existsSync as existsSync10 } from "fs";
3033
+ import path11 from "path";
2303
3034
  function getLicenseSync() {
2304
3035
  try {
2305
- if (!existsSync8(CACHE_PATH2)) return freeLicense();
2306
- const raw = JSON.parse(readFileSync9(CACHE_PATH2, "utf8"));
3036
+ if (!existsSync10(CACHE_PATH2)) return freeLicense();
3037
+ const raw = JSON.parse(readFileSync10(CACHE_PATH2, "utf8"));
2307
3038
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
2308
3039
  const parts = raw.token.split(".");
2309
3040
  if (parts.length !== 3) return freeLicense();
@@ -2341,8 +3072,8 @@ function assertEmployeeLimitSync(rosterPath) {
2341
3072
  const filePath = rosterPath ?? EMPLOYEES_PATH;
2342
3073
  let count = 0;
2343
3074
  try {
2344
- if (existsSync8(filePath)) {
2345
- const raw = readFileSync9(filePath, "utf8");
3075
+ if (existsSync10(filePath)) {
3076
+ const raw = readFileSync10(filePath, "utf8");
2346
3077
  const employees = JSON.parse(raw);
2347
3078
  count = Array.isArray(employees) ? employees.length : 0;
2348
3079
  }
@@ -2371,7 +3102,7 @@ var init_plan_limits = __esm({
2371
3102
  this.name = "PlanLimitError";
2372
3103
  }
2373
3104
  };
2374
- CACHE_PATH2 = path9.join(EXE_AI_DIR, "license-cache.json");
3105
+ CACHE_PATH2 = path11.join(EXE_AI_DIR, "license-cache.json");
2375
3106
  }
2376
3107
  });
2377
3108
 
@@ -2387,24 +3118,25 @@ __export(notifications_exports, {
2387
3118
  readUnreadNotifications: () => readUnreadNotifications,
2388
3119
  writeNotification: () => writeNotification
2389
3120
  });
2390
- import crypto from "crypto";
2391
- import path10 from "path";
2392
- import os6 from "os";
3121
+ import crypto2 from "crypto";
3122
+ import path12 from "path";
3123
+ import os8 from "os";
2393
3124
  import {
2394
- readFileSync as readFileSync10,
3125
+ readFileSync as readFileSync11,
2395
3126
  readdirSync as readdirSync2,
2396
3127
  unlinkSync as unlinkSync4,
2397
- existsSync as existsSync9,
3128
+ existsSync as existsSync11,
2398
3129
  rmdirSync
2399
3130
  } from "fs";
2400
3131
  async function writeNotification(notification) {
2401
3132
  try {
2402
3133
  const client = getClient();
2403
- const id = crypto.randomUUID();
3134
+ const id = crypto2.randomUUID();
2404
3135
  const now = (/* @__PURE__ */ new Date()).toISOString();
3136
+ const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
2405
3137
  await client.execute({
2406
- sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
2407
- VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
3138
+ sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
3139
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
2408
3140
  args: [
2409
3141
  id,
2410
3142
  notification.agentId,
@@ -2413,6 +3145,7 @@ async function writeNotification(notification) {
2413
3145
  notification.project,
2414
3146
  notification.summary,
2415
3147
  notification.taskFile ?? null,
3148
+ sessionScope,
2416
3149
  now
2417
3150
  ]
2418
3151
  });
@@ -2421,21 +3154,22 @@ async function writeNotification(notification) {
2421
3154
  `);
2422
3155
  }
2423
3156
  }
2424
- async function readUnreadNotifications(agentFilter) {
3157
+ async function readUnreadNotifications(agentFilter, sessionScope) {
2425
3158
  try {
2426
3159
  const client = getClient();
2427
3160
  const conditions = ["read = 0"];
2428
3161
  const args = [];
3162
+ const scope = strictSessionScopeFilter(sessionScope);
2429
3163
  if (agentFilter) {
2430
3164
  conditions.push("agent_id = ?");
2431
3165
  args.push(agentFilter);
2432
3166
  }
2433
3167
  const result = await client.execute({
2434
- sql: `SELECT id, agent_id, agent_role, event, project, summary, task_file, created_at
3168
+ sql: `SELECT id, agent_id, agent_role, event, project, summary, task_file, session_scope, created_at
2435
3169
  FROM notifications
2436
- WHERE ${conditions.join(" AND ")}
3170
+ WHERE ${conditions.join(" AND ")}${scope.sql}
2437
3171
  ORDER BY created_at ASC`,
2438
- args
3172
+ args: [...args, ...scope.args]
2439
3173
  });
2440
3174
  return result.rows.map((r) => ({
2441
3175
  id: String(r.id),
@@ -2445,6 +3179,7 @@ async function readUnreadNotifications(agentFilter) {
2445
3179
  project: String(r.project),
2446
3180
  summary: String(r.summary),
2447
3181
  taskFile: r.task_file ? String(r.task_file) : void 0,
3182
+ sessionScope: r.session_scope == null ? null : String(r.session_scope),
2448
3183
  timestamp: String(r.created_at),
2449
3184
  read: false
2450
3185
  }));
@@ -2452,54 +3187,60 @@ async function readUnreadNotifications(agentFilter) {
2452
3187
  return [];
2453
3188
  }
2454
3189
  }
2455
- async function markAsRead(ids) {
3190
+ async function markAsRead(ids, sessionScope) {
2456
3191
  if (ids.length === 0) return;
2457
3192
  try {
2458
3193
  const client = getClient();
2459
3194
  const placeholders = ids.map(() => "?").join(", ");
3195
+ const scope = strictSessionScopeFilter(sessionScope);
2460
3196
  await client.execute({
2461
- sql: `UPDATE notifications SET read = 1 WHERE id IN (${placeholders})`,
2462
- args: ids
3197
+ sql: `UPDATE notifications SET read = 1 WHERE id IN (${placeholders})${scope.sql}`,
3198
+ args: [...ids, ...scope.args]
2463
3199
  });
2464
3200
  } catch {
2465
3201
  }
2466
3202
  }
2467
- async function markAsReadByTaskFile(taskFile) {
3203
+ async function markAsReadByTaskFile(taskFile, sessionScope) {
2468
3204
  try {
2469
3205
  const client = getClient();
3206
+ const scope = strictSessionScopeFilter(sessionScope);
2470
3207
  await client.execute({
2471
- sql: "UPDATE notifications SET read = 1 WHERE task_file = ? AND read = 0",
2472
- args: [taskFile]
3208
+ sql: `UPDATE notifications SET read = 1
3209
+ WHERE task_file = ? AND read = 0${scope.sql}`,
3210
+ args: [taskFile, ...scope.args]
2473
3211
  });
2474
3212
  } catch {
2475
3213
  }
2476
3214
  }
2477
- async function cleanupOldNotifications(daysOld = CLEANUP_DAYS) {
3215
+ async function cleanupOldNotifications(daysOld = CLEANUP_DAYS, sessionScope) {
2478
3216
  try {
2479
3217
  const client = getClient();
2480
3218
  const cutoff = new Date(
2481
3219
  Date.now() - daysOld * 24 * 60 * 60 * 1e3
2482
3220
  ).toISOString();
3221
+ const scope = strictSessionScopeFilter(sessionScope);
2483
3222
  const result = await client.execute({
2484
- sql: "DELETE FROM notifications WHERE created_at < ?",
2485
- args: [cutoff]
3223
+ sql: `DELETE FROM notifications WHERE created_at < ?${scope.sql}`,
3224
+ args: [cutoff, ...scope.args]
2486
3225
  });
2487
3226
  return result.rowsAffected;
2488
3227
  } catch {
2489
3228
  return 0;
2490
3229
  }
2491
3230
  }
2492
- async function markDoneTaskNotificationsAsRead() {
3231
+ async function markDoneTaskNotificationsAsRead(sessionScope) {
2493
3232
  try {
2494
3233
  const client = getClient();
3234
+ const scope = strictSessionScopeFilter(sessionScope);
2495
3235
  const result = await client.execute({
2496
3236
  sql: `UPDATE notifications SET read = 1
2497
3237
  WHERE read = 0
2498
3238
  AND task_file IS NOT NULL
3239
+ ${scope.sql}
2499
3240
  AND task_file IN (
2500
- SELECT task_file FROM tasks WHERE status = 'done'
3241
+ SELECT task_file FROM tasks WHERE status = 'done'${scope.sql}
2501
3242
  )`,
2502
- args: []
3243
+ args: [...scope.args, ...scope.args]
2503
3244
  });
2504
3245
  return result.rowsAffected;
2505
3246
  } catch {
@@ -2530,9 +3271,9 @@ function formatNotifications(notifications) {
2530
3271
  return lines.join("\n");
2531
3272
  }
2532
3273
  async function migrateJsonNotifications() {
2533
- const base = process.env.EXE_OS_DIR || process.env.EXE_MEM_DIR || path10.join(os6.homedir(), ".exe-os");
2534
- const notifDir = path10.join(base, "notifications");
2535
- if (!existsSync9(notifDir)) return 0;
3274
+ const base = process.env.EXE_OS_DIR || process.env.EXE_MEM_DIR || path12.join(os8.homedir(), ".exe-os");
3275
+ const notifDir = path12.join(base, "notifications");
3276
+ if (!existsSync11(notifDir)) return 0;
2536
3277
  let migrated = 0;
2537
3278
  try {
2538
3279
  const files = readdirSync2(notifDir).filter((f) => f.endsWith(".json"));
@@ -2540,19 +3281,20 @@ async function migrateJsonNotifications() {
2540
3281
  const client = getClient();
2541
3282
  for (const file of files) {
2542
3283
  try {
2543
- const filePath = path10.join(notifDir, file);
2544
- const data = JSON.parse(readFileSync10(filePath, "utf8"));
3284
+ const filePath = path12.join(notifDir, file);
3285
+ const data = JSON.parse(readFileSync11(filePath, "utf8"));
2545
3286
  await client.execute({
2546
- sql: `INSERT OR IGNORE INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
2547
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
3287
+ sql: `INSERT OR IGNORE INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
3288
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
2548
3289
  args: [
2549
- crypto.randomUUID(),
3290
+ crypto2.randomUUID(),
2550
3291
  data.agentId ?? "unknown",
2551
3292
  data.agentRole ?? "unknown",
2552
3293
  data.event ?? "session_summary",
2553
3294
  data.project ?? "unknown",
2554
3295
  data.summary ?? "",
2555
3296
  data.taskFile ?? null,
3297
+ null,
2556
3298
  data.read ? 1 : 0,
2557
3299
  data.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
2558
3300
  ]
@@ -2606,12 +3348,13 @@ var init_notifications = __esm({
2606
3348
  "src/lib/notifications.ts"() {
2607
3349
  "use strict";
2608
3350
  init_database();
3351
+ init_task_scope();
2609
3352
  CLEANUP_DAYS = 7;
2610
3353
  }
2611
3354
  });
2612
3355
 
2613
3356
  // src/lib/session-kill-telemetry.ts
2614
- import crypto2 from "crypto";
3357
+ import crypto3 from "crypto";
2615
3358
  async function recordSessionKill(input2) {
2616
3359
  try {
2617
3360
  const client = getClient();
@@ -2621,7 +3364,7 @@ async function recordSessionKill(input2) {
2621
3364
  ticks_idle, estimated_tokens_saved)
2622
3365
  VALUES (?, ?, ?, ?, ?, ?, ?)`,
2623
3366
  args: [
2624
- crypto2.randomUUID(),
3367
+ crypto3.randomUUID(),
2625
3368
  input2.sessionName,
2626
3369
  input2.agentId,
2627
3370
  (/* @__PURE__ */ new Date()).toISOString(),
@@ -2716,12 +3459,12 @@ __export(tasks_crud_exports, {
2716
3459
  updateTaskStatus: () => updateTaskStatus,
2717
3460
  writeCheckpoint: () => writeCheckpoint
2718
3461
  });
2719
- import crypto3 from "crypto";
2720
- import path11 from "path";
2721
- import os7 from "os";
3462
+ import crypto4 from "crypto";
3463
+ import path13 from "path";
3464
+ import os9 from "os";
2722
3465
  import { execSync as execSync5 } from "child_process";
2723
3466
  import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
2724
- import { existsSync as existsSync10, readFileSync as readFileSync11 } from "fs";
3467
+ import { existsSync as existsSync12, readFileSync as readFileSync12 } from "fs";
2725
3468
  async function writeCheckpoint(input2) {
2726
3469
  const client = getClient();
2727
3470
  const row = await resolveTask(client, input2.taskId);
@@ -2837,7 +3580,7 @@ async function resolveTask(client, identifier, scopeSession) {
2837
3580
  }
2838
3581
  async function createTaskCore(input2) {
2839
3582
  const client = getClient();
2840
- const id = crypto3.randomUUID();
3583
+ const id = crypto4.randomUUID();
2841
3584
  const now = (/* @__PURE__ */ new Date()).toISOString();
2842
3585
  const slug = slugify(input2.title);
2843
3586
  let earlySessionScope = null;
@@ -2896,8 +3639,8 @@ ${laneWarning}` : laneWarning;
2896
3639
  }
2897
3640
  if (input2.baseDir) {
2898
3641
  try {
2899
- await mkdir3(path11.join(input2.baseDir, "exe", "output"), { recursive: true });
2900
- await mkdir3(path11.join(input2.baseDir, "exe", "research"), { recursive: true });
3642
+ await mkdir3(path13.join(input2.baseDir, "exe", "output"), { recursive: true });
3643
+ await mkdir3(path13.join(input2.baseDir, "exe", "research"), { recursive: true });
2901
3644
  await ensureArchitectureDoc(input2.baseDir, input2.projectName);
2902
3645
  await ensureGitignoreExe(input2.baseDir);
2903
3646
  } catch {
@@ -2933,13 +3676,19 @@ ${laneWarning}` : laneWarning;
2933
3676
  });
2934
3677
  if (input2.baseDir) {
2935
3678
  try {
2936
- const EXE_OS_DIR = path11.join(os7.homedir(), ".exe-os");
2937
- const mdPath = path11.join(EXE_OS_DIR, taskFile);
2938
- const mdDir = path11.dirname(mdPath);
2939
- if (!existsSync10(mdDir)) await mkdir3(mdDir, { recursive: true });
3679
+ const EXE_OS_DIR = path13.join(os9.homedir(), ".exe-os");
3680
+ const mdPath = path13.join(EXE_OS_DIR, taskFile);
3681
+ const mdDir = path13.dirname(mdPath);
3682
+ if (!existsSync12(mdDir)) await mkdir3(mdDir, { recursive: true });
2940
3683
  const reviewer = input2.reviewer ?? input2.assignedBy;
2941
3684
  const mdContent = `# ${input2.title}
2942
3685
 
3686
+ ## MANDATORY: When done
3687
+
3688
+ You MUST call update_task with status "done" and a result summary when finished.
3689
+ If you skip this, your reviewer will not know you're done and your work won't be reviewed.
3690
+ Do NOT let a failed commit or any error prevent you from calling update_task(done).
3691
+
2943
3692
  **ID:** ${id}
2944
3693
  **Status:** ${initialStatus}
2945
3694
  **Priority:** ${input2.priority}
@@ -2953,12 +3702,6 @@ ${laneWarning}` : laneWarning;
2953
3702
  ## Context
2954
3703
 
2955
3704
  ${input2.context}
2956
-
2957
- ## MANDATORY: When done
2958
-
2959
- You MUST call update_task with status "done" and a result summary when finished.
2960
- If you skip this, your reviewer will not know you're done and your work won't be reviewed.
2961
- Do NOT let a failed commit or any error prevent you from calling update_task(done).
2962
3705
  `;
2963
3706
  await writeFile3(mdPath, mdContent, "utf-8");
2964
3707
  } catch (err) {
@@ -3207,7 +3950,7 @@ ${input2.result}` : `\u26A0\uFE0F ${warning}`;
3207
3950
  await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
3208
3951
  } catch {
3209
3952
  }
3210
- if (input2.status === "done" || input2.status === "cancelled") {
3953
+ if (input2.status === "done" || input2.status === "cancelled" || input2.status === "closed") {
3211
3954
  try {
3212
3955
  const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
3213
3956
  clearQueueForAgent2(String(row.assigned_to));
@@ -3236,9 +3979,9 @@ async function deleteTaskCore(taskId, _baseDir) {
3236
3979
  return { taskFile, assignedTo, assignedBy, taskSlug };
3237
3980
  }
3238
3981
  async function ensureArchitectureDoc(baseDir, projectName) {
3239
- const archPath = path11.join(baseDir, "exe", "ARCHITECTURE.md");
3982
+ const archPath = path13.join(baseDir, "exe", "ARCHITECTURE.md");
3240
3983
  try {
3241
- if (existsSync10(archPath)) return;
3984
+ if (existsSync12(archPath)) return;
3242
3985
  const template = [
3243
3986
  `# ${projectName} \u2014 System Architecture`,
3244
3987
  "",
@@ -3271,10 +4014,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
3271
4014
  }
3272
4015
  }
3273
4016
  async function ensureGitignoreExe(baseDir) {
3274
- const gitignorePath = path11.join(baseDir, ".gitignore");
4017
+ const gitignorePath = path13.join(baseDir, ".gitignore");
3275
4018
  try {
3276
- if (existsSync10(gitignorePath)) {
3277
- const content = readFileSync11(gitignorePath, "utf-8");
4019
+ if (existsSync12(gitignorePath)) {
4020
+ const content = readFileSync12(gitignorePath, "utf-8");
3278
4021
  if (/^\/?exe\/?$/m.test(content)) return;
3279
4022
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
3280
4023
  } else {
@@ -3305,58 +4048,42 @@ var init_tasks_crud = __esm({
3305
4048
  });
3306
4049
 
3307
4050
  // src/lib/tasks-review.ts
3308
- import path12 from "path";
3309
- import { existsSync as existsSync11, readdirSync as readdirSync3, unlinkSync as unlinkSync5 } from "fs";
4051
+ import path14 from "path";
4052
+ import { existsSync as existsSync13, readdirSync as readdirSync3, unlinkSync as unlinkSync5 } from "fs";
3310
4053
  async function countPendingReviews(sessionScope) {
3311
4054
  const client = getClient();
3312
- if (sessionScope) {
3313
- const result2 = await client.execute({
3314
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review' AND (session_scope = ? OR session_scope IS NULL)",
3315
- args: [sessionScope]
3316
- });
3317
- return Number(result2.rows[0]?.cnt) || 0;
3318
- }
4055
+ const scope = strictSessionScopeFilter(
4056
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
4057
+ );
3319
4058
  const result = await client.execute({
3320
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review'",
3321
- args: []
4059
+ sql: `SELECT COUNT(*) as cnt FROM tasks
4060
+ WHERE status = 'needs_review'${scope.sql}`,
4061
+ args: [...scope.args]
3322
4062
  });
3323
4063
  return Number(result.rows[0]?.cnt) || 0;
3324
4064
  }
3325
4065
  async function countNewPendingReviewsSince(sinceIso, sessionScope) {
3326
4066
  const client = getClient();
3327
- if (sessionScope) {
3328
- const result2 = await client.execute({
3329
- sql: `SELECT COUNT(*) as cnt FROM tasks
3330
- WHERE status = 'needs_review' AND updated_at > ?
3331
- AND session_scope = ?`,
3332
- args: [sinceIso, sessionScope]
3333
- });
3334
- return Number(result2.rows[0]?.cnt) || 0;
3335
- }
4067
+ const scope = strictSessionScopeFilter(
4068
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
4069
+ );
3336
4070
  const result = await client.execute({
3337
4071
  sql: `SELECT COUNT(*) as cnt FROM tasks
3338
- WHERE status = 'needs_review' AND updated_at > ?`,
3339
- args: [sinceIso]
4072
+ WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
4073
+ args: [sinceIso, ...scope.args]
3340
4074
  });
3341
4075
  return Number(result.rows[0]?.cnt) || 0;
3342
4076
  }
3343
4077
  async function listPendingReviews(limit, sessionScope) {
3344
4078
  const client = getClient();
3345
- if (sessionScope) {
3346
- const result2 = await client.execute({
3347
- sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
3348
- WHERE status = 'needs_review'
3349
- AND session_scope = ?
3350
- ORDER BY updated_at ASC LIMIT ?`,
3351
- args: [sessionScope, limit]
3352
- });
3353
- return result2.rows;
3354
- }
4079
+ const scope = strictSessionScopeFilter(
4080
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
4081
+ );
3355
4082
  const result = await client.execute({
3356
4083
  sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
3357
- WHERE status = 'needs_review'
4084
+ WHERE status = 'needs_review'${scope.sql}
3358
4085
  ORDER BY updated_at ASC LIMIT ?`,
3359
- args: [limit]
4086
+ args: [...scope.args, limit]
3360
4087
  });
3361
4088
  return result.rows;
3362
4089
  }
@@ -3368,7 +4095,7 @@ async function cleanupOrphanedReviews() {
3368
4095
  WHERE status IN ('open', 'needs_review', 'in_progress')
3369
4096
  AND assigned_by = 'system'
3370
4097
  AND title LIKE 'Review:%'
3371
- AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
4098
+ AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
3372
4099
  args: [now]
3373
4100
  });
3374
4101
  const r1b = await client.execute({
@@ -3487,11 +4214,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
3487
4214
  );
3488
4215
  }
3489
4216
  try {
3490
- const cacheDir = path12.join(EXE_AI_DIR, "session-cache");
3491
- if (existsSync11(cacheDir)) {
4217
+ const cacheDir = path14.join(EXE_AI_DIR, "session-cache");
4218
+ if (existsSync13(cacheDir)) {
3492
4219
  for (const f of readdirSync3(cacheDir)) {
3493
4220
  if (f.startsWith("review-notified-")) {
3494
- unlinkSync5(path12.join(cacheDir, f));
4221
+ unlinkSync5(path14.join(cacheDir, f));
3495
4222
  }
3496
4223
  }
3497
4224
  }
@@ -3508,11 +4235,12 @@ var init_tasks_review = __esm({
3508
4235
  init_tmux_routing();
3509
4236
  init_session_key();
3510
4237
  init_state_bus();
4238
+ init_task_scope();
3511
4239
  }
3512
4240
  });
3513
4241
 
3514
4242
  // src/lib/tasks-chain.ts
3515
- import path13 from "path";
4243
+ import path15 from "path";
3516
4244
  import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
3517
4245
  async function cascadeUnblock(taskId, baseDir, now) {
3518
4246
  const client = getClient();
@@ -3529,7 +4257,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
3529
4257
  });
3530
4258
  for (const ur of unblockedRows.rows) {
3531
4259
  try {
3532
- const ubFile = path13.join(baseDir, String(ur.task_file));
4260
+ const ubFile = path15.join(baseDir, String(ur.task_file));
3533
4261
  let ubContent = await readFile3(ubFile, "utf-8");
3534
4262
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
3535
4263
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -3564,7 +4292,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
3564
4292
  const scScope = sessionScopeFilter();
3565
4293
  const remaining = await client.execute({
3566
4294
  sql: `SELECT COUNT(*) as cnt FROM tasks
3567
- WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
4295
+ WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
3568
4296
  args: [parentTaskId, ...scScope.args]
3569
4297
  });
3570
4298
  const cnt = Number(remaining.rows[0]?.cnt ?? 1);
@@ -3598,7 +4326,7 @@ var init_tasks_chain = __esm({
3598
4326
 
3599
4327
  // src/lib/project-name.ts
3600
4328
  import { execSync as execSync6 } from "child_process";
3601
- import path14 from "path";
4329
+ import path16 from "path";
3602
4330
  function getProjectName(cwd) {
3603
4331
  const dir = cwd ?? process.cwd();
3604
4332
  if (_cached2 && _cachedCwd === dir) return _cached2;
@@ -3611,7 +4339,7 @@ function getProjectName(cwd) {
3611
4339
  timeout: 2e3,
3612
4340
  stdio: ["pipe", "pipe", "pipe"]
3613
4341
  }).trim();
3614
- repoRoot = path14.dirname(gitCommonDir);
4342
+ repoRoot = path16.dirname(gitCommonDir);
3615
4343
  } catch {
3616
4344
  repoRoot = execSync6("git rev-parse --show-toplevel", {
3617
4345
  cwd: dir,
@@ -3620,11 +4348,11 @@ function getProjectName(cwd) {
3620
4348
  stdio: ["pipe", "pipe", "pipe"]
3621
4349
  }).trim();
3622
4350
  }
3623
- _cached2 = path14.basename(repoRoot);
4351
+ _cached2 = path16.basename(repoRoot);
3624
4352
  _cachedCwd = dir;
3625
4353
  return _cached2;
3626
4354
  } catch {
3627
- _cached2 = path14.basename(dir);
4355
+ _cached2 = path16.basename(dir);
3628
4356
  _cachedCwd = dir;
3629
4357
  return _cached2;
3630
4358
  }
@@ -3767,10 +4495,10 @@ var init_tasks_notify = __esm({
3767
4495
  });
3768
4496
 
3769
4497
  // src/lib/behaviors.ts
3770
- import crypto4 from "crypto";
4498
+ import crypto5 from "crypto";
3771
4499
  async function storeBehavior(opts) {
3772
4500
  const client = getClient();
3773
- const id = crypto4.randomUUID();
4501
+ const id = crypto5.randomUUID();
3774
4502
  const now = (/* @__PURE__ */ new Date()).toISOString();
3775
4503
  await client.execute({
3776
4504
  sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
@@ -3799,7 +4527,7 @@ __export(skill_learning_exports, {
3799
4527
  storeTrajectory: () => storeTrajectory,
3800
4528
  sweepTrajectories: () => sweepTrajectories
3801
4529
  });
3802
- import crypto5 from "crypto";
4530
+ import crypto6 from "crypto";
3803
4531
  async function extractTrajectory(taskId, agentId) {
3804
4532
  const client = getClient();
3805
4533
  const result = await client.execute({
@@ -3828,11 +4556,11 @@ async function extractTrajectory(taskId, agentId) {
3828
4556
  return signature;
3829
4557
  }
3830
4558
  function hashSignature(signature) {
3831
- return crypto5.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
4559
+ return crypto6.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
3832
4560
  }
3833
4561
  async function storeTrajectory(opts) {
3834
4562
  const client = getClient();
3835
- const id = crypto5.randomUUID();
4563
+ const id = crypto6.randomUUID();
3836
4564
  const now = (/* @__PURE__ */ new Date()).toISOString();
3837
4565
  const signatureHash = hashSignature(opts.signature);
3838
4566
  await client.execute({
@@ -4097,8 +4825,8 @@ __export(tasks_exports, {
4097
4825
  updateTaskStatus: () => updateTaskStatus,
4098
4826
  writeCheckpoint: () => writeCheckpoint
4099
4827
  });
4100
- import path15 from "path";
4101
- import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, unlinkSync as unlinkSync6 } from "fs";
4828
+ import path17 from "path";
4829
+ import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync6, unlinkSync as unlinkSync6 } from "fs";
4102
4830
  async function createTask(input2) {
4103
4831
  const result = await createTaskCore(input2);
4104
4832
  if (!input2.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -4117,12 +4845,12 @@ async function updateTask(input2) {
4117
4845
  const { row, taskFile, now, taskId } = await updateTaskStatus(input2);
4118
4846
  try {
4119
4847
  const agent = String(row.assigned_to);
4120
- const cacheDir = path15.join(EXE_AI_DIR, "session-cache");
4121
- const cachePath = path15.join(cacheDir, `current-task-${agent}.json`);
4848
+ const cacheDir = path17.join(EXE_AI_DIR, "session-cache");
4849
+ const cachePath = path17.join(cacheDir, `current-task-${agent}.json`);
4122
4850
  if (input2.status === "in_progress") {
4123
4851
  mkdirSync6(cacheDir, { recursive: true });
4124
- writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
4125
- } else if (input2.status === "done" || input2.status === "blocked" || input2.status === "cancelled") {
4852
+ writeFileSync8(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
4853
+ } else if (input2.status === "done" || input2.status === "blocked" || input2.status === "cancelled" || input2.status === "closed") {
4126
4854
  try {
4127
4855
  unlinkSync6(cachePath);
4128
4856
  } catch {
@@ -4130,10 +4858,10 @@ async function updateTask(input2) {
4130
4858
  }
4131
4859
  } catch {
4132
4860
  }
4133
- if (input2.status === "done") {
4861
+ if (input2.status === "done" || input2.status === "closed") {
4134
4862
  await cleanupReviewFile(row, taskFile, input2.baseDir);
4135
4863
  }
4136
- if (input2.status === "done" || input2.status === "cancelled") {
4864
+ if (input2.status === "done" || input2.status === "cancelled" || input2.status === "closed") {
4137
4865
  try {
4138
4866
  const client = getClient();
4139
4867
  const taskTitle = String(row.title);
@@ -4149,7 +4877,7 @@ async function updateTask(input2) {
4149
4877
  if (!isCoordinatorName(assignedAgent)) {
4150
4878
  try {
4151
4879
  const draftClient = getClient();
4152
- if (input2.status === "done") {
4880
+ if (input2.status === "done" || input2.status === "closed") {
4153
4881
  await draftClient.execute({
4154
4882
  sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
4155
4883
  args: [assignedAgent]
@@ -4166,7 +4894,7 @@ async function updateTask(input2) {
4166
4894
  try {
4167
4895
  const client = getClient();
4168
4896
  const cascaded = await client.execute({
4169
- sql: `UPDATE tasks SET status = 'done', updated_at = ?
4897
+ sql: `UPDATE tasks SET status = 'closed', updated_at = ?
4170
4898
  WHERE parent_task_id = ? AND status = 'needs_review'`,
4171
4899
  args: [now, taskId]
4172
4900
  });
@@ -4179,14 +4907,14 @@ async function updateTask(input2) {
4179
4907
  } catch {
4180
4908
  }
4181
4909
  }
4182
- const isTerminal = input2.status === "done" || input2.status === "needs_review";
4910
+ const isTerminal = input2.status === "done" || input2.status === "needs_review" || input2.status === "closed";
4183
4911
  if (isTerminal) {
4184
4912
  const isCoordinator = isCoordinatorName(String(row.assigned_to));
4185
4913
  if (!isCoordinator) {
4186
4914
  notifyTaskDone();
4187
4915
  }
4188
4916
  await markTaskNotificationsRead(taskFile);
4189
- if (input2.status === "done") {
4917
+ if (input2.status === "done" || input2.status === "closed") {
4190
4918
  try {
4191
4919
  await cascadeUnblock(taskId, input2.baseDir, now);
4192
4920
  } catch {
@@ -4206,7 +4934,7 @@ async function updateTask(input2) {
4206
4934
  }
4207
4935
  }
4208
4936
  }
4209
- if (input2.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
4937
+ if ((input2.status === "done" || input2.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
4210
4938
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
4211
4939
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
4212
4940
  taskId,
@@ -4578,6 +5306,7 @@ __export(tmux_routing_exports, {
4578
5306
  isEmployeeAlive: () => isEmployeeAlive,
4579
5307
  isExeSession: () => isExeSession,
4580
5308
  isSessionBusy: () => isSessionBusy,
5309
+ notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
4581
5310
  notifyParentExe: () => notifyParentExe,
4582
5311
  parseParentExe: () => parseParentExe,
4583
5312
  registerParentExe: () => registerParentExe,
@@ -4588,13 +5317,13 @@ __export(tmux_routing_exports, {
4588
5317
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
4589
5318
  });
4590
5319
  import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
4591
- import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, mkdirSync as mkdirSync7, existsSync as existsSync12, appendFileSync, readdirSync as readdirSync4 } from "fs";
4592
- import path16 from "path";
4593
- import os8 from "os";
5320
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync9, mkdirSync as mkdirSync7, existsSync as existsSync14, appendFileSync, readdirSync as readdirSync4 } from "fs";
5321
+ import path18 from "path";
5322
+ import os10 from "os";
4594
5323
  import { fileURLToPath as fileURLToPath2 } from "url";
4595
5324
  import { unlinkSync as unlinkSync7 } from "fs";
4596
5325
  function spawnLockPath(sessionName) {
4597
- return path16.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
5326
+ return path18.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
4598
5327
  }
4599
5328
  function isProcessAlive(pid) {
4600
5329
  try {
@@ -4605,13 +5334,13 @@ function isProcessAlive(pid) {
4605
5334
  }
4606
5335
  }
4607
5336
  function acquireSpawnLock2(sessionName) {
4608
- if (!existsSync12(SPAWN_LOCK_DIR)) {
5337
+ if (!existsSync14(SPAWN_LOCK_DIR)) {
4609
5338
  mkdirSync7(SPAWN_LOCK_DIR, { recursive: true });
4610
5339
  }
4611
5340
  const lockFile = spawnLockPath(sessionName);
4612
- if (existsSync12(lockFile)) {
5341
+ if (existsSync14(lockFile)) {
4613
5342
  try {
4614
- const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
5343
+ const lock = JSON.parse(readFileSync13(lockFile, "utf8"));
4615
5344
  const age = Date.now() - lock.timestamp;
4616
5345
  if (isProcessAlive(lock.pid) && age < 6e4) {
4617
5346
  return false;
@@ -4619,7 +5348,7 @@ function acquireSpawnLock2(sessionName) {
4619
5348
  } catch {
4620
5349
  }
4621
5350
  }
4622
- writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
5351
+ writeFileSync9(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
4623
5352
  return true;
4624
5353
  }
4625
5354
  function releaseSpawnLock2(sessionName) {
@@ -4631,13 +5360,13 @@ function releaseSpawnLock2(sessionName) {
4631
5360
  function resolveBehaviorsExporterScript() {
4632
5361
  try {
4633
5362
  const thisFile = fileURLToPath2(import.meta.url);
4634
- const scriptPath = path16.join(
4635
- path16.dirname(thisFile),
5363
+ const scriptPath = path18.join(
5364
+ path18.dirname(thisFile),
4636
5365
  "..",
4637
5366
  "bin",
4638
5367
  "exe-export-behaviors.js"
4639
5368
  );
4640
- return existsSync12(scriptPath) ? scriptPath : null;
5369
+ return existsSync14(scriptPath) ? scriptPath : null;
4641
5370
  } catch {
4642
5371
  return null;
4643
5372
  }
@@ -4703,12 +5432,12 @@ function extractRootExe(name) {
4703
5432
  return parts.length > 0 ? parts[parts.length - 1] : null;
4704
5433
  }
4705
5434
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
4706
- if (!existsSync12(SESSION_CACHE)) {
5435
+ if (!existsSync14(SESSION_CACHE)) {
4707
5436
  mkdirSync7(SESSION_CACHE, { recursive: true });
4708
5437
  }
4709
5438
  const rootExe = extractRootExe(parentExe) ?? parentExe;
4710
- const filePath = path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
4711
- writeFileSync8(filePath, JSON.stringify({
5439
+ const filePath = path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
5440
+ writeFileSync9(filePath, JSON.stringify({
4712
5441
  parentExe: rootExe,
4713
5442
  dispatchedBy: dispatchedBy || rootExe,
4714
5443
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -4716,7 +5445,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
4716
5445
  }
4717
5446
  function getParentExe(sessionKey) {
4718
5447
  try {
4719
- const data = JSON.parse(readFileSync12(path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
5448
+ const data = JSON.parse(readFileSync13(path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
4720
5449
  return data.parentExe || null;
4721
5450
  } catch {
4722
5451
  return null;
@@ -4724,8 +5453,8 @@ function getParentExe(sessionKey) {
4724
5453
  }
4725
5454
  function getDispatchedBy(sessionKey) {
4726
5455
  try {
4727
- const data = JSON.parse(readFileSync12(
4728
- path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
5456
+ const data = JSON.parse(readFileSync13(
5457
+ path18.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
4729
5458
  "utf8"
4730
5459
  ));
4731
5460
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -4795,8 +5524,8 @@ async function verifyPaneAtCapacity(sessionName) {
4795
5524
  }
4796
5525
  function readDebounceState() {
4797
5526
  try {
4798
- if (!existsSync12(DEBOUNCE_FILE)) return {};
4799
- const raw = JSON.parse(readFileSync12(DEBOUNCE_FILE, "utf8"));
5527
+ if (!existsSync14(DEBOUNCE_FILE)) return {};
5528
+ const raw = JSON.parse(readFileSync13(DEBOUNCE_FILE, "utf8"));
4800
5529
  const state = {};
4801
5530
  for (const [key, val] of Object.entries(raw)) {
4802
5531
  if (typeof val === "number") {
@@ -4812,8 +5541,8 @@ function readDebounceState() {
4812
5541
  }
4813
5542
  function writeDebounceState(state) {
4814
5543
  try {
4815
- if (!existsSync12(SESSION_CACHE)) mkdirSync7(SESSION_CACHE, { recursive: true });
4816
- writeFileSync8(DEBOUNCE_FILE, JSON.stringify(state));
5544
+ if (!existsSync14(SESSION_CACHE)) mkdirSync7(SESSION_CACHE, { recursive: true });
5545
+ writeFileSync9(DEBOUNCE_FILE, JSON.stringify(state));
4817
5546
  } catch {
4818
5547
  }
4819
5548
  }
@@ -4911,8 +5640,8 @@ function sendIntercom(targetSession) {
4911
5640
  try {
4912
5641
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
4913
5642
  const agent = baseAgentName(rawAgent);
4914
- const markerPath = path16.join(SESSION_CACHE, `current-task-${agent}.json`);
4915
- if (existsSync12(markerPath)) {
5643
+ const markerPath = path18.join(SESSION_CACHE, `current-task-${agent}.json`);
5644
+ if (existsSync14(markerPath)) {
4916
5645
  logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
4917
5646
  return "debounced";
4918
5647
  }
@@ -4921,8 +5650,8 @@ function sendIntercom(targetSession) {
4921
5650
  try {
4922
5651
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
4923
5652
  const agent = baseAgentName(rawAgent);
4924
- const taskDir = path16.join(process.cwd(), "exe", agent);
4925
- if (existsSync12(taskDir)) {
5653
+ const taskDir = path18.join(process.cwd(), "exe", agent);
5654
+ if (existsSync14(taskDir)) {
4926
5655
  const files = readdirSync4(taskDir).filter(
4927
5656
  (f) => f.endsWith(".md") && f !== "DONE.txt"
4928
5657
  );
@@ -4982,6 +5711,21 @@ function notifyParentExe(sessionKey) {
4982
5711
  }
4983
5712
  return true;
4984
5713
  }
5714
+ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
5715
+ const transport = getTransport();
5716
+ try {
5717
+ const sessions = transport.listSessions();
5718
+ if (!sessions.includes(coordinatorSession)) return false;
5719
+ execSync7(
5720
+ `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
5721
+ { timeout: 3e3 }
5722
+ );
5723
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
5724
+ return true;
5725
+ } catch {
5726
+ return false;
5727
+ }
5728
+ }
4985
5729
  function ensureEmployee(employeeName, exeSession, projectDir, opts) {
4986
5730
  if (isCoordinatorName(employeeName)) {
4987
5731
  return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
@@ -5055,26 +5799,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5055
5799
  const transport = getTransport();
5056
5800
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
5057
5801
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
5058
- const logDir = path16.join(os8.homedir(), ".exe-os", "session-logs");
5059
- const logFile = path16.join(logDir, `${instanceLabel}-${Date.now()}.log`);
5060
- if (!existsSync12(logDir)) {
5802
+ const logDir = path18.join(os10.homedir(), ".exe-os", "session-logs");
5803
+ const logFile = path18.join(logDir, `${instanceLabel}-${Date.now()}.log`);
5804
+ if (!existsSync14(logDir)) {
5061
5805
  mkdirSync7(logDir, { recursive: true });
5062
5806
  }
5063
5807
  transport.kill(sessionName);
5064
5808
  let cleanupSuffix = "";
5065
5809
  try {
5066
5810
  const thisFile = fileURLToPath2(import.meta.url);
5067
- const cleanupScript = path16.join(path16.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
5068
- if (existsSync12(cleanupScript)) {
5811
+ const cleanupScript = path18.join(path18.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
5812
+ if (existsSync14(cleanupScript)) {
5069
5813
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
5070
5814
  }
5071
5815
  } catch {
5072
5816
  }
5073
5817
  try {
5074
- const claudeJsonPath = path16.join(os8.homedir(), ".claude.json");
5818
+ const claudeJsonPath = path18.join(os10.homedir(), ".claude.json");
5075
5819
  let claudeJson = {};
5076
5820
  try {
5077
- claudeJson = JSON.parse(readFileSync12(claudeJsonPath, "utf8"));
5821
+ claudeJson = JSON.parse(readFileSync13(claudeJsonPath, "utf8"));
5078
5822
  } catch {
5079
5823
  }
5080
5824
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -5082,17 +5826,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5082
5826
  const trustDir = opts?.cwd ?? projectDir;
5083
5827
  if (!projects[trustDir]) projects[trustDir] = {};
5084
5828
  projects[trustDir].hasTrustDialogAccepted = true;
5085
- writeFileSync8(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
5829
+ writeFileSync9(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
5086
5830
  } catch {
5087
5831
  }
5088
5832
  try {
5089
- const settingsDir = path16.join(os8.homedir(), ".claude", "projects");
5833
+ const settingsDir = path18.join(os10.homedir(), ".claude", "projects");
5090
5834
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
5091
- const projSettingsDir = path16.join(settingsDir, normalizedKey);
5092
- const settingsPath = path16.join(projSettingsDir, "settings.json");
5835
+ const projSettingsDir = path18.join(settingsDir, normalizedKey);
5836
+ const settingsPath = path18.join(projSettingsDir, "settings.json");
5093
5837
  let settings = {};
5094
5838
  try {
5095
- settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
5839
+ settings = JSON.parse(readFileSync13(settingsPath, "utf8"));
5096
5840
  } catch {
5097
5841
  }
5098
5842
  const perms = settings.permissions ?? {};
@@ -5121,7 +5865,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5121
5865
  perms.allow = allow;
5122
5866
  settings.permissions = perms;
5123
5867
  mkdirSync7(projSettingsDir, { recursive: true });
5124
- writeFileSync8(settingsPath, JSON.stringify(settings, null, 2) + "\n");
5868
+ writeFileSync9(settingsPath, JSON.stringify(settings, null, 2) + "\n");
5125
5869
  }
5126
5870
  } catch {
5127
5871
  }
@@ -5136,8 +5880,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5136
5880
  let behaviorsFlag = "";
5137
5881
  let legacyFallbackWarned = false;
5138
5882
  if (!useExeAgent && !useBinSymlink) {
5139
- const identityPath = path16.join(
5140
- os8.homedir(),
5883
+ const identityPath = path18.join(
5884
+ os10.homedir(),
5141
5885
  ".exe-os",
5142
5886
  "identity",
5143
5887
  `${employeeName}.md`
@@ -5146,13 +5890,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5146
5890
  const hasAgentFlag = claudeSupportsAgentFlag();
5147
5891
  if (hasAgentFlag) {
5148
5892
  identityFlag = ` --agent ${employeeName}`;
5149
- } else if (existsSync12(identityPath)) {
5893
+ } else if (existsSync14(identityPath)) {
5150
5894
  identityFlag = ` --append-system-prompt-file ${identityPath}`;
5151
5895
  legacyFallbackWarned = true;
5152
5896
  }
5153
5897
  const behaviorsFile = exportBehaviorsSync(
5154
5898
  employeeName,
5155
- path16.basename(spawnCwd),
5899
+ path18.basename(spawnCwd),
5156
5900
  sessionName
5157
5901
  );
5158
5902
  if (behaviorsFile) {
@@ -5167,16 +5911,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5167
5911
  }
5168
5912
  let sessionContextFlag = "";
5169
5913
  try {
5170
- const ctxDir = path16.join(os8.homedir(), ".exe-os", "session-cache");
5914
+ const ctxDir = path18.join(os10.homedir(), ".exe-os", "session-cache");
5171
5915
  mkdirSync7(ctxDir, { recursive: true });
5172
- const ctxFile = path16.join(ctxDir, `session-context-${sessionName}.md`);
5916
+ const ctxFile = path18.join(ctxDir, `session-context-${sessionName}.md`);
5173
5917
  const ctxContent = [
5174
5918
  `## Session Context`,
5175
5919
  `You are running in tmux session: ${sessionName}.`,
5176
5920
  `Your parent coordinator session is ${exeSession}.`,
5177
5921
  `Your employees (if any) use the -${exeSession} suffix.`
5178
5922
  ].join("\n");
5179
- writeFileSync8(ctxFile, ctxContent);
5923
+ writeFileSync9(ctxFile, ctxContent);
5180
5924
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
5181
5925
  } catch {
5182
5926
  }
@@ -5253,8 +5997,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5253
5997
  transport.pipeLog(sessionName, logFile);
5254
5998
  try {
5255
5999
  const mySession = getMySession();
5256
- const dispatchInfo = path16.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
5257
- writeFileSync8(dispatchInfo, JSON.stringify({
6000
+ const dispatchInfo = path18.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
6001
+ writeFileSync9(dispatchInfo, JSON.stringify({
5258
6002
  dispatchedBy: mySession,
5259
6003
  rootExe: exeSession,
5260
6004
  provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
@@ -5328,15 +6072,15 @@ var init_tmux_routing = __esm({
5328
6072
  init_intercom_queue();
5329
6073
  init_plan_limits();
5330
6074
  init_employees();
5331
- SPAWN_LOCK_DIR = path16.join(os8.homedir(), ".exe-os", "spawn-locks");
5332
- SESSION_CACHE = path16.join(os8.homedir(), ".exe-os", "session-cache");
6075
+ SPAWN_LOCK_DIR = path18.join(os10.homedir(), ".exe-os", "spawn-locks");
6076
+ SESSION_CACHE = path18.join(os10.homedir(), ".exe-os", "session-cache");
5333
6077
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
5334
6078
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
5335
6079
  VERIFY_PANE_LINES = 200;
5336
6080
  INTERCOM_DEBOUNCE_MS = 3e4;
5337
6081
  CODEX_DEBOUNCE_MS = 12e4;
5338
- INTERCOM_LOG2 = path16.join(os8.homedir(), ".exe-os", "intercom.log");
5339
- DEBOUNCE_FILE = path16.join(SESSION_CACHE, "intercom-debounce.json");
6082
+ INTERCOM_LOG2 = path18.join(os10.homedir(), ".exe-os", "intercom.log");
6083
+ DEBOUNCE_FILE = path18.join(SESSION_CACHE, "intercom-debounce.json");
5340
6084
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
5341
6085
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
5342
6086
  }
@@ -5359,6 +6103,15 @@ function sessionScopeFilter(sessionScope, tableAlias) {
5359
6103
  args: [scope]
5360
6104
  };
5361
6105
  }
6106
+ function strictSessionScopeFilter(sessionScope, tableAlias) {
6107
+ const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
6108
+ if (!scope) return { sql: "", args: [] };
6109
+ const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
6110
+ return {
6111
+ sql: ` AND ${col} = ?`,
6112
+ args: [scope]
6113
+ };
6114
+ }
5362
6115
  var init_task_scope = __esm({
5363
6116
  "src/lib/task-scope.ts"() {
5364
6117
  "use strict";
@@ -5377,14 +6130,14 @@ var init_memory = __esm({
5377
6130
 
5378
6131
  // src/lib/keychain.ts
5379
6132
  import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
5380
- import { existsSync as existsSync13 } from "fs";
5381
- import path17 from "path";
5382
- import os9 from "os";
6133
+ import { existsSync as existsSync15 } from "fs";
6134
+ import path19 from "path";
6135
+ import os11 from "os";
5383
6136
  function getKeyDir() {
5384
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path17.join(os9.homedir(), ".exe-os");
6137
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path19.join(os11.homedir(), ".exe-os");
5385
6138
  }
5386
6139
  function getKeyPath() {
5387
- return path17.join(getKeyDir(), "master.key");
6140
+ return path19.join(getKeyDir(), "master.key");
5388
6141
  }
5389
6142
  async function tryKeytar() {
5390
6143
  try {
@@ -5405,9 +6158,9 @@ async function getMasterKey() {
5405
6158
  }
5406
6159
  }
5407
6160
  const keyPath = getKeyPath();
5408
- if (!existsSync13(keyPath)) {
6161
+ if (!existsSync15(keyPath)) {
5409
6162
  process.stderr.write(
5410
- `[keychain] Key not found at ${keyPath} (HOME=${os9.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
6163
+ `[keychain] Key not found at ${keyPath} (HOME=${os11.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
5411
6164
  `
5412
6165
  );
5413
6166
  return null;
@@ -5437,6 +6190,7 @@ var shard_manager_exports = {};
5437
6190
  __export(shard_manager_exports, {
5438
6191
  disposeShards: () => disposeShards,
5439
6192
  ensureShardSchema: () => ensureShardSchema,
6193
+ getOpenShardCount: () => getOpenShardCount,
5440
6194
  getReadyShardClient: () => getReadyShardClient,
5441
6195
  getShardClient: () => getShardClient,
5442
6196
  getShardsDir: () => getShardsDir,
@@ -5445,15 +6199,18 @@ __export(shard_manager_exports, {
5445
6199
  listShards: () => listShards,
5446
6200
  shardExists: () => shardExists
5447
6201
  });
5448
- import path18 from "path";
5449
- import { existsSync as existsSync14, mkdirSync as mkdirSync8, readdirSync as readdirSync5 } from "fs";
6202
+ import path20 from "path";
6203
+ import { existsSync as existsSync16, mkdirSync as mkdirSync8, readdirSync as readdirSync5 } from "fs";
5450
6204
  import { createClient as createClient2 } from "@libsql/client";
5451
6205
  function initShardManager(encryptionKey) {
5452
6206
  _encryptionKey = encryptionKey;
5453
- if (!existsSync14(SHARDS_DIR)) {
6207
+ if (!existsSync16(SHARDS_DIR)) {
5454
6208
  mkdirSync8(SHARDS_DIR, { recursive: true });
5455
6209
  }
5456
6210
  _shardingEnabled = true;
6211
+ if (_evictionTimer) clearInterval(_evictionTimer);
6212
+ _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
6213
+ _evictionTimer.unref();
5457
6214
  }
5458
6215
  function isShardingEnabled() {
5459
6216
  return _shardingEnabled;
@@ -5470,21 +6227,28 @@ function getShardClient(projectName) {
5470
6227
  throw new Error(`Invalid project name for shard: "${projectName}"`);
5471
6228
  }
5472
6229
  const cached = _shards.get(safeName);
5473
- if (cached) return cached;
5474
- const dbPath = path18.join(SHARDS_DIR, `${safeName}.db`);
6230
+ if (cached) {
6231
+ _shardLastAccess.set(safeName, Date.now());
6232
+ return cached;
6233
+ }
6234
+ while (_shards.size >= MAX_OPEN_SHARDS) {
6235
+ evictLRU();
6236
+ }
6237
+ const dbPath = path20.join(SHARDS_DIR, `${safeName}.db`);
5475
6238
  const client = createClient2({
5476
6239
  url: `file:${dbPath}`,
5477
6240
  encryptionKey: _encryptionKey
5478
6241
  });
5479
6242
  _shards.set(safeName, client);
6243
+ _shardLastAccess.set(safeName, Date.now());
5480
6244
  return client;
5481
6245
  }
5482
6246
  function shardExists(projectName) {
5483
6247
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
5484
- return existsSync14(path18.join(SHARDS_DIR, `${safeName}.db`));
6248
+ return existsSync16(path20.join(SHARDS_DIR, `${safeName}.db`));
5485
6249
  }
5486
6250
  function listShards() {
5487
- if (!existsSync14(SHARDS_DIR)) return [];
6251
+ if (!existsSync16(SHARDS_DIR)) return [];
5488
6252
  return readdirSync5(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
5489
6253
  }
5490
6254
  async function ensureShardSchema(client) {
@@ -5536,6 +6300,8 @@ async function ensureShardSchema(client) {
5536
6300
  for (const col of [
5537
6301
  "ALTER TABLE memories ADD COLUMN task_id TEXT",
5538
6302
  "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
6303
+ "ALTER TABLE memories ADD COLUMN author_device_id TEXT",
6304
+ "ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
5539
6305
  "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
5540
6306
  "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
5541
6307
  "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
@@ -5558,7 +6324,23 @@ async function ensureShardSchema(client) {
5558
6324
  // MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
5559
6325
  "ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
5560
6326
  "ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
5561
- "ALTER TABLE memories ADD COLUMN trajectory TEXT"
6327
+ "ALTER TABLE memories ADD COLUMN trajectory TEXT",
6328
+ // Metadata enrichment columns (must match database.ts)
6329
+ "ALTER TABLE memories ADD COLUMN intent TEXT",
6330
+ "ALTER TABLE memories ADD COLUMN outcome TEXT",
6331
+ "ALTER TABLE memories ADD COLUMN domain TEXT",
6332
+ "ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
6333
+ "ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
6334
+ "ALTER TABLE memories ADD COLUMN chain_position TEXT",
6335
+ "ALTER TABLE memories ADD COLUMN review_status TEXT",
6336
+ "ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
6337
+ "ALTER TABLE memories ADD COLUMN file_paths TEXT",
6338
+ "ALTER TABLE memories ADD COLUMN commit_hash TEXT",
6339
+ "ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
6340
+ "ALTER TABLE memories ADD COLUMN token_cost REAL",
6341
+ "ALTER TABLE memories ADD COLUMN audience TEXT",
6342
+ "ALTER TABLE memories ADD COLUMN language_type TEXT",
6343
+ "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
5562
6344
  ]) {
5563
6345
  try {
5564
6346
  await client.execute(col);
@@ -5657,21 +6439,69 @@ async function getReadyShardClient(projectName) {
5657
6439
  await ensureShardSchema(client);
5658
6440
  return client;
5659
6441
  }
6442
+ function evictLRU() {
6443
+ let oldest = null;
6444
+ let oldestTime = Infinity;
6445
+ for (const [name, time] of _shardLastAccess) {
6446
+ if (time < oldestTime) {
6447
+ oldestTime = time;
6448
+ oldest = name;
6449
+ }
6450
+ }
6451
+ if (oldest) {
6452
+ const client = _shards.get(oldest);
6453
+ if (client) {
6454
+ client.close();
6455
+ }
6456
+ _shards.delete(oldest);
6457
+ _shardLastAccess.delete(oldest);
6458
+ }
6459
+ }
6460
+ function evictIdleShards() {
6461
+ const now = Date.now();
6462
+ const toEvict = [];
6463
+ for (const [name, lastAccess] of _shardLastAccess) {
6464
+ if (now - lastAccess > SHARD_IDLE_MS) {
6465
+ toEvict.push(name);
6466
+ }
6467
+ }
6468
+ for (const name of toEvict) {
6469
+ const client = _shards.get(name);
6470
+ if (client) {
6471
+ client.close();
6472
+ }
6473
+ _shards.delete(name);
6474
+ _shardLastAccess.delete(name);
6475
+ }
6476
+ }
6477
+ function getOpenShardCount() {
6478
+ return _shards.size;
6479
+ }
5660
6480
  function disposeShards() {
6481
+ if (_evictionTimer) {
6482
+ clearInterval(_evictionTimer);
6483
+ _evictionTimer = null;
6484
+ }
5661
6485
  for (const [, client] of _shards) {
5662
6486
  client.close();
5663
6487
  }
5664
6488
  _shards.clear();
6489
+ _shardLastAccess.clear();
5665
6490
  _shardingEnabled = false;
5666
6491
  _encryptionKey = null;
5667
6492
  }
5668
- var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
6493
+ var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
5669
6494
  var init_shard_manager = __esm({
5670
6495
  "src/lib/shard-manager.ts"() {
5671
6496
  "use strict";
5672
6497
  init_config();
5673
- SHARDS_DIR = path18.join(EXE_AI_DIR, "shards");
6498
+ SHARDS_DIR = path20.join(EXE_AI_DIR, "shards");
6499
+ SHARD_IDLE_MS = 5 * 60 * 1e3;
6500
+ MAX_OPEN_SHARDS = 10;
6501
+ EVICTION_INTERVAL_MS = 60 * 1e3;
5674
6502
  _shards = /* @__PURE__ */ new Map();
6503
+ _shardLastAccess = /* @__PURE__ */ new Map();
6504
+ _evictionTimer = null;
5675
6505
  _encryptionKey = null;
5676
6506
  _shardingEnabled = false;
5677
6507
  }
@@ -6656,7 +7486,7 @@ var init_git_task_sweep = __esm({
6656
7486
  init_config();
6657
7487
  init_session_key();
6658
7488
  init_employees();
6659
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync, unlinkSync as unlinkSync2, readdirSync } from "fs";
7489
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2, readdirSync } from "fs";
6660
7490
  import { execSync as execSync3 } from "child_process";
6661
7491
  import path3 from "path";
6662
7492
  var CACHE_DIR = path3.join(EXE_AI_DIR, "session-cache");
@@ -6787,7 +7617,7 @@ process.stdin.on("end", async () => {
6787
7617
  const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
6788
7618
  const { randomUUID: randomUUID4 } = await import("crypto");
6789
7619
  const client = getClient2();
6790
- const seScope = sessionScopeFilter();
7620
+ const seScope = strictSessionScopeFilter();
6791
7621
  const orphanResult = await client.execute({
6792
7622
  sql: `SELECT title, status FROM tasks WHERE assigned_to = ? AND status IN ('open', 'in_progress')${seScope.sql}`,
6793
7623
  args: [agent.agentId, ...seScope.args]
@@ -6831,8 +7661,10 @@ Orphaned tasks at session end: ${orphanResult.rows.map((r) => `"${String(r.title
6831
7661
  let context;
6832
7662
  try {
6833
7663
  const ctxResult = await client.execute({
6834
- sql: "SELECT id, context FROM tasks WHERE title = ? AND assigned_to = ? AND status = 'in_progress' LIMIT 1",
6835
- args: [title, agent.agentId]
7664
+ sql: `SELECT id, context FROM tasks
7665
+ WHERE title = ? AND assigned_to = ? AND status = 'in_progress'${seScope.sql}
7666
+ LIMIT 1`,
7667
+ args: [title, agent.agentId, ...seScope.args]
6836
7668
  });
6837
7669
  if (ctxResult.rows.length > 0) {
6838
7670
  context = ctxResult.rows[0].context ? String(ctxResult.rows[0].context) : void 0;
@@ -6843,12 +7675,14 @@ Orphaned tasks at session end: ${orphanResult.rows.map((r) => `"${String(r.title
6843
7675
  const match = findBestMatch2(taskForMatch, commits);
6844
7676
  if (match) {
6845
7677
  await client.execute({
6846
- sql: "UPDATE tasks SET status = 'done', result = ?, updated_at = ? WHERE title = ? AND assigned_to = ? AND status = 'in_progress'",
7678
+ sql: `UPDATE tasks SET status = 'done', result = ?, updated_at = ?
7679
+ WHERE title = ? AND assigned_to = ? AND status = 'in_progress'${seScope.sql}`,
6847
7680
  args: [
6848
7681
  `Auto-closed: session ended but matching commit ${match.commit.hash} found (score: ${match.score.toFixed(2)}). Message: "${match.commit.message}"`,
6849
7682
  (/* @__PURE__ */ new Date()).toISOString(),
6850
7683
  title,
6851
- agent.agentId
7684
+ agent.agentId,
7685
+ ...seScope.args
6852
7686
  ]
6853
7687
  });
6854
7688
  autoClosed.push(`"${title}" \u2192 commit ${match.commit.hash}`);