@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
@@ -320,9 +320,47 @@ var init_provider_table = __esm({
320
320
  }
321
321
  });
322
322
 
323
+ // src/lib/secure-files.ts
324
+ import { chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
325
+ import { chmod, mkdir } from "fs/promises";
326
+ async function ensurePrivateDir(dirPath) {
327
+ await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
328
+ try {
329
+ await chmod(dirPath, PRIVATE_DIR_MODE);
330
+ } catch {
331
+ }
332
+ }
333
+ function ensurePrivateDirSync(dirPath) {
334
+ mkdirSync2(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
335
+ try {
336
+ chmodSync(dirPath, PRIVATE_DIR_MODE);
337
+ } catch {
338
+ }
339
+ }
340
+ async function enforcePrivateFile(filePath) {
341
+ try {
342
+ await chmod(filePath, PRIVATE_FILE_MODE);
343
+ } catch {
344
+ }
345
+ }
346
+ function enforcePrivateFileSync(filePath) {
347
+ try {
348
+ if (existsSync2(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
349
+ } catch {
350
+ }
351
+ }
352
+ var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
353
+ var init_secure_files = __esm({
354
+ "src/lib/secure-files.ts"() {
355
+ "use strict";
356
+ PRIVATE_DIR_MODE = 448;
357
+ PRIVATE_FILE_MODE = 384;
358
+ }
359
+ });
360
+
323
361
  // src/lib/config.ts
324
- import { readFile, writeFile, mkdir, chmod } from "fs/promises";
325
- import { readFileSync as readFileSync2, existsSync as existsSync2, renameSync } from "fs";
362
+ import { readFile, writeFile } from "fs/promises";
363
+ import { readFileSync as readFileSync2, existsSync as existsSync3, renameSync } from "fs";
326
364
  import path2 from "path";
327
365
  import os2 from "os";
328
366
  function resolveDataDir() {
@@ -330,7 +368,7 @@ function resolveDataDir() {
330
368
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
331
369
  const newDir = path2.join(os2.homedir(), ".exe-os");
332
370
  const legacyDir = path2.join(os2.homedir(), ".exe-mem");
333
- if (!existsSync2(newDir) && existsSync2(legacyDir)) {
371
+ if (!existsSync3(newDir) && existsSync3(legacyDir)) {
334
372
  try {
335
373
  renameSync(legacyDir, newDir);
336
374
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -393,9 +431,9 @@ function normalizeAutoUpdate(raw) {
393
431
  }
394
432
  async function loadConfig() {
395
433
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
396
- await mkdir(dir, { recursive: true });
434
+ await ensurePrivateDir(dir);
397
435
  const configPath = path2.join(dir, "config.json");
398
- if (!existsSync2(configPath)) {
436
+ if (!existsSync3(configPath)) {
399
437
  return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
400
438
  }
401
439
  const raw = await readFile(configPath, "utf-8");
@@ -408,6 +446,7 @@ async function loadConfig() {
408
446
  `);
409
447
  try {
410
448
  await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
449
+ await enforcePrivateFile(configPath);
411
450
  } catch {
412
451
  }
413
452
  }
@@ -427,6 +466,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
427
466
  var init_config = __esm({
428
467
  "src/lib/config.ts"() {
429
468
  "use strict";
469
+ init_secure_files();
430
470
  EXE_AI_DIR = resolveDataDir();
431
471
  DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
432
472
  MODELS_DIR = path2.join(EXE_AI_DIR, "models");
@@ -531,10 +571,10 @@ var init_runtime_table = __esm({
531
571
  });
532
572
 
533
573
  // src/lib/agent-config.ts
534
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
574
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync4 } from "fs";
535
575
  import path3 from "path";
536
576
  function loadAgentConfig() {
537
- if (!existsSync3(AGENT_CONFIG_PATH)) return {};
577
+ if (!existsSync4(AGENT_CONFIG_PATH)) return {};
538
578
  try {
539
579
  return JSON.parse(readFileSync3(AGENT_CONFIG_PATH, "utf-8"));
540
580
  } catch {
@@ -555,6 +595,7 @@ var init_agent_config = __esm({
555
595
  "use strict";
556
596
  init_config();
557
597
  init_runtime_table();
598
+ init_secure_files();
558
599
  AGENT_CONFIG_PATH = path3.join(EXE_AI_DIR, "agent-config.json");
559
600
  DEFAULT_MODELS = {
560
601
  claude: "claude-opus-4",
@@ -573,16 +614,16 @@ __export(intercom_queue_exports, {
573
614
  queueIntercom: () => queueIntercom,
574
615
  readQueue: () => readQueue
575
616
  });
576
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
617
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
577
618
  import path4 from "path";
578
619
  import os3 from "os";
579
620
  function ensureDir() {
580
621
  const dir = path4.dirname(QUEUE_PATH);
581
- if (!existsSync4(dir)) mkdirSync3(dir, { recursive: true });
622
+ if (!existsSync5(dir)) mkdirSync3(dir, { recursive: true });
582
623
  }
583
624
  function readQueue() {
584
625
  try {
585
- if (!existsSync4(QUEUE_PATH)) return [];
626
+ if (!existsSync5(QUEUE_PATH)) return [];
586
627
  return JSON.parse(readFileSync4(QUEUE_PATH, "utf8"));
587
628
  } catch {
588
629
  return [];
@@ -747,7 +788,7 @@ var init_db_retry = __esm({
747
788
 
748
789
  // src/lib/employees.ts
749
790
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
750
- import { existsSync as existsSync5, symlinkSync, readlinkSync, readFileSync as readFileSync5, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
791
+ import { existsSync as existsSync6, symlinkSync, readlinkSync, readFileSync as readFileSync5, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
751
792
  import { execSync as execSync3 } from "child_process";
752
793
  import path5 from "path";
753
794
  import os4 from "os";
@@ -768,7 +809,7 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
768
809
  return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
769
810
  }
770
811
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
771
- if (!existsSync5(employeesPath)) return [];
812
+ if (!existsSync6(employeesPath)) return [];
772
813
  try {
773
814
  return JSON.parse(readFileSync5(employeesPath, "utf-8"));
774
815
  } catch {
@@ -792,7 +833,7 @@ function isMultiInstance(agentName, employees) {
792
833
  if (!emp) return false;
793
834
  return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
794
835
  }
795
- var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
836
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR;
796
837
  var init_employees = __esm({
797
838
  "src/lib/employees.ts"() {
798
839
  "use strict";
@@ -801,16 +842,638 @@ var init_employees = __esm({
801
842
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
802
843
  COORDINATOR_ROLE = "COO";
803
844
  MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
845
+ IDENTITY_DIR = path5.join(EXE_AI_DIR, "identity");
846
+ }
847
+ });
848
+
849
+ // src/lib/database-adapter.ts
850
+ import os5 from "os";
851
+ import path6 from "path";
852
+ import { createRequire } from "module";
853
+ import { pathToFileURL } from "url";
854
+ function quotedIdentifier(identifier) {
855
+ return `"${identifier.replace(/"/g, '""')}"`;
856
+ }
857
+ function unqualifiedTableName(name) {
858
+ const raw = name.trim().replace(/^"|"$/g, "");
859
+ const parts = raw.split(".");
860
+ return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
861
+ }
862
+ function stripTrailingSemicolon(sql) {
863
+ return sql.trim().replace(/;+\s*$/u, "");
864
+ }
865
+ function appendClause(sql, clause) {
866
+ const trimmed = stripTrailingSemicolon(sql);
867
+ const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
868
+ if (!returningMatch) {
869
+ return `${trimmed}${clause}`;
870
+ }
871
+ const idx = returningMatch.index;
872
+ return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
873
+ }
874
+ function normalizeStatement(stmt) {
875
+ if (typeof stmt === "string") {
876
+ return { kind: "positional", sql: stmt, args: [] };
877
+ }
878
+ const sql = stmt.sql;
879
+ if (Array.isArray(stmt.args) || stmt.args === void 0) {
880
+ return { kind: "positional", sql, args: stmt.args ?? [] };
881
+ }
882
+ return { kind: "named", sql, args: stmt.args };
883
+ }
884
+ function rewriteBooleanLiterals(sql) {
885
+ let out = sql;
886
+ for (const column of BOOLEAN_COLUMN_NAMES) {
887
+ const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
888
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
889
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
890
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
891
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
892
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
893
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
894
+ }
895
+ return out;
896
+ }
897
+ function rewriteInsertOrIgnore(sql) {
898
+ if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
899
+ return sql;
900
+ }
901
+ const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
902
+ return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
903
+ }
904
+ function rewriteInsertOrReplace(sql) {
905
+ const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
906
+ if (!match) {
907
+ return sql;
908
+ }
909
+ const rawTable = match[1];
910
+ const rawColumns = match[2];
911
+ const remainder = match[3];
912
+ const tableName = unqualifiedTableName(rawTable);
913
+ const conflictKeys = UPSERT_KEYS[tableName];
914
+ if (!conflictKeys?.length) {
915
+ return sql;
916
+ }
917
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
918
+ const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
919
+ const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
920
+ const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
921
+ return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
922
+ }
923
+ function rewriteSql(sql) {
924
+ let out = sql;
925
+ out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
926
+ out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
927
+ out = rewriteBooleanLiterals(out);
928
+ out = rewriteInsertOrReplace(out);
929
+ out = rewriteInsertOrIgnore(out);
930
+ return stripTrailingSemicolon(out);
931
+ }
932
+ function toBoolean(value) {
933
+ if (value === null || value === void 0) return value;
934
+ if (typeof value === "boolean") return value;
935
+ if (typeof value === "number") return value !== 0;
936
+ if (typeof value === "bigint") return value !== 0n;
937
+ if (typeof value === "string") {
938
+ const normalized = value.trim().toLowerCase();
939
+ if (normalized === "0" || normalized === "false") return false;
940
+ if (normalized === "1" || normalized === "true") return true;
941
+ }
942
+ return Boolean(value);
943
+ }
944
+ function countQuestionMarks(sql, end) {
945
+ let count = 0;
946
+ let inSingle = false;
947
+ let inDouble = false;
948
+ let inLineComment = false;
949
+ let inBlockComment = false;
950
+ for (let i = 0; i < end; i++) {
951
+ const ch = sql[i];
952
+ const next = sql[i + 1];
953
+ if (inLineComment) {
954
+ if (ch === "\n") inLineComment = false;
955
+ continue;
956
+ }
957
+ if (inBlockComment) {
958
+ if (ch === "*" && next === "/") {
959
+ inBlockComment = false;
960
+ i += 1;
961
+ }
962
+ continue;
963
+ }
964
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
965
+ inLineComment = true;
966
+ i += 1;
967
+ continue;
968
+ }
969
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
970
+ inBlockComment = true;
971
+ i += 1;
972
+ continue;
973
+ }
974
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
975
+ inSingle = !inSingle;
976
+ continue;
977
+ }
978
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
979
+ inDouble = !inDouble;
980
+ continue;
981
+ }
982
+ if (!inSingle && !inDouble && ch === "?") {
983
+ count += 1;
984
+ }
985
+ }
986
+ return count;
987
+ }
988
+ function findBooleanPlaceholderIndexes(sql) {
989
+ const indexes = /* @__PURE__ */ new Set();
990
+ for (const column of BOOLEAN_COLUMN_NAMES) {
991
+ const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
992
+ for (const match of sql.matchAll(pattern)) {
993
+ const matchText = match[0];
994
+ const qIndex = match.index + matchText.lastIndexOf("?");
995
+ indexes.add(countQuestionMarks(sql, qIndex + 1));
996
+ }
997
+ }
998
+ return indexes;
999
+ }
1000
+ function coerceInsertBooleanArgs(sql, args) {
1001
+ const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
1002
+ if (!match) return;
1003
+ const rawTable = match[1];
1004
+ const rawColumns = match[2];
1005
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
1006
+ if (!boolColumns?.size) return;
1007
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
1008
+ for (const [index, column] of columns.entries()) {
1009
+ if (boolColumns.has(column) && index < args.length) {
1010
+ args[index] = toBoolean(args[index]);
1011
+ }
1012
+ }
1013
+ }
1014
+ function coerceUpdateBooleanArgs(sql, args) {
1015
+ const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
1016
+ if (!match) return;
1017
+ const rawTable = match[1];
1018
+ const setClause = match[2];
1019
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
1020
+ if (!boolColumns?.size) return;
1021
+ const assignments = setClause.split(",");
1022
+ let placeholderIndex = 0;
1023
+ for (const assignment of assignments) {
1024
+ if (!assignment.includes("?")) continue;
1025
+ placeholderIndex += 1;
1026
+ const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
1027
+ if (colMatch && boolColumns.has(colMatch[1])) {
1028
+ args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
1029
+ }
1030
+ }
1031
+ }
1032
+ function coerceBooleanArgs(sql, args) {
1033
+ const nextArgs = [...args];
1034
+ coerceInsertBooleanArgs(sql, nextArgs);
1035
+ coerceUpdateBooleanArgs(sql, nextArgs);
1036
+ const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
1037
+ for (const index of placeholderIndexes) {
1038
+ if (index > 0 && index <= nextArgs.length) {
1039
+ nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
1040
+ }
1041
+ }
1042
+ return nextArgs;
1043
+ }
1044
+ function convertQuestionMarksToDollarParams(sql) {
1045
+ let out = "";
1046
+ let placeholder = 0;
1047
+ let inSingle = false;
1048
+ let inDouble = false;
1049
+ let inLineComment = false;
1050
+ let inBlockComment = false;
1051
+ for (let i = 0; i < sql.length; i++) {
1052
+ const ch = sql[i];
1053
+ const next = sql[i + 1];
1054
+ if (inLineComment) {
1055
+ out += ch;
1056
+ if (ch === "\n") inLineComment = false;
1057
+ continue;
1058
+ }
1059
+ if (inBlockComment) {
1060
+ out += ch;
1061
+ if (ch === "*" && next === "/") {
1062
+ out += next;
1063
+ inBlockComment = false;
1064
+ i += 1;
1065
+ }
1066
+ continue;
1067
+ }
1068
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
1069
+ out += ch + next;
1070
+ inLineComment = true;
1071
+ i += 1;
1072
+ continue;
1073
+ }
1074
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
1075
+ out += ch + next;
1076
+ inBlockComment = true;
1077
+ i += 1;
1078
+ continue;
1079
+ }
1080
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
1081
+ inSingle = !inSingle;
1082
+ out += ch;
1083
+ continue;
1084
+ }
1085
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
1086
+ inDouble = !inDouble;
1087
+ out += ch;
1088
+ continue;
1089
+ }
1090
+ if (!inSingle && !inDouble && ch === "?") {
1091
+ placeholder += 1;
1092
+ out += `$${placeholder}`;
1093
+ continue;
1094
+ }
1095
+ out += ch;
1096
+ }
1097
+ return out;
1098
+ }
1099
+ function translateStatementForPostgres(stmt) {
1100
+ const normalized = normalizeStatement(stmt);
1101
+ if (normalized.kind === "named") {
1102
+ throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
1103
+ }
1104
+ const rewrittenSql = rewriteSql(normalized.sql);
1105
+ const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
1106
+ return {
1107
+ sql: convertQuestionMarksToDollarParams(rewrittenSql),
1108
+ args: coercedArgs
1109
+ };
1110
+ }
1111
+ function shouldBypassPostgres(stmt) {
1112
+ const normalized = normalizeStatement(stmt);
1113
+ if (normalized.kind === "named") {
1114
+ return true;
1115
+ }
1116
+ return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
1117
+ }
1118
+ function shouldFallbackOnError(error) {
1119
+ const message = error instanceof Error ? error.message : String(error);
1120
+ return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
1121
+ }
1122
+ function isReadQuery(sql) {
1123
+ const trimmed = sql.trimStart();
1124
+ return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
1125
+ }
1126
+ function buildRow(row, columns) {
1127
+ const values = columns.map((column) => row[column]);
1128
+ return Object.assign(values, row);
1129
+ }
1130
+ function buildResultSet(rows, rowsAffected = 0) {
1131
+ const columns = rows[0] ? Object.keys(rows[0]) : [];
1132
+ const resultRows = rows.map((row) => buildRow(row, columns));
1133
+ return {
1134
+ columns,
1135
+ columnTypes: columns.map(() => ""),
1136
+ rows: resultRows,
1137
+ rowsAffected,
1138
+ lastInsertRowid: void 0,
1139
+ toJSON() {
1140
+ return {
1141
+ columns,
1142
+ columnTypes: columns.map(() => ""),
1143
+ rows,
1144
+ rowsAffected,
1145
+ lastInsertRowid: void 0
1146
+ };
1147
+ }
1148
+ };
1149
+ }
1150
+ async function loadPrismaClient() {
1151
+ if (!prismaClientPromise) {
1152
+ prismaClientPromise = (async () => {
1153
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
1154
+ if (explicitPath) {
1155
+ const module2 = await import(pathToFileURL(explicitPath).href);
1156
+ const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
1157
+ if (!PrismaClient2) {
1158
+ throw new Error(`No PrismaClient export found at ${explicitPath}`);
1159
+ }
1160
+ return new PrismaClient2();
1161
+ }
1162
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path6.join(os5.homedir(), "exe-db");
1163
+ const requireFromExeDb = createRequire(path6.join(exeDbRoot, "package.json"));
1164
+ const prismaEntry = requireFromExeDb.resolve("@prisma/client");
1165
+ const module = await import(pathToFileURL(prismaEntry).href);
1166
+ const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
1167
+ if (!PrismaClient) {
1168
+ throw new Error(`No PrismaClient export found in ${prismaEntry}`);
1169
+ }
1170
+ return new PrismaClient();
1171
+ })();
1172
+ }
1173
+ return prismaClientPromise;
1174
+ }
1175
+ async function ensureCompatibilityViews(prisma) {
1176
+ if (!compatibilityBootstrapPromise) {
1177
+ compatibilityBootstrapPromise = (async () => {
1178
+ for (const mapping of VIEW_MAPPINGS) {
1179
+ const relation = mapping.source.replace(/"/g, "");
1180
+ const rows = await prisma.$queryRawUnsafe(
1181
+ "SELECT to_regclass($1) AS regclass",
1182
+ relation
1183
+ );
1184
+ if (!rows[0]?.regclass) {
1185
+ continue;
1186
+ }
1187
+ await prisma.$executeRawUnsafe(
1188
+ `CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
1189
+ );
1190
+ }
1191
+ })();
1192
+ }
1193
+ return compatibilityBootstrapPromise;
1194
+ }
1195
+ async function executeOnPrisma(executor, stmt) {
1196
+ const translated = translateStatementForPostgres(stmt);
1197
+ if (isReadQuery(translated.sql)) {
1198
+ const rows = await executor.$queryRawUnsafe(
1199
+ translated.sql,
1200
+ ...translated.args
1201
+ );
1202
+ return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
1203
+ }
1204
+ const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
1205
+ return buildResultSet([], rowsAffected);
1206
+ }
1207
+ function splitSqlStatements(sql) {
1208
+ const parts = [];
1209
+ let current = "";
1210
+ let inSingle = false;
1211
+ let inDouble = false;
1212
+ let inLineComment = false;
1213
+ let inBlockComment = false;
1214
+ for (let i = 0; i < sql.length; i++) {
1215
+ const ch = sql[i];
1216
+ const next = sql[i + 1];
1217
+ if (inLineComment) {
1218
+ current += ch;
1219
+ if (ch === "\n") inLineComment = false;
1220
+ continue;
1221
+ }
1222
+ if (inBlockComment) {
1223
+ current += ch;
1224
+ if (ch === "*" && next === "/") {
1225
+ current += next;
1226
+ inBlockComment = false;
1227
+ i += 1;
1228
+ }
1229
+ continue;
1230
+ }
1231
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
1232
+ current += ch + next;
1233
+ inLineComment = true;
1234
+ i += 1;
1235
+ continue;
1236
+ }
1237
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
1238
+ current += ch + next;
1239
+ inBlockComment = true;
1240
+ i += 1;
1241
+ continue;
1242
+ }
1243
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
1244
+ inSingle = !inSingle;
1245
+ current += ch;
1246
+ continue;
1247
+ }
1248
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
1249
+ inDouble = !inDouble;
1250
+ current += ch;
1251
+ continue;
1252
+ }
1253
+ if (!inSingle && !inDouble && ch === ";") {
1254
+ if (current.trim()) {
1255
+ parts.push(current.trim());
1256
+ }
1257
+ current = "";
1258
+ continue;
1259
+ }
1260
+ current += ch;
1261
+ }
1262
+ if (current.trim()) {
1263
+ parts.push(current.trim());
1264
+ }
1265
+ return parts;
1266
+ }
1267
+ async function createPrismaDbAdapter(fallbackClient) {
1268
+ const prisma = await loadPrismaClient();
1269
+ await ensureCompatibilityViews(prisma);
1270
+ let closed = false;
1271
+ let adapter;
1272
+ const fallbackExecute = async (stmt, error) => {
1273
+ if (!fallbackClient) {
1274
+ if (error) throw error;
1275
+ throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
1276
+ }
1277
+ if (error) {
1278
+ process.stderr.write(
1279
+ `[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
1280
+ `
1281
+ );
1282
+ }
1283
+ return fallbackClient.execute(stmt);
1284
+ };
1285
+ adapter = {
1286
+ async execute(stmt) {
1287
+ if (shouldBypassPostgres(stmt)) {
1288
+ return fallbackExecute(stmt);
1289
+ }
1290
+ try {
1291
+ return await executeOnPrisma(prisma, stmt);
1292
+ } catch (error) {
1293
+ if (shouldFallbackOnError(error)) {
1294
+ return fallbackExecute(stmt, error);
1295
+ }
1296
+ throw error;
1297
+ }
1298
+ },
1299
+ async batch(stmts, mode) {
1300
+ if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
1301
+ if (!fallbackClient) {
1302
+ throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
1303
+ }
1304
+ return fallbackClient.batch(stmts, mode);
1305
+ }
1306
+ try {
1307
+ if (prisma.$transaction) {
1308
+ return await prisma.$transaction(async (tx) => {
1309
+ const results2 = [];
1310
+ for (const stmt of stmts) {
1311
+ results2.push(await executeOnPrisma(tx, stmt));
1312
+ }
1313
+ return results2;
1314
+ });
1315
+ }
1316
+ const results = [];
1317
+ for (const stmt of stmts) {
1318
+ results.push(await executeOnPrisma(prisma, stmt));
1319
+ }
1320
+ return results;
1321
+ } catch (error) {
1322
+ if (fallbackClient && shouldFallbackOnError(error)) {
1323
+ process.stderr.write(
1324
+ `[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
1325
+ `
1326
+ );
1327
+ return fallbackClient.batch(stmts, mode);
1328
+ }
1329
+ throw error;
1330
+ }
1331
+ },
1332
+ async migrate(stmts) {
1333
+ if (fallbackClient) {
1334
+ return fallbackClient.migrate(stmts);
1335
+ }
1336
+ return adapter.batch(stmts, "deferred");
1337
+ },
1338
+ async transaction(mode) {
1339
+ if (!fallbackClient) {
1340
+ throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
1341
+ }
1342
+ return fallbackClient.transaction(mode);
1343
+ },
1344
+ async executeMultiple(sql) {
1345
+ if (fallbackClient && shouldBypassPostgres(sql)) {
1346
+ return fallbackClient.executeMultiple(sql);
1347
+ }
1348
+ for (const statement of splitSqlStatements(sql)) {
1349
+ await adapter.execute(statement);
1350
+ }
1351
+ },
1352
+ async sync() {
1353
+ if (fallbackClient) {
1354
+ return fallbackClient.sync();
1355
+ }
1356
+ return { frame_no: 0, frames_synced: 0 };
1357
+ },
1358
+ close() {
1359
+ closed = true;
1360
+ prismaClientPromise = null;
1361
+ compatibilityBootstrapPromise = null;
1362
+ void prisma.$disconnect?.();
1363
+ },
1364
+ get closed() {
1365
+ return closed;
1366
+ },
1367
+ get protocol() {
1368
+ return "prisma-postgres";
1369
+ }
1370
+ };
1371
+ return adapter;
1372
+ }
1373
+ var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
1374
+ var init_database_adapter = __esm({
1375
+ "src/lib/database-adapter.ts"() {
1376
+ "use strict";
1377
+ VIEW_MAPPINGS = [
1378
+ { view: "memories", source: "memory.memory_records" },
1379
+ { view: "tasks", source: "memory.tasks" },
1380
+ { view: "behaviors", source: "memory.behaviors" },
1381
+ { view: "entities", source: "memory.entities" },
1382
+ { view: "relationships", source: "memory.relationships" },
1383
+ { view: "entity_memories", source: "memory.entity_memories" },
1384
+ { view: "entity_aliases", source: "memory.entity_aliases" },
1385
+ { view: "notifications", source: "memory.notifications" },
1386
+ { view: "messages", source: "memory.messages" },
1387
+ { view: "users", source: "wiki.users" },
1388
+ { view: "workspaces", source: "wiki.workspaces" },
1389
+ { view: "workspace_users", source: "wiki.workspace_users" },
1390
+ { view: "documents", source: "wiki.workspace_documents" },
1391
+ { view: "chats", source: "wiki.workspace_chats" }
1392
+ ];
1393
+ UPSERT_KEYS = {
1394
+ memories: ["id"],
1395
+ tasks: ["id"],
1396
+ behaviors: ["id"],
1397
+ entities: ["id"],
1398
+ relationships: ["id"],
1399
+ entity_aliases: ["alias"],
1400
+ notifications: ["id"],
1401
+ messages: ["id"],
1402
+ users: ["id"],
1403
+ workspaces: ["id"],
1404
+ workspace_users: ["id"],
1405
+ documents: ["id"],
1406
+ chats: ["id"]
1407
+ };
1408
+ BOOLEAN_COLUMNS_BY_TABLE = {
1409
+ memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
1410
+ behaviors: /* @__PURE__ */ new Set(["active"]),
1411
+ notifications: /* @__PURE__ */ new Set(["read"]),
1412
+ users: /* @__PURE__ */ new Set(["has_personal_memory"])
1413
+ };
1414
+ BOOLEAN_COLUMN_NAMES = new Set(
1415
+ Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
1416
+ );
1417
+ IMMEDIATE_FALLBACK_PATTERNS = [
1418
+ /\bPRAGMA\b/i,
1419
+ /\bsqlite_master\b/i,
1420
+ /(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
1421
+ /\bMATCH\b/i,
1422
+ /\bvector_distance_cos\s*\(/i,
1423
+ /\bjson_extract\s*\(/i,
1424
+ /\bjulianday\s*\(/i,
1425
+ /\bstrftime\s*\(/i,
1426
+ /\blast_insert_rowid\s*\(/i
1427
+ ];
1428
+ prismaClientPromise = null;
1429
+ compatibilityBootstrapPromise = null;
1430
+ }
1431
+ });
1432
+
1433
+ // src/lib/daemon-auth.ts
1434
+ import crypto from "crypto";
1435
+ import path7 from "path";
1436
+ import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
1437
+ function normalizeToken(token) {
1438
+ if (!token) return null;
1439
+ const trimmed = token.trim();
1440
+ return trimmed.length > 0 ? trimmed : null;
1441
+ }
1442
+ function readDaemonToken() {
1443
+ try {
1444
+ if (!existsSync7(DAEMON_TOKEN_PATH)) return null;
1445
+ return normalizeToken(readFileSync6(DAEMON_TOKEN_PATH, "utf8"));
1446
+ } catch {
1447
+ return null;
1448
+ }
1449
+ }
1450
+ function ensureDaemonToken(seed) {
1451
+ const existing = readDaemonToken();
1452
+ if (existing) return existing;
1453
+ const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
1454
+ ensurePrivateDirSync(EXE_AI_DIR);
1455
+ writeFileSync5(DAEMON_TOKEN_PATH, `${token}
1456
+ `, "utf8");
1457
+ enforcePrivateFileSync(DAEMON_TOKEN_PATH);
1458
+ return token;
1459
+ }
1460
+ var DAEMON_TOKEN_PATH;
1461
+ var init_daemon_auth = __esm({
1462
+ "src/lib/daemon-auth.ts"() {
1463
+ "use strict";
1464
+ init_config();
1465
+ init_secure_files();
1466
+ DAEMON_TOKEN_PATH = path7.join(EXE_AI_DIR, "exed.token");
804
1467
  }
805
1468
  });
806
1469
 
807
1470
  // src/lib/exe-daemon-client.ts
808
1471
  import net from "net";
809
- import os5 from "os";
1472
+ import os6 from "os";
810
1473
  import { spawn } from "child_process";
811
1474
  import { randomUUID } from "crypto";
812
- import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync6, openSync, closeSync, statSync } from "fs";
813
- import path6 from "path";
1475
+ import { existsSync as existsSync8, unlinkSync as unlinkSync2, readFileSync as readFileSync7, openSync, closeSync, statSync } from "fs";
1476
+ import path8 from "path";
814
1477
  import { fileURLToPath as fileURLToPath2 } from "url";
815
1478
  function handleData(chunk) {
816
1479
  _buffer += chunk.toString();
@@ -838,9 +1501,9 @@ function handleData(chunk) {
838
1501
  }
839
1502
  }
840
1503
  function cleanupStaleFiles() {
841
- if (existsSync6(PID_PATH)) {
1504
+ if (existsSync8(PID_PATH)) {
842
1505
  try {
843
- const pid = parseInt(readFileSync6(PID_PATH, "utf8").trim(), 10);
1506
+ const pid = parseInt(readFileSync7(PID_PATH, "utf8").trim(), 10);
844
1507
  if (pid > 0) {
845
1508
  try {
846
1509
  process.kill(pid, 0);
@@ -861,17 +1524,17 @@ function cleanupStaleFiles() {
861
1524
  }
862
1525
  }
863
1526
  function findPackageRoot() {
864
- let dir = path6.dirname(fileURLToPath2(import.meta.url));
865
- const { root } = path6.parse(dir);
1527
+ let dir = path8.dirname(fileURLToPath2(import.meta.url));
1528
+ const { root } = path8.parse(dir);
866
1529
  while (dir !== root) {
867
- if (existsSync6(path6.join(dir, "package.json"))) return dir;
868
- dir = path6.dirname(dir);
1530
+ if (existsSync8(path8.join(dir, "package.json"))) return dir;
1531
+ dir = path8.dirname(dir);
869
1532
  }
870
1533
  return null;
871
1534
  }
872
1535
  function spawnDaemon() {
873
- const freeGB = os5.freemem() / (1024 * 1024 * 1024);
874
- const totalGB = os5.totalmem() / (1024 * 1024 * 1024);
1536
+ const freeGB = os6.freemem() / (1024 * 1024 * 1024);
1537
+ const totalGB = os6.totalmem() / (1024 * 1024 * 1024);
875
1538
  if (totalGB <= 8) {
876
1539
  process.stderr.write(
877
1540
  `[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
@@ -891,16 +1554,17 @@ function spawnDaemon() {
891
1554
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
892
1555
  return;
893
1556
  }
894
- const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
895
- if (!existsSync6(daemonPath)) {
1557
+ const daemonPath = path8.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1558
+ if (!existsSync8(daemonPath)) {
896
1559
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
897
1560
  `);
898
1561
  return;
899
1562
  }
900
1563
  const resolvedPath = daemonPath;
1564
+ const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
901
1565
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
902
1566
  `);
903
- const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
1567
+ const logPath = path8.join(path8.dirname(SOCKET_PATH), "exed.log");
904
1568
  let stderrFd = "ignore";
905
1569
  try {
906
1570
  stderrFd = openSync(logPath, "a");
@@ -918,7 +1582,8 @@ function spawnDaemon() {
918
1582
  TMUX_PANE: void 0,
919
1583
  // Prevents resolveExeSession() from scoping to one session
920
1584
  EXE_DAEMON_SOCK: SOCKET_PATH,
921
- EXE_DAEMON_PID: PID_PATH
1585
+ EXE_DAEMON_PID: PID_PATH,
1586
+ [DAEMON_TOKEN_ENV]: daemonToken
922
1587
  }
923
1588
  });
924
1589
  child.unref();
@@ -1025,13 +1690,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
1025
1690
  return;
1026
1691
  }
1027
1692
  const id = randomUUID();
1693
+ const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
1028
1694
  const timer = setTimeout(() => {
1029
1695
  _pending.delete(id);
1030
1696
  resolve({ error: "Request timeout" });
1031
1697
  }, timeoutMs);
1032
1698
  _pending.set(id, { resolve, timer });
1033
1699
  try {
1034
- _socket.write(JSON.stringify({ id, ...payload }) + "\n");
1700
+ _socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
1035
1701
  } catch {
1036
1702
  clearTimeout(timer);
1037
1703
  _pending.delete(id);
@@ -1042,17 +1708,19 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
1042
1708
  function isClientConnected() {
1043
1709
  return _connected;
1044
1710
  }
1045
- var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
1711
+ 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;
1046
1712
  var init_exe_daemon_client = __esm({
1047
1713
  "src/lib/exe-daemon-client.ts"() {
1048
1714
  "use strict";
1049
1715
  init_config();
1050
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
1051
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
1052
- SPAWN_LOCK_PATH = path6.join(EXE_AI_DIR, "exed-spawn.lock");
1716
+ init_daemon_auth();
1717
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path8.join(EXE_AI_DIR, "exed.sock");
1718
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path8.join(EXE_AI_DIR, "exed.pid");
1719
+ SPAWN_LOCK_PATH = path8.join(EXE_AI_DIR, "exed-spawn.lock");
1053
1720
  SPAWN_LOCK_STALE_MS = 3e4;
1054
1721
  CONNECT_TIMEOUT_MS = 15e3;
1055
1722
  REQUEST_TIMEOUT_MS = 3e4;
1723
+ DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
1056
1724
  _socket = null;
1057
1725
  _connected = false;
1058
1726
  _buffer = "";
@@ -1131,7 +1799,7 @@ __export(db_daemon_client_exports, {
1131
1799
  createDaemonDbClient: () => createDaemonDbClient,
1132
1800
  initDaemonDbClient: () => initDaemonDbClient
1133
1801
  });
1134
- function normalizeStatement(stmt) {
1802
+ function normalizeStatement2(stmt) {
1135
1803
  if (typeof stmt === "string") {
1136
1804
  return { sql: stmt, args: [] };
1137
1805
  }
@@ -1155,7 +1823,7 @@ function createDaemonDbClient(fallbackClient) {
1155
1823
  if (!_useDaemon || !isClientConnected()) {
1156
1824
  return fallbackClient.execute(stmt);
1157
1825
  }
1158
- const { sql, args } = normalizeStatement(stmt);
1826
+ const { sql, args } = normalizeStatement2(stmt);
1159
1827
  const response = await sendDaemonRequest({
1160
1828
  type: "db-execute",
1161
1829
  sql,
@@ -1180,7 +1848,7 @@ function createDaemonDbClient(fallbackClient) {
1180
1848
  if (!_useDaemon || !isClientConnected()) {
1181
1849
  return fallbackClient.batch(stmts, mode);
1182
1850
  }
1183
- const statements = stmts.map(normalizeStatement);
1851
+ const statements = stmts.map(normalizeStatement2);
1184
1852
  const response = await sendDaemonRequest({
1185
1853
  type: "db-batch",
1186
1854
  statements,
@@ -1275,6 +1943,18 @@ __export(database_exports, {
1275
1943
  });
1276
1944
  import { createClient } from "@libsql/client";
1277
1945
  async function initDatabase(config) {
1946
+ if (_walCheckpointTimer) {
1947
+ clearInterval(_walCheckpointTimer);
1948
+ _walCheckpointTimer = null;
1949
+ }
1950
+ if (_daemonClient) {
1951
+ _daemonClient.close();
1952
+ _daemonClient = null;
1953
+ }
1954
+ if (_adapterClient && _adapterClient !== _resilientClient) {
1955
+ _adapterClient.close();
1956
+ }
1957
+ _adapterClient = null;
1278
1958
  if (_client) {
1279
1959
  _client.close();
1280
1960
  _client = null;
@@ -1288,6 +1968,7 @@ async function initDatabase(config) {
1288
1968
  }
1289
1969
  _client = createClient(opts);
1290
1970
  _resilientClient = wrapWithRetry(_client);
1971
+ _adapterClient = _resilientClient;
1291
1972
  _client.execute("PRAGMA busy_timeout = 30000").catch(() => {
1292
1973
  });
1293
1974
  _client.execute("PRAGMA journal_mode = WAL").catch(() => {
@@ -1298,14 +1979,20 @@ async function initDatabase(config) {
1298
1979
  });
1299
1980
  }, 3e4);
1300
1981
  _walCheckpointTimer.unref();
1982
+ if (process.env.DATABASE_URL) {
1983
+ _adapterClient = await createPrismaDbAdapter(_resilientClient);
1984
+ }
1301
1985
  }
1302
1986
  function isInitialized() {
1303
- return _client !== null;
1987
+ return _adapterClient !== null || _client !== null;
1304
1988
  }
1305
1989
  function getClient() {
1306
- if (!_resilientClient) {
1990
+ if (!_adapterClient) {
1307
1991
  throw new Error("Database client not initialized. Call initDatabase() first.");
1308
1992
  }
1993
+ if (process.env.DATABASE_URL) {
1994
+ return _adapterClient;
1995
+ }
1309
1996
  if (process.env.EXE_IS_DAEMON === "1") {
1310
1997
  return _resilientClient;
1311
1998
  }
@@ -1315,6 +2002,7 @@ function getClient() {
1315
2002
  return _resilientClient;
1316
2003
  }
1317
2004
  async function initDaemonClient() {
2005
+ if (process.env.DATABASE_URL) return;
1318
2006
  if (process.env.EXE_IS_DAEMON === "1") return;
1319
2007
  if (!_resilientClient) return;
1320
2008
  try {
@@ -1611,6 +2299,7 @@ async function ensureSchema() {
1611
2299
  project TEXT NOT NULL,
1612
2300
  summary TEXT NOT NULL,
1613
2301
  task_file TEXT,
2302
+ session_scope TEXT,
1614
2303
  read INTEGER NOT NULL DEFAULT 0,
1615
2304
  created_at TEXT NOT NULL
1616
2305
  );
@@ -1619,7 +2308,7 @@ async function ensureSchema() {
1619
2308
  ON notifications(read);
1620
2309
 
1621
2310
  CREATE INDEX IF NOT EXISTS idx_notifications_agent
1622
- ON notifications(agent_id);
2311
+ ON notifications(agent_id, session_scope);
1623
2312
 
1624
2313
  CREATE INDEX IF NOT EXISTS idx_notifications_task_file
1625
2314
  ON notifications(task_file);
@@ -1657,6 +2346,7 @@ async function ensureSchema() {
1657
2346
  target_agent TEXT NOT NULL,
1658
2347
  target_project TEXT,
1659
2348
  target_device TEXT NOT NULL DEFAULT 'local',
2349
+ session_scope TEXT,
1660
2350
  content TEXT NOT NULL,
1661
2351
  priority TEXT DEFAULT 'normal',
1662
2352
  status TEXT DEFAULT 'pending',
@@ -1670,10 +2360,31 @@ async function ensureSchema() {
1670
2360
  );
1671
2361
 
1672
2362
  CREATE INDEX IF NOT EXISTS idx_messages_target
1673
- ON messages(target_agent, status);
2363
+ ON messages(target_agent, session_scope, status);
1674
2364
 
1675
2365
  CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
1676
- ON messages(target_agent, from_agent, server_seq);
2366
+ ON messages(target_agent, session_scope, from_agent, server_seq);
2367
+ `);
2368
+ try {
2369
+ await client.execute({
2370
+ sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
2371
+ args: []
2372
+ });
2373
+ } catch {
2374
+ }
2375
+ try {
2376
+ await client.execute({
2377
+ sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
2378
+ args: []
2379
+ });
2380
+ } catch {
2381
+ }
2382
+ await client.executeMultiple(`
2383
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
2384
+ ON notifications(agent_id, session_scope, read, created_at);
2385
+
2386
+ CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
2387
+ ON messages(target_agent, session_scope, status, created_at);
1677
2388
  `);
1678
2389
  try {
1679
2390
  await client.execute({
@@ -2257,46 +2968,66 @@ async function ensureSchema() {
2257
2968
  } catch {
2258
2969
  }
2259
2970
  }
2971
+ try {
2972
+ await client.execute({
2973
+ sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
2974
+ args: []
2975
+ });
2976
+ } catch {
2977
+ }
2260
2978
  }
2261
2979
  async function disposeDatabase() {
2980
+ if (_walCheckpointTimer) {
2981
+ clearInterval(_walCheckpointTimer);
2982
+ _walCheckpointTimer = null;
2983
+ }
2262
2984
  if (_daemonClient) {
2263
2985
  _daemonClient.close();
2264
2986
  _daemonClient = null;
2265
2987
  }
2988
+ if (_adapterClient && _adapterClient !== _resilientClient) {
2989
+ _adapterClient.close();
2990
+ }
2991
+ _adapterClient = null;
2266
2992
  if (_client) {
2267
2993
  _client.close();
2268
2994
  _client = null;
2269
2995
  _resilientClient = null;
2270
2996
  }
2271
2997
  }
2272
- var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso, disposeTurso;
2998
+ var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
2273
2999
  var init_database = __esm({
2274
3000
  "src/lib/database.ts"() {
2275
3001
  "use strict";
2276
3002
  init_db_retry();
2277
3003
  init_employees();
3004
+ init_database_adapter();
2278
3005
  _client = null;
2279
3006
  _resilientClient = null;
2280
3007
  _walCheckpointTimer = null;
2281
3008
  _daemonClient = null;
3009
+ _adapterClient = null;
2282
3010
  initTurso = initDatabase;
2283
3011
  disposeTurso = disposeDatabase;
2284
3012
  }
2285
3013
  });
2286
3014
 
2287
3015
  // src/lib/license.ts
2288
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
3016
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync9, mkdirSync as mkdirSync4 } from "fs";
2289
3017
  import { randomUUID as randomUUID2 } from "crypto";
2290
- import path7 from "path";
3018
+ import { createRequire as createRequire2 } from "module";
3019
+ import { pathToFileURL as pathToFileURL2 } from "url";
3020
+ import os7 from "os";
3021
+ import path9 from "path";
2291
3022
  import { jwtVerify, importSPKI } from "jose";
2292
3023
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
2293
3024
  var init_license = __esm({
2294
3025
  "src/lib/license.ts"() {
2295
3026
  "use strict";
2296
3027
  init_config();
2297
- LICENSE_PATH = path7.join(EXE_AI_DIR, "license.key");
2298
- CACHE_PATH = path7.join(EXE_AI_DIR, "license-cache.json");
2299
- DEVICE_ID_PATH = path7.join(EXE_AI_DIR, "device-id");
3028
+ LICENSE_PATH = path9.join(EXE_AI_DIR, "license.key");
3029
+ CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
3030
+ DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
2300
3031
  PLAN_LIMITS = {
2301
3032
  free: { devices: 1, employees: 1, memories: 5e3 },
2302
3033
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -2308,12 +3039,12 @@ var init_license = __esm({
2308
3039
  });
2309
3040
 
2310
3041
  // src/lib/plan-limits.ts
2311
- import { readFileSync as readFileSync8, existsSync as existsSync8 } from "fs";
2312
- import path8 from "path";
3042
+ import { readFileSync as readFileSync9, existsSync as existsSync10 } from "fs";
3043
+ import path10 from "path";
2313
3044
  function getLicenseSync() {
2314
3045
  try {
2315
- if (!existsSync8(CACHE_PATH2)) return freeLicense();
2316
- const raw = JSON.parse(readFileSync8(CACHE_PATH2, "utf8"));
3046
+ if (!existsSync10(CACHE_PATH2)) return freeLicense();
3047
+ const raw = JSON.parse(readFileSync9(CACHE_PATH2, "utf8"));
2317
3048
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
2318
3049
  const parts = raw.token.split(".");
2319
3050
  if (parts.length !== 3) return freeLicense();
@@ -2351,8 +3082,8 @@ function assertEmployeeLimitSync(rosterPath) {
2351
3082
  const filePath = rosterPath ?? EMPLOYEES_PATH;
2352
3083
  let count = 0;
2353
3084
  try {
2354
- if (existsSync8(filePath)) {
2355
- const raw = readFileSync8(filePath, "utf8");
3085
+ if (existsSync10(filePath)) {
3086
+ const raw = readFileSync9(filePath, "utf8");
2356
3087
  const employees = JSON.parse(raw);
2357
3088
  count = Array.isArray(employees) ? employees.length : 0;
2358
3089
  }
@@ -2381,29 +3112,30 @@ var init_plan_limits = __esm({
2381
3112
  this.name = "PlanLimitError";
2382
3113
  }
2383
3114
  };
2384
- CACHE_PATH2 = path8.join(EXE_AI_DIR, "license-cache.json");
3115
+ CACHE_PATH2 = path10.join(EXE_AI_DIR, "license-cache.json");
2385
3116
  }
2386
3117
  });
2387
3118
 
2388
3119
  // src/lib/notifications.ts
2389
- import crypto from "crypto";
2390
- import path9 from "path";
2391
- import os6 from "os";
3120
+ import crypto2 from "crypto";
3121
+ import path11 from "path";
3122
+ import os8 from "os";
2392
3123
  import {
2393
- readFileSync as readFileSync9,
3124
+ readFileSync as readFileSync10,
2394
3125
  readdirSync,
2395
3126
  unlinkSync as unlinkSync3,
2396
- existsSync as existsSync9,
3127
+ existsSync as existsSync11,
2397
3128
  rmdirSync
2398
3129
  } from "fs";
2399
3130
  async function writeNotification(notification) {
2400
3131
  try {
2401
3132
  const client = getClient();
2402
- const id = crypto.randomUUID();
3133
+ const id = crypto2.randomUUID();
2403
3134
  const now = (/* @__PURE__ */ new Date()).toISOString();
3135
+ const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
2404
3136
  await client.execute({
2405
- sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
2406
- VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
3137
+ sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
3138
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
2407
3139
  args: [
2408
3140
  id,
2409
3141
  notification.agentId,
@@ -2412,6 +3144,7 @@ async function writeNotification(notification) {
2412
3144
  notification.project,
2413
3145
  notification.summary,
2414
3146
  notification.taskFile ?? null,
3147
+ sessionScope,
2415
3148
  now
2416
3149
  ]
2417
3150
  });
@@ -2420,12 +3153,14 @@ async function writeNotification(notification) {
2420
3153
  `);
2421
3154
  }
2422
3155
  }
2423
- async function markAsReadByTaskFile(taskFile) {
3156
+ async function markAsReadByTaskFile(taskFile, sessionScope) {
2424
3157
  try {
2425
3158
  const client = getClient();
3159
+ const scope = strictSessionScopeFilter(sessionScope);
2426
3160
  await client.execute({
2427
- sql: "UPDATE notifications SET read = 1 WHERE task_file = ? AND read = 0",
2428
- args: [taskFile]
3161
+ sql: `UPDATE notifications SET read = 1
3162
+ WHERE task_file = ? AND read = 0${scope.sql}`,
3163
+ args: [taskFile, ...scope.args]
2429
3164
  });
2430
3165
  } catch {
2431
3166
  }
@@ -2434,11 +3169,12 @@ var init_notifications = __esm({
2434
3169
  "src/lib/notifications.ts"() {
2435
3170
  "use strict";
2436
3171
  init_database();
3172
+ init_task_scope();
2437
3173
  }
2438
3174
  });
2439
3175
 
2440
3176
  // src/lib/session-kill-telemetry.ts
2441
- import crypto2 from "crypto";
3177
+ import crypto3 from "crypto";
2442
3178
  async function recordSessionKill(input) {
2443
3179
  try {
2444
3180
  const client = getClient();
@@ -2448,7 +3184,7 @@ async function recordSessionKill(input) {
2448
3184
  ticks_idle, estimated_tokens_saved)
2449
3185
  VALUES (?, ?, ?, ?, ?, ?, ?)`,
2450
3186
  args: [
2451
- crypto2.randomUUID(),
3187
+ crypto3.randomUUID(),
2452
3188
  input.sessionName,
2453
3189
  input.agentId,
2454
3190
  (/* @__PURE__ */ new Date()).toISOString(),
@@ -2527,12 +3263,12 @@ var init_state_bus = __esm({
2527
3263
  });
2528
3264
 
2529
3265
  // src/lib/tasks-crud.ts
2530
- import crypto3 from "crypto";
2531
- import path10 from "path";
2532
- import os7 from "os";
3266
+ import crypto4 from "crypto";
3267
+ import path12 from "path";
3268
+ import os9 from "os";
2533
3269
  import { execSync as execSync4 } from "child_process";
2534
3270
  import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
2535
- import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
3271
+ import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
2536
3272
  async function writeCheckpoint(input) {
2537
3273
  const client = getClient();
2538
3274
  const row = await resolveTask(client, input.taskId);
@@ -2648,7 +3384,7 @@ async function resolveTask(client, identifier, scopeSession) {
2648
3384
  }
2649
3385
  async function createTaskCore(input) {
2650
3386
  const client = getClient();
2651
- const id = crypto3.randomUUID();
3387
+ const id = crypto4.randomUUID();
2652
3388
  const now = (/* @__PURE__ */ new Date()).toISOString();
2653
3389
  const slug = slugify(input.title);
2654
3390
  let earlySessionScope = null;
@@ -2707,8 +3443,8 @@ ${laneWarning}` : laneWarning;
2707
3443
  }
2708
3444
  if (input.baseDir) {
2709
3445
  try {
2710
- await mkdir3(path10.join(input.baseDir, "exe", "output"), { recursive: true });
2711
- await mkdir3(path10.join(input.baseDir, "exe", "research"), { recursive: true });
3446
+ await mkdir3(path12.join(input.baseDir, "exe", "output"), { recursive: true });
3447
+ await mkdir3(path12.join(input.baseDir, "exe", "research"), { recursive: true });
2712
3448
  await ensureArchitectureDoc(input.baseDir, input.projectName);
2713
3449
  await ensureGitignoreExe(input.baseDir);
2714
3450
  } catch {
@@ -2744,13 +3480,19 @@ ${laneWarning}` : laneWarning;
2744
3480
  });
2745
3481
  if (input.baseDir) {
2746
3482
  try {
2747
- const EXE_OS_DIR = path10.join(os7.homedir(), ".exe-os");
2748
- const mdPath = path10.join(EXE_OS_DIR, taskFile);
2749
- const mdDir = path10.dirname(mdPath);
2750
- if (!existsSync10(mdDir)) await mkdir3(mdDir, { recursive: true });
3483
+ const EXE_OS_DIR = path12.join(os9.homedir(), ".exe-os");
3484
+ const mdPath = path12.join(EXE_OS_DIR, taskFile);
3485
+ const mdDir = path12.dirname(mdPath);
3486
+ if (!existsSync12(mdDir)) await mkdir3(mdDir, { recursive: true });
2751
3487
  const reviewer = input.reviewer ?? input.assignedBy;
2752
3488
  const mdContent = `# ${input.title}
2753
3489
 
3490
+ ## MANDATORY: When done
3491
+
3492
+ You MUST call update_task with status "done" and a result summary when finished.
3493
+ If you skip this, your reviewer will not know you're done and your work won't be reviewed.
3494
+ Do NOT let a failed commit or any error prevent you from calling update_task(done).
3495
+
2754
3496
  **ID:** ${id}
2755
3497
  **Status:** ${initialStatus}
2756
3498
  **Priority:** ${input.priority}
@@ -2764,12 +3506,6 @@ ${laneWarning}` : laneWarning;
2764
3506
  ## Context
2765
3507
 
2766
3508
  ${input.context}
2767
-
2768
- ## MANDATORY: When done
2769
-
2770
- You MUST call update_task with status "done" and a result summary when finished.
2771
- If you skip this, your reviewer will not know you're done and your work won't be reviewed.
2772
- Do NOT let a failed commit or any error prevent you from calling update_task(done).
2773
3509
  `;
2774
3510
  await writeFile3(mdPath, mdContent, "utf-8");
2775
3511
  } catch (err) {
@@ -3018,7 +3754,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
3018
3754
  await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
3019
3755
  } catch {
3020
3756
  }
3021
- if (input.status === "done" || input.status === "cancelled") {
3757
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
3022
3758
  try {
3023
3759
  const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
3024
3760
  clearQueueForAgent2(String(row.assigned_to));
@@ -3047,9 +3783,9 @@ async function deleteTaskCore(taskId, _baseDir) {
3047
3783
  return { taskFile, assignedTo, assignedBy, taskSlug };
3048
3784
  }
3049
3785
  async function ensureArchitectureDoc(baseDir, projectName) {
3050
- const archPath = path10.join(baseDir, "exe", "ARCHITECTURE.md");
3786
+ const archPath = path12.join(baseDir, "exe", "ARCHITECTURE.md");
3051
3787
  try {
3052
- if (existsSync10(archPath)) return;
3788
+ if (existsSync12(archPath)) return;
3053
3789
  const template = [
3054
3790
  `# ${projectName} \u2014 System Architecture`,
3055
3791
  "",
@@ -3082,10 +3818,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
3082
3818
  }
3083
3819
  }
3084
3820
  async function ensureGitignoreExe(baseDir) {
3085
- const gitignorePath = path10.join(baseDir, ".gitignore");
3821
+ const gitignorePath = path12.join(baseDir, ".gitignore");
3086
3822
  try {
3087
- if (existsSync10(gitignorePath)) {
3088
- const content = readFileSync10(gitignorePath, "utf-8");
3823
+ if (existsSync12(gitignorePath)) {
3824
+ const content = readFileSync11(gitignorePath, "utf-8");
3089
3825
  if (/^\/?exe\/?$/m.test(content)) return;
3090
3826
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
3091
3827
  } else {
@@ -3116,58 +3852,42 @@ var init_tasks_crud = __esm({
3116
3852
  });
3117
3853
 
3118
3854
  // src/lib/tasks-review.ts
3119
- import path11 from "path";
3120
- import { existsSync as existsSync11, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
3855
+ import path13 from "path";
3856
+ import { existsSync as existsSync13, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
3121
3857
  async function countPendingReviews(sessionScope) {
3122
3858
  const client = getClient();
3123
- if (sessionScope) {
3124
- const result2 = await client.execute({
3125
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review' AND (session_scope = ? OR session_scope IS NULL)",
3126
- args: [sessionScope]
3127
- });
3128
- return Number(result2.rows[0]?.cnt) || 0;
3129
- }
3859
+ const scope = strictSessionScopeFilter(
3860
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
3861
+ );
3130
3862
  const result = await client.execute({
3131
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review'",
3132
- args: []
3863
+ sql: `SELECT COUNT(*) as cnt FROM tasks
3864
+ WHERE status = 'needs_review'${scope.sql}`,
3865
+ args: [...scope.args]
3133
3866
  });
3134
3867
  return Number(result.rows[0]?.cnt) || 0;
3135
3868
  }
3136
3869
  async function countNewPendingReviewsSince(sinceIso, sessionScope) {
3137
3870
  const client = getClient();
3138
- if (sessionScope) {
3139
- const result2 = await client.execute({
3140
- sql: `SELECT COUNT(*) as cnt FROM tasks
3141
- WHERE status = 'needs_review' AND updated_at > ?
3142
- AND session_scope = ?`,
3143
- args: [sinceIso, sessionScope]
3144
- });
3145
- return Number(result2.rows[0]?.cnt) || 0;
3146
- }
3871
+ const scope = strictSessionScopeFilter(
3872
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
3873
+ );
3147
3874
  const result = await client.execute({
3148
3875
  sql: `SELECT COUNT(*) as cnt FROM tasks
3149
- WHERE status = 'needs_review' AND updated_at > ?`,
3150
- args: [sinceIso]
3876
+ WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
3877
+ args: [sinceIso, ...scope.args]
3151
3878
  });
3152
3879
  return Number(result.rows[0]?.cnt) || 0;
3153
3880
  }
3154
3881
  async function listPendingReviews(limit, sessionScope) {
3155
3882
  const client = getClient();
3156
- if (sessionScope) {
3157
- const result2 = await client.execute({
3158
- sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
3159
- WHERE status = 'needs_review'
3160
- AND session_scope = ?
3161
- ORDER BY updated_at ASC LIMIT ?`,
3162
- args: [sessionScope, limit]
3163
- });
3164
- return result2.rows;
3165
- }
3883
+ const scope = strictSessionScopeFilter(
3884
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
3885
+ );
3166
3886
  const result = await client.execute({
3167
3887
  sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
3168
- WHERE status = 'needs_review'
3888
+ WHERE status = 'needs_review'${scope.sql}
3169
3889
  ORDER BY updated_at ASC LIMIT ?`,
3170
- args: [limit]
3890
+ args: [...scope.args, limit]
3171
3891
  });
3172
3892
  return result.rows;
3173
3893
  }
@@ -3179,7 +3899,7 @@ async function cleanupOrphanedReviews() {
3179
3899
  WHERE status IN ('open', 'needs_review', 'in_progress')
3180
3900
  AND assigned_by = 'system'
3181
3901
  AND title LIKE 'Review:%'
3182
- AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
3902
+ AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
3183
3903
  args: [now]
3184
3904
  });
3185
3905
  const r1b = await client.execute({
@@ -3298,11 +4018,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
3298
4018
  );
3299
4019
  }
3300
4020
  try {
3301
- const cacheDir = path11.join(EXE_AI_DIR, "session-cache");
3302
- if (existsSync11(cacheDir)) {
4021
+ const cacheDir = path13.join(EXE_AI_DIR, "session-cache");
4022
+ if (existsSync13(cacheDir)) {
3303
4023
  for (const f of readdirSync2(cacheDir)) {
3304
4024
  if (f.startsWith("review-notified-")) {
3305
- unlinkSync4(path11.join(cacheDir, f));
4025
+ unlinkSync4(path13.join(cacheDir, f));
3306
4026
  }
3307
4027
  }
3308
4028
  }
@@ -3319,11 +4039,12 @@ var init_tasks_review = __esm({
3319
4039
  init_tmux_routing();
3320
4040
  init_session_key();
3321
4041
  init_state_bus();
4042
+ init_task_scope();
3322
4043
  }
3323
4044
  });
3324
4045
 
3325
4046
  // src/lib/tasks-chain.ts
3326
- import path12 from "path";
4047
+ import path14 from "path";
3327
4048
  import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
3328
4049
  async function cascadeUnblock(taskId, baseDir, now) {
3329
4050
  const client = getClient();
@@ -3340,7 +4061,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
3340
4061
  });
3341
4062
  for (const ur of unblockedRows.rows) {
3342
4063
  try {
3343
- const ubFile = path12.join(baseDir, String(ur.task_file));
4064
+ const ubFile = path14.join(baseDir, String(ur.task_file));
3344
4065
  let ubContent = await readFile3(ubFile, "utf-8");
3345
4066
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
3346
4067
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -3375,7 +4096,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
3375
4096
  const scScope = sessionScopeFilter();
3376
4097
  const remaining = await client.execute({
3377
4098
  sql: `SELECT COUNT(*) as cnt FROM tasks
3378
- WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
4099
+ WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
3379
4100
  args: [parentTaskId, ...scScope.args]
3380
4101
  });
3381
4102
  const cnt = Number(remaining.rows[0]?.cnt ?? 1);
@@ -3414,7 +4135,7 @@ __export(project_name_exports, {
3414
4135
  getProjectName: () => getProjectName
3415
4136
  });
3416
4137
  import { execSync as execSync5 } from "child_process";
3417
- import path13 from "path";
4138
+ import path15 from "path";
3418
4139
  function getProjectName(cwd) {
3419
4140
  const dir = cwd ?? process.cwd();
3420
4141
  if (_cached2 && _cachedCwd === dir) return _cached2;
@@ -3427,7 +4148,7 @@ function getProjectName(cwd) {
3427
4148
  timeout: 2e3,
3428
4149
  stdio: ["pipe", "pipe", "pipe"]
3429
4150
  }).trim();
3430
- repoRoot = path13.dirname(gitCommonDir);
4151
+ repoRoot = path15.dirname(gitCommonDir);
3431
4152
  } catch {
3432
4153
  repoRoot = execSync5("git rev-parse --show-toplevel", {
3433
4154
  cwd: dir,
@@ -3436,11 +4157,11 @@ function getProjectName(cwd) {
3436
4157
  stdio: ["pipe", "pipe", "pipe"]
3437
4158
  }).trim();
3438
4159
  }
3439
- _cached2 = path13.basename(repoRoot);
4160
+ _cached2 = path15.basename(repoRoot);
3440
4161
  _cachedCwd = dir;
3441
4162
  return _cached2;
3442
4163
  } catch {
3443
- _cached2 = path13.basename(dir);
4164
+ _cached2 = path15.basename(dir);
3444
4165
  _cachedCwd = dir;
3445
4166
  return _cached2;
3446
4167
  }
@@ -3587,10 +4308,10 @@ var init_tasks_notify = __esm({
3587
4308
  });
3588
4309
 
3589
4310
  // src/lib/behaviors.ts
3590
- import crypto4 from "crypto";
4311
+ import crypto5 from "crypto";
3591
4312
  async function storeBehavior(opts) {
3592
4313
  const client = getClient();
3593
- const id = crypto4.randomUUID();
4314
+ const id = crypto5.randomUUID();
3594
4315
  const now = (/* @__PURE__ */ new Date()).toISOString();
3595
4316
  await client.execute({
3596
4317
  sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
@@ -3619,7 +4340,7 @@ __export(skill_learning_exports, {
3619
4340
  storeTrajectory: () => storeTrajectory,
3620
4341
  sweepTrajectories: () => sweepTrajectories
3621
4342
  });
3622
- import crypto5 from "crypto";
4343
+ import crypto6 from "crypto";
3623
4344
  async function extractTrajectory(taskId, agentId) {
3624
4345
  const client = getClient();
3625
4346
  const result = await client.execute({
@@ -3648,11 +4369,11 @@ async function extractTrajectory(taskId, agentId) {
3648
4369
  return signature;
3649
4370
  }
3650
4371
  function hashSignature(signature) {
3651
- return crypto5.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
4372
+ return crypto6.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
3652
4373
  }
3653
4374
  async function storeTrajectory(opts) {
3654
4375
  const client = getClient();
3655
- const id = crypto5.randomUUID();
4376
+ const id = crypto6.randomUUID();
3656
4377
  const now = (/* @__PURE__ */ new Date()).toISOString();
3657
4378
  const signatureHash = hashSignature(opts.signature);
3658
4379
  await client.execute({
@@ -3917,8 +4638,8 @@ __export(tasks_exports, {
3917
4638
  updateTaskStatus: () => updateTaskStatus,
3918
4639
  writeCheckpoint: () => writeCheckpoint
3919
4640
  });
3920
- import path14 from "path";
3921
- import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
4641
+ import path16 from "path";
4642
+ import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
3922
4643
  async function createTask(input) {
3923
4644
  const result = await createTaskCore(input);
3924
4645
  if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -3937,12 +4658,12 @@ async function updateTask(input) {
3937
4658
  const { row, taskFile, now, taskId } = await updateTaskStatus(input);
3938
4659
  try {
3939
4660
  const agent = String(row.assigned_to);
3940
- const cacheDir = path14.join(EXE_AI_DIR, "session-cache");
3941
- const cachePath = path14.join(cacheDir, `current-task-${agent}.json`);
4661
+ const cacheDir = path16.join(EXE_AI_DIR, "session-cache");
4662
+ const cachePath = path16.join(cacheDir, `current-task-${agent}.json`);
3942
4663
  if (input.status === "in_progress") {
3943
4664
  mkdirSync5(cacheDir, { recursive: true });
3944
- writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
3945
- } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
4665
+ writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
4666
+ } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
3946
4667
  try {
3947
4668
  unlinkSync5(cachePath);
3948
4669
  } catch {
@@ -3950,10 +4671,10 @@ async function updateTask(input) {
3950
4671
  }
3951
4672
  } catch {
3952
4673
  }
3953
- if (input.status === "done") {
4674
+ if (input.status === "done" || input.status === "closed") {
3954
4675
  await cleanupReviewFile(row, taskFile, input.baseDir);
3955
4676
  }
3956
- if (input.status === "done" || input.status === "cancelled") {
4677
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
3957
4678
  try {
3958
4679
  const client = getClient();
3959
4680
  const taskTitle = String(row.title);
@@ -3969,7 +4690,7 @@ async function updateTask(input) {
3969
4690
  if (!isCoordinatorName(assignedAgent)) {
3970
4691
  try {
3971
4692
  const draftClient = getClient();
3972
- if (input.status === "done") {
4693
+ if (input.status === "done" || input.status === "closed") {
3973
4694
  await draftClient.execute({
3974
4695
  sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
3975
4696
  args: [assignedAgent]
@@ -3986,7 +4707,7 @@ async function updateTask(input) {
3986
4707
  try {
3987
4708
  const client = getClient();
3988
4709
  const cascaded = await client.execute({
3989
- sql: `UPDATE tasks SET status = 'done', updated_at = ?
4710
+ sql: `UPDATE tasks SET status = 'closed', updated_at = ?
3990
4711
  WHERE parent_task_id = ? AND status = 'needs_review'`,
3991
4712
  args: [now, taskId]
3992
4713
  });
@@ -3999,14 +4720,14 @@ async function updateTask(input) {
3999
4720
  } catch {
4000
4721
  }
4001
4722
  }
4002
- const isTerminal = input.status === "done" || input.status === "needs_review";
4723
+ const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
4003
4724
  if (isTerminal) {
4004
4725
  const isCoordinator = isCoordinatorName(String(row.assigned_to));
4005
4726
  if (!isCoordinator) {
4006
4727
  notifyTaskDone();
4007
4728
  }
4008
4729
  await markTaskNotificationsRead(taskFile);
4009
- if (input.status === "done") {
4730
+ if (input.status === "done" || input.status === "closed") {
4010
4731
  try {
4011
4732
  await cascadeUnblock(taskId, input.baseDir, now);
4012
4733
  } catch {
@@ -4026,7 +4747,7 @@ async function updateTask(input) {
4026
4747
  }
4027
4748
  }
4028
4749
  }
4029
- if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
4750
+ if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
4030
4751
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
4031
4752
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
4032
4753
  taskId,
@@ -4398,6 +5119,7 @@ __export(tmux_routing_exports, {
4398
5119
  isEmployeeAlive: () => isEmployeeAlive,
4399
5120
  isExeSession: () => isExeSession,
4400
5121
  isSessionBusy: () => isSessionBusy,
5122
+ notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
4401
5123
  notifyParentExe: () => notifyParentExe,
4402
5124
  parseParentExe: () => parseParentExe,
4403
5125
  registerParentExe: () => registerParentExe,
@@ -4408,13 +5130,13 @@ __export(tmux_routing_exports, {
4408
5130
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
4409
5131
  });
4410
5132
  import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
4411
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync12, appendFileSync, readdirSync as readdirSync3 } from "fs";
4412
- import path15 from "path";
4413
- import os8 from "os";
5133
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, mkdirSync as mkdirSync6, existsSync as existsSync14, appendFileSync, readdirSync as readdirSync3 } from "fs";
5134
+ import path17 from "path";
5135
+ import os10 from "os";
4414
5136
  import { fileURLToPath as fileURLToPath3 } from "url";
4415
5137
  import { unlinkSync as unlinkSync6 } from "fs";
4416
5138
  function spawnLockPath(sessionName) {
4417
- return path15.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
5139
+ return path17.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
4418
5140
  }
4419
5141
  function isProcessAlive(pid) {
4420
5142
  try {
@@ -4425,13 +5147,13 @@ function isProcessAlive(pid) {
4425
5147
  }
4426
5148
  }
4427
5149
  function acquireSpawnLock2(sessionName) {
4428
- if (!existsSync12(SPAWN_LOCK_DIR)) {
5150
+ if (!existsSync14(SPAWN_LOCK_DIR)) {
4429
5151
  mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
4430
5152
  }
4431
5153
  const lockFile = spawnLockPath(sessionName);
4432
- if (existsSync12(lockFile)) {
5154
+ if (existsSync14(lockFile)) {
4433
5155
  try {
4434
- const lock = JSON.parse(readFileSync11(lockFile, "utf8"));
5156
+ const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
4435
5157
  const age = Date.now() - lock.timestamp;
4436
5158
  if (isProcessAlive(lock.pid) && age < 6e4) {
4437
5159
  return false;
@@ -4439,7 +5161,7 @@ function acquireSpawnLock2(sessionName) {
4439
5161
  } catch {
4440
5162
  }
4441
5163
  }
4442
- writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
5164
+ writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
4443
5165
  return true;
4444
5166
  }
4445
5167
  function releaseSpawnLock2(sessionName) {
@@ -4451,13 +5173,13 @@ function releaseSpawnLock2(sessionName) {
4451
5173
  function resolveBehaviorsExporterScript() {
4452
5174
  try {
4453
5175
  const thisFile = fileURLToPath3(import.meta.url);
4454
- const scriptPath = path15.join(
4455
- path15.dirname(thisFile),
5176
+ const scriptPath = path17.join(
5177
+ path17.dirname(thisFile),
4456
5178
  "..",
4457
5179
  "bin",
4458
5180
  "exe-export-behaviors.js"
4459
5181
  );
4460
- return existsSync12(scriptPath) ? scriptPath : null;
5182
+ return existsSync14(scriptPath) ? scriptPath : null;
4461
5183
  } catch {
4462
5184
  return null;
4463
5185
  }
@@ -4523,12 +5245,12 @@ function extractRootExe(name) {
4523
5245
  return parts.length > 0 ? parts[parts.length - 1] : null;
4524
5246
  }
4525
5247
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
4526
- if (!existsSync12(SESSION_CACHE)) {
5248
+ if (!existsSync14(SESSION_CACHE)) {
4527
5249
  mkdirSync6(SESSION_CACHE, { recursive: true });
4528
5250
  }
4529
5251
  const rootExe = extractRootExe(parentExe) ?? parentExe;
4530
- const filePath = path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
4531
- writeFileSync7(filePath, JSON.stringify({
5252
+ const filePath = path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
5253
+ writeFileSync8(filePath, JSON.stringify({
4532
5254
  parentExe: rootExe,
4533
5255
  dispatchedBy: dispatchedBy || rootExe,
4534
5256
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -4536,7 +5258,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
4536
5258
  }
4537
5259
  function getParentExe(sessionKey) {
4538
5260
  try {
4539
- const data = JSON.parse(readFileSync11(path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
5261
+ const data = JSON.parse(readFileSync12(path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
4540
5262
  return data.parentExe || null;
4541
5263
  } catch {
4542
5264
  return null;
@@ -4544,8 +5266,8 @@ function getParentExe(sessionKey) {
4544
5266
  }
4545
5267
  function getDispatchedBy(sessionKey) {
4546
5268
  try {
4547
- const data = JSON.parse(readFileSync11(
4548
- path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
5269
+ const data = JSON.parse(readFileSync12(
5270
+ path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
4549
5271
  "utf8"
4550
5272
  ));
4551
5273
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -4615,8 +5337,8 @@ async function verifyPaneAtCapacity(sessionName) {
4615
5337
  }
4616
5338
  function readDebounceState() {
4617
5339
  try {
4618
- if (!existsSync12(DEBOUNCE_FILE)) return {};
4619
- const raw = JSON.parse(readFileSync11(DEBOUNCE_FILE, "utf8"));
5340
+ if (!existsSync14(DEBOUNCE_FILE)) return {};
5341
+ const raw = JSON.parse(readFileSync12(DEBOUNCE_FILE, "utf8"));
4620
5342
  const state = {};
4621
5343
  for (const [key, val] of Object.entries(raw)) {
4622
5344
  if (typeof val === "number") {
@@ -4632,8 +5354,8 @@ function readDebounceState() {
4632
5354
  }
4633
5355
  function writeDebounceState(state) {
4634
5356
  try {
4635
- if (!existsSync12(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
4636
- writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
5357
+ if (!existsSync14(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
5358
+ writeFileSync8(DEBOUNCE_FILE, JSON.stringify(state));
4637
5359
  } catch {
4638
5360
  }
4639
5361
  }
@@ -4731,8 +5453,8 @@ function sendIntercom(targetSession) {
4731
5453
  try {
4732
5454
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
4733
5455
  const agent = baseAgentName(rawAgent);
4734
- const markerPath = path15.join(SESSION_CACHE, `current-task-${agent}.json`);
4735
- if (existsSync12(markerPath)) {
5456
+ const markerPath = path17.join(SESSION_CACHE, `current-task-${agent}.json`);
5457
+ if (existsSync14(markerPath)) {
4736
5458
  logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
4737
5459
  return "debounced";
4738
5460
  }
@@ -4741,8 +5463,8 @@ function sendIntercom(targetSession) {
4741
5463
  try {
4742
5464
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
4743
5465
  const agent = baseAgentName(rawAgent);
4744
- const taskDir = path15.join(process.cwd(), "exe", agent);
4745
- if (existsSync12(taskDir)) {
5466
+ const taskDir = path17.join(process.cwd(), "exe", agent);
5467
+ if (existsSync14(taskDir)) {
4746
5468
  const files = readdirSync3(taskDir).filter(
4747
5469
  (f) => f.endsWith(".md") && f !== "DONE.txt"
4748
5470
  );
@@ -4802,6 +5524,21 @@ function notifyParentExe(sessionKey) {
4802
5524
  }
4803
5525
  return true;
4804
5526
  }
5527
+ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
5528
+ const transport = getTransport();
5529
+ try {
5530
+ const sessions = transport.listSessions();
5531
+ if (!sessions.includes(coordinatorSession)) return false;
5532
+ execSync6(
5533
+ `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
5534
+ { timeout: 3e3 }
5535
+ );
5536
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
5537
+ return true;
5538
+ } catch {
5539
+ return false;
5540
+ }
5541
+ }
4805
5542
  function ensureEmployee(employeeName, exeSession, projectDir, opts) {
4806
5543
  if (isCoordinatorName(employeeName)) {
4807
5544
  return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
@@ -4875,26 +5612,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4875
5612
  const transport = getTransport();
4876
5613
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
4877
5614
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
4878
- const logDir = path15.join(os8.homedir(), ".exe-os", "session-logs");
4879
- const logFile = path15.join(logDir, `${instanceLabel}-${Date.now()}.log`);
4880
- if (!existsSync12(logDir)) {
5615
+ const logDir = path17.join(os10.homedir(), ".exe-os", "session-logs");
5616
+ const logFile = path17.join(logDir, `${instanceLabel}-${Date.now()}.log`);
5617
+ if (!existsSync14(logDir)) {
4881
5618
  mkdirSync6(logDir, { recursive: true });
4882
5619
  }
4883
5620
  transport.kill(sessionName);
4884
5621
  let cleanupSuffix = "";
4885
5622
  try {
4886
5623
  const thisFile = fileURLToPath3(import.meta.url);
4887
- const cleanupScript = path15.join(path15.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
4888
- if (existsSync12(cleanupScript)) {
5624
+ const cleanupScript = path17.join(path17.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
5625
+ if (existsSync14(cleanupScript)) {
4889
5626
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
4890
5627
  }
4891
5628
  } catch {
4892
5629
  }
4893
5630
  try {
4894
- const claudeJsonPath = path15.join(os8.homedir(), ".claude.json");
5631
+ const claudeJsonPath = path17.join(os10.homedir(), ".claude.json");
4895
5632
  let claudeJson = {};
4896
5633
  try {
4897
- claudeJson = JSON.parse(readFileSync11(claudeJsonPath, "utf8"));
5634
+ claudeJson = JSON.parse(readFileSync12(claudeJsonPath, "utf8"));
4898
5635
  } catch {
4899
5636
  }
4900
5637
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -4902,17 +5639,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4902
5639
  const trustDir = opts?.cwd ?? projectDir;
4903
5640
  if (!projects[trustDir]) projects[trustDir] = {};
4904
5641
  projects[trustDir].hasTrustDialogAccepted = true;
4905
- writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
5642
+ writeFileSync8(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
4906
5643
  } catch {
4907
5644
  }
4908
5645
  try {
4909
- const settingsDir = path15.join(os8.homedir(), ".claude", "projects");
5646
+ const settingsDir = path17.join(os10.homedir(), ".claude", "projects");
4910
5647
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
4911
- const projSettingsDir = path15.join(settingsDir, normalizedKey);
4912
- const settingsPath = path15.join(projSettingsDir, "settings.json");
5648
+ const projSettingsDir = path17.join(settingsDir, normalizedKey);
5649
+ const settingsPath = path17.join(projSettingsDir, "settings.json");
4913
5650
  let settings = {};
4914
5651
  try {
4915
- settings = JSON.parse(readFileSync11(settingsPath, "utf8"));
5652
+ settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
4916
5653
  } catch {
4917
5654
  }
4918
5655
  const perms = settings.permissions ?? {};
@@ -4941,7 +5678,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4941
5678
  perms.allow = allow;
4942
5679
  settings.permissions = perms;
4943
5680
  mkdirSync6(projSettingsDir, { recursive: true });
4944
- writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + "\n");
5681
+ writeFileSync8(settingsPath, JSON.stringify(settings, null, 2) + "\n");
4945
5682
  }
4946
5683
  } catch {
4947
5684
  }
@@ -4956,8 +5693,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4956
5693
  let behaviorsFlag = "";
4957
5694
  let legacyFallbackWarned = false;
4958
5695
  if (!useExeAgent && !useBinSymlink) {
4959
- const identityPath = path15.join(
4960
- os8.homedir(),
5696
+ const identityPath = path17.join(
5697
+ os10.homedir(),
4961
5698
  ".exe-os",
4962
5699
  "identity",
4963
5700
  `${employeeName}.md`
@@ -4966,13 +5703,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4966
5703
  const hasAgentFlag = claudeSupportsAgentFlag();
4967
5704
  if (hasAgentFlag) {
4968
5705
  identityFlag = ` --agent ${employeeName}`;
4969
- } else if (existsSync12(identityPath)) {
5706
+ } else if (existsSync14(identityPath)) {
4970
5707
  identityFlag = ` --append-system-prompt-file ${identityPath}`;
4971
5708
  legacyFallbackWarned = true;
4972
5709
  }
4973
5710
  const behaviorsFile = exportBehaviorsSync(
4974
5711
  employeeName,
4975
- path15.basename(spawnCwd),
5712
+ path17.basename(spawnCwd),
4976
5713
  sessionName
4977
5714
  );
4978
5715
  if (behaviorsFile) {
@@ -4987,16 +5724,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4987
5724
  }
4988
5725
  let sessionContextFlag = "";
4989
5726
  try {
4990
- const ctxDir = path15.join(os8.homedir(), ".exe-os", "session-cache");
5727
+ const ctxDir = path17.join(os10.homedir(), ".exe-os", "session-cache");
4991
5728
  mkdirSync6(ctxDir, { recursive: true });
4992
- const ctxFile = path15.join(ctxDir, `session-context-${sessionName}.md`);
5729
+ const ctxFile = path17.join(ctxDir, `session-context-${sessionName}.md`);
4993
5730
  const ctxContent = [
4994
5731
  `## Session Context`,
4995
5732
  `You are running in tmux session: ${sessionName}.`,
4996
5733
  `Your parent coordinator session is ${exeSession}.`,
4997
5734
  `Your employees (if any) use the -${exeSession} suffix.`
4998
5735
  ].join("\n");
4999
- writeFileSync7(ctxFile, ctxContent);
5736
+ writeFileSync8(ctxFile, ctxContent);
5000
5737
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
5001
5738
  } catch {
5002
5739
  }
@@ -5073,8 +5810,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5073
5810
  transport.pipeLog(sessionName, logFile);
5074
5811
  try {
5075
5812
  const mySession = getMySession();
5076
- const dispatchInfo = path15.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
5077
- writeFileSync7(dispatchInfo, JSON.stringify({
5813
+ const dispatchInfo = path17.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
5814
+ writeFileSync8(dispatchInfo, JSON.stringify({
5078
5815
  dispatchedBy: mySession,
5079
5816
  rootExe: exeSession,
5080
5817
  provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
@@ -5148,15 +5885,15 @@ var init_tmux_routing = __esm({
5148
5885
  init_intercom_queue();
5149
5886
  init_plan_limits();
5150
5887
  init_employees();
5151
- SPAWN_LOCK_DIR = path15.join(os8.homedir(), ".exe-os", "spawn-locks");
5152
- SESSION_CACHE = path15.join(os8.homedir(), ".exe-os", "session-cache");
5888
+ SPAWN_LOCK_DIR = path17.join(os10.homedir(), ".exe-os", "spawn-locks");
5889
+ SESSION_CACHE = path17.join(os10.homedir(), ".exe-os", "session-cache");
5153
5890
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
5154
5891
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
5155
5892
  VERIFY_PANE_LINES = 200;
5156
5893
  INTERCOM_DEBOUNCE_MS = 3e4;
5157
5894
  CODEX_DEBOUNCE_MS = 12e4;
5158
- INTERCOM_LOG2 = path15.join(os8.homedir(), ".exe-os", "intercom.log");
5159
- DEBOUNCE_FILE = path15.join(SESSION_CACHE, "intercom-debounce.json");
5895
+ INTERCOM_LOG2 = path17.join(os10.homedir(), ".exe-os", "intercom.log");
5896
+ DEBOUNCE_FILE = path17.join(SESSION_CACHE, "intercom-debounce.json");
5160
5897
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
5161
5898
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
5162
5899
  }
@@ -5179,6 +5916,15 @@ function sessionScopeFilter(sessionScope, tableAlias) {
5179
5916
  args: [scope]
5180
5917
  };
5181
5918
  }
5919
+ function strictSessionScopeFilter(sessionScope, tableAlias) {
5920
+ const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
5921
+ if (!scope) return { sql: "", args: [] };
5922
+ const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
5923
+ return {
5924
+ sql: ` AND ${col} = ?`,
5925
+ args: [scope]
5926
+ };
5927
+ }
5182
5928
  var init_task_scope = __esm({
5183
5929
  "src/lib/task-scope.ts"() {
5184
5930
  "use strict";
@@ -5197,14 +5943,14 @@ var init_memory = __esm({
5197
5943
 
5198
5944
  // src/lib/keychain.ts
5199
5945
  import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
5200
- import { existsSync as existsSync13 } from "fs";
5201
- import path16 from "path";
5202
- import os9 from "os";
5946
+ import { existsSync as existsSync15 } from "fs";
5947
+ import path18 from "path";
5948
+ import os11 from "os";
5203
5949
  function getKeyDir() {
5204
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path16.join(os9.homedir(), ".exe-os");
5950
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path18.join(os11.homedir(), ".exe-os");
5205
5951
  }
5206
5952
  function getKeyPath() {
5207
- return path16.join(getKeyDir(), "master.key");
5953
+ return path18.join(getKeyDir(), "master.key");
5208
5954
  }
5209
5955
  async function tryKeytar() {
5210
5956
  try {
@@ -5225,9 +5971,9 @@ async function getMasterKey() {
5225
5971
  }
5226
5972
  }
5227
5973
  const keyPath = getKeyPath();
5228
- if (!existsSync13(keyPath)) {
5974
+ if (!existsSync15(keyPath)) {
5229
5975
  process.stderr.write(
5230
- `[keychain] Key not found at ${keyPath} (HOME=${os9.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
5976
+ `[keychain] Key not found at ${keyPath} (HOME=${os11.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
5231
5977
  `
5232
5978
  );
5233
5979
  return null;
@@ -5257,6 +6003,7 @@ var shard_manager_exports = {};
5257
6003
  __export(shard_manager_exports, {
5258
6004
  disposeShards: () => disposeShards,
5259
6005
  ensureShardSchema: () => ensureShardSchema,
6006
+ getOpenShardCount: () => getOpenShardCount,
5260
6007
  getReadyShardClient: () => getReadyShardClient,
5261
6008
  getShardClient: () => getShardClient,
5262
6009
  getShardsDir: () => getShardsDir,
@@ -5265,15 +6012,18 @@ __export(shard_manager_exports, {
5265
6012
  listShards: () => listShards,
5266
6013
  shardExists: () => shardExists
5267
6014
  });
5268
- import path17 from "path";
5269
- import { existsSync as existsSync14, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
6015
+ import path19 from "path";
6016
+ import { existsSync as existsSync16, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
5270
6017
  import { createClient as createClient2 } from "@libsql/client";
5271
6018
  function initShardManager(encryptionKey) {
5272
6019
  _encryptionKey = encryptionKey;
5273
- if (!existsSync14(SHARDS_DIR)) {
6020
+ if (!existsSync16(SHARDS_DIR)) {
5274
6021
  mkdirSync7(SHARDS_DIR, { recursive: true });
5275
6022
  }
5276
6023
  _shardingEnabled = true;
6024
+ if (_evictionTimer) clearInterval(_evictionTimer);
6025
+ _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
6026
+ _evictionTimer.unref();
5277
6027
  }
5278
6028
  function isShardingEnabled() {
5279
6029
  return _shardingEnabled;
@@ -5290,21 +6040,28 @@ function getShardClient(projectName) {
5290
6040
  throw new Error(`Invalid project name for shard: "${projectName}"`);
5291
6041
  }
5292
6042
  const cached = _shards.get(safeName);
5293
- if (cached) return cached;
5294
- const dbPath = path17.join(SHARDS_DIR, `${safeName}.db`);
6043
+ if (cached) {
6044
+ _shardLastAccess.set(safeName, Date.now());
6045
+ return cached;
6046
+ }
6047
+ while (_shards.size >= MAX_OPEN_SHARDS) {
6048
+ evictLRU();
6049
+ }
6050
+ const dbPath = path19.join(SHARDS_DIR, `${safeName}.db`);
5295
6051
  const client = createClient2({
5296
6052
  url: `file:${dbPath}`,
5297
6053
  encryptionKey: _encryptionKey
5298
6054
  });
5299
6055
  _shards.set(safeName, client);
6056
+ _shardLastAccess.set(safeName, Date.now());
5300
6057
  return client;
5301
6058
  }
5302
6059
  function shardExists(projectName) {
5303
6060
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
5304
- return existsSync14(path17.join(SHARDS_DIR, `${safeName}.db`));
6061
+ return existsSync16(path19.join(SHARDS_DIR, `${safeName}.db`));
5305
6062
  }
5306
6063
  function listShards() {
5307
- if (!existsSync14(SHARDS_DIR)) return [];
6064
+ if (!existsSync16(SHARDS_DIR)) return [];
5308
6065
  return readdirSync4(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
5309
6066
  }
5310
6067
  async function ensureShardSchema(client) {
@@ -5356,6 +6113,8 @@ async function ensureShardSchema(client) {
5356
6113
  for (const col of [
5357
6114
  "ALTER TABLE memories ADD COLUMN task_id TEXT",
5358
6115
  "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
6116
+ "ALTER TABLE memories ADD COLUMN author_device_id TEXT",
6117
+ "ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
5359
6118
  "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
5360
6119
  "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
5361
6120
  "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
@@ -5378,7 +6137,23 @@ async function ensureShardSchema(client) {
5378
6137
  // MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
5379
6138
  "ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
5380
6139
  "ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
5381
- "ALTER TABLE memories ADD COLUMN trajectory TEXT"
6140
+ "ALTER TABLE memories ADD COLUMN trajectory TEXT",
6141
+ // Metadata enrichment columns (must match database.ts)
6142
+ "ALTER TABLE memories ADD COLUMN intent TEXT",
6143
+ "ALTER TABLE memories ADD COLUMN outcome TEXT",
6144
+ "ALTER TABLE memories ADD COLUMN domain TEXT",
6145
+ "ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
6146
+ "ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
6147
+ "ALTER TABLE memories ADD COLUMN chain_position TEXT",
6148
+ "ALTER TABLE memories ADD COLUMN review_status TEXT",
6149
+ "ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
6150
+ "ALTER TABLE memories ADD COLUMN file_paths TEXT",
6151
+ "ALTER TABLE memories ADD COLUMN commit_hash TEXT",
6152
+ "ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
6153
+ "ALTER TABLE memories ADD COLUMN token_cost REAL",
6154
+ "ALTER TABLE memories ADD COLUMN audience TEXT",
6155
+ "ALTER TABLE memories ADD COLUMN language_type TEXT",
6156
+ "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
5382
6157
  ]) {
5383
6158
  try {
5384
6159
  await client.execute(col);
@@ -5477,21 +6252,69 @@ async function getReadyShardClient(projectName) {
5477
6252
  await ensureShardSchema(client);
5478
6253
  return client;
5479
6254
  }
6255
+ function evictLRU() {
6256
+ let oldest = null;
6257
+ let oldestTime = Infinity;
6258
+ for (const [name, time] of _shardLastAccess) {
6259
+ if (time < oldestTime) {
6260
+ oldestTime = time;
6261
+ oldest = name;
6262
+ }
6263
+ }
6264
+ if (oldest) {
6265
+ const client = _shards.get(oldest);
6266
+ if (client) {
6267
+ client.close();
6268
+ }
6269
+ _shards.delete(oldest);
6270
+ _shardLastAccess.delete(oldest);
6271
+ }
6272
+ }
6273
+ function evictIdleShards() {
6274
+ const now = Date.now();
6275
+ const toEvict = [];
6276
+ for (const [name, lastAccess] of _shardLastAccess) {
6277
+ if (now - lastAccess > SHARD_IDLE_MS) {
6278
+ toEvict.push(name);
6279
+ }
6280
+ }
6281
+ for (const name of toEvict) {
6282
+ const client = _shards.get(name);
6283
+ if (client) {
6284
+ client.close();
6285
+ }
6286
+ _shards.delete(name);
6287
+ _shardLastAccess.delete(name);
6288
+ }
6289
+ }
6290
+ function getOpenShardCount() {
6291
+ return _shards.size;
6292
+ }
5480
6293
  function disposeShards() {
6294
+ if (_evictionTimer) {
6295
+ clearInterval(_evictionTimer);
6296
+ _evictionTimer = null;
6297
+ }
5481
6298
  for (const [, client] of _shards) {
5482
6299
  client.close();
5483
6300
  }
5484
6301
  _shards.clear();
6302
+ _shardLastAccess.clear();
5485
6303
  _shardingEnabled = false;
5486
6304
  _encryptionKey = null;
5487
6305
  }
5488
- var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
6306
+ var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
5489
6307
  var init_shard_manager = __esm({
5490
6308
  "src/lib/shard-manager.ts"() {
5491
6309
  "use strict";
5492
6310
  init_config();
5493
- SHARDS_DIR = path17.join(EXE_AI_DIR, "shards");
6311
+ SHARDS_DIR = path19.join(EXE_AI_DIR, "shards");
6312
+ SHARD_IDLE_MS = 5 * 60 * 1e3;
6313
+ MAX_OPEN_SHARDS = 10;
6314
+ EVICTION_INTERVAL_MS = 60 * 1e3;
5494
6315
  _shards = /* @__PURE__ */ new Map();
6316
+ _shardLastAccess = /* @__PURE__ */ new Map();
6317
+ _evictionTimer = null;
5495
6318
  _encryptionKey = null;
5496
6319
  _shardingEnabled = false;
5497
6320
  }
@@ -6255,9 +7078,9 @@ var init_store = __esm({
6255
7078
  });
6256
7079
 
6257
7080
  // src/bin/scan-tasks.ts
6258
- import { existsSync as existsSync15, readFileSync as readFileSync12 } from "fs";
6259
- import path18 from "path";
6260
- import os10 from "os";
7081
+ import { existsSync as existsSync17, readFileSync as readFileSync13 } from "fs";
7082
+ import path20 from "path";
7083
+ import os12 from "os";
6261
7084
 
6262
7085
  // src/lib/is-main.ts
6263
7086
  import { realpathSync } from "fs";
@@ -6277,27 +7100,27 @@ function isMainModule(importMetaUrl) {
6277
7100
  // src/bin/scan-tasks.ts
6278
7101
  init_session_key();
6279
7102
  init_task_scope();
6280
- function getMcpHealthWarning(runtime = getSessionRuntime(), homeDir = os10.homedir()) {
7103
+ function getMcpHealthWarning(runtime = getSessionRuntime(), homeDir = os12.homedir()) {
6281
7104
  if (runtime === "codex") {
6282
7105
  return null;
6283
7106
  }
6284
7107
  try {
6285
7108
  if (runtime === "opencode") {
6286
- const opencodeJson = path18.join(homeDir, ".config", "opencode", "opencode.json");
6287
- if (!existsSync15(opencodeJson)) {
7109
+ const opencodeJson = path20.join(homeDir, ".config", "opencode", "opencode.json");
7110
+ if (!existsSync17(opencodeJson)) {
6288
7111
  return "\u26A0\uFE0F MCP config missing (~/.config/opencode/opencode.json not found) \u2014 exe-os task tools may be unavailable. Run `exe-os opencode`.\n";
6289
7112
  }
6290
- const config2 = JSON.parse(readFileSync12(opencodeJson, "utf8"));
7113
+ const config2 = JSON.parse(readFileSync13(opencodeJson, "utf8"));
6291
7114
  if (!config2.mcp?.["exe-os"]?.enabled) {
6292
7115
  return "\u26A0\uFE0F MCP task tools not available \u2014 exe-os server is not enabled in ~/.config/opencode/opencode.json.\n";
6293
7116
  }
6294
7117
  return null;
6295
7118
  }
6296
- const claudeJson = path18.join(homeDir, ".claude.json");
6297
- if (!existsSync15(claudeJson)) {
7119
+ const claudeJson = path20.join(homeDir, ".claude.json");
7120
+ if (!existsSync17(claudeJson)) {
6298
7121
  return "\u26A0\uFE0F MCP config missing (~/.claude.json not found) \u2014 close_task won't work. Run /exe-setup\n";
6299
7122
  }
6300
- const config = JSON.parse(readFileSync12(claudeJson, "utf8"));
7123
+ const config = JSON.parse(readFileSync13(claudeJson, "utf8"));
6301
7124
  const servers = config.mcpServers;
6302
7125
  if (!servers?.["exe-os"] && !servers?.["exe-mem"]) {
6303
7126
  return "\u26A0\uFE0F MCP task tools not available \u2014 exe-os server not configured in ~/.claude.json. close_task won't work.\n";
@@ -6462,7 +7285,7 @@ async function main() {
6462
7285
  if (out) tmuxSession = out;
6463
7286
  } catch {
6464
7287
  }
6465
- const result = await scanFromDb(agentId, tmuxSession);
7288
+ const result = await scanFromDb(agentId, tmuxSession, null);
6466
7289
  switch (format) {
6467
7290
  case "json":
6468
7291
  console.log(formatJson(result));