@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
package/dist/tui/App.js CHANGED
@@ -355,6 +355,44 @@ var init_provider_table = __esm({
355
355
  }
356
356
  });
357
357
 
358
+ // src/lib/secure-files.ts
359
+ import { chmodSync, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
360
+ import { chmod, mkdir } from "fs/promises";
361
+ async function ensurePrivateDir(dirPath) {
362
+ await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
363
+ try {
364
+ await chmod(dirPath, PRIVATE_DIR_MODE);
365
+ } catch {
366
+ }
367
+ }
368
+ function ensurePrivateDirSync(dirPath) {
369
+ mkdirSync2(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
370
+ try {
371
+ chmodSync(dirPath, PRIVATE_DIR_MODE);
372
+ } catch {
373
+ }
374
+ }
375
+ async function enforcePrivateFile(filePath) {
376
+ try {
377
+ await chmod(filePath, PRIVATE_FILE_MODE);
378
+ } catch {
379
+ }
380
+ }
381
+ function enforcePrivateFileSync(filePath) {
382
+ try {
383
+ if (existsSync3(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
384
+ } catch {
385
+ }
386
+ }
387
+ var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
388
+ var init_secure_files = __esm({
389
+ "src/lib/secure-files.ts"() {
390
+ "use strict";
391
+ PRIVATE_DIR_MODE = 448;
392
+ PRIVATE_FILE_MODE = 384;
393
+ }
394
+ });
395
+
358
396
  // src/lib/config.ts
359
397
  var config_exports = {};
360
398
  __export(config_exports, {
@@ -371,8 +409,8 @@ __export(config_exports, {
371
409
  migrateConfig: () => migrateConfig,
372
410
  saveConfig: () => saveConfig
373
411
  });
374
- import { readFile, writeFile, mkdir, chmod } from "fs/promises";
375
- import { readFileSync as readFileSync3, existsSync as existsSync3, renameSync } from "fs";
412
+ import { readFile, writeFile } from "fs/promises";
413
+ import { readFileSync as readFileSync3, existsSync as existsSync4, renameSync } from "fs";
376
414
  import path2 from "path";
377
415
  import os2 from "os";
378
416
  function resolveDataDir() {
@@ -380,7 +418,7 @@ function resolveDataDir() {
380
418
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
381
419
  const newDir = path2.join(os2.homedir(), ".exe-os");
382
420
  const legacyDir = path2.join(os2.homedir(), ".exe-mem");
383
- if (!existsSync3(newDir) && existsSync3(legacyDir)) {
421
+ if (!existsSync4(newDir) && existsSync4(legacyDir)) {
384
422
  try {
385
423
  renameSync(legacyDir, newDir);
386
424
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -443,9 +481,9 @@ function normalizeAutoUpdate(raw) {
443
481
  }
444
482
  async function loadConfig() {
445
483
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
446
- await mkdir(dir, { recursive: true });
484
+ await ensurePrivateDir(dir);
447
485
  const configPath = path2.join(dir, "config.json");
448
- if (!existsSync3(configPath)) {
486
+ if (!existsSync4(configPath)) {
449
487
  return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
450
488
  }
451
489
  const raw = await readFile(configPath, "utf-8");
@@ -458,6 +496,7 @@ async function loadConfig() {
458
496
  `);
459
497
  try {
460
498
  await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
499
+ await enforcePrivateFile(configPath);
461
500
  } catch {
462
501
  }
463
502
  }
@@ -476,7 +515,7 @@ async function loadConfig() {
476
515
  function loadConfigSync() {
477
516
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
478
517
  const configPath = path2.join(dir, "config.json");
479
- if (!existsSync3(configPath)) {
518
+ if (!existsSync4(configPath)) {
480
519
  return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
481
520
  }
482
521
  try {
@@ -494,12 +533,10 @@ function loadConfigSync() {
494
533
  }
495
534
  async function saveConfig(config) {
496
535
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
497
- await mkdir(dir, { recursive: true });
536
+ await ensurePrivateDir(dir);
498
537
  const configPath = path2.join(dir, "config.json");
499
538
  await writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
500
- if (config.cloud?.apiKey) {
501
- await chmod(configPath, 384);
502
- }
539
+ await enforcePrivateFile(configPath);
503
540
  }
504
541
  async function loadConfigFrom(configPath) {
505
542
  const raw = await readFile(configPath, "utf-8");
@@ -519,6 +556,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
519
556
  var init_config = __esm({
520
557
  "src/lib/config.ts"() {
521
558
  "use strict";
559
+ init_secure_files();
522
560
  EXE_AI_DIR = resolveDataDir();
523
561
  DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
524
562
  MODELS_DIR = path2.join(EXE_AI_DIR, "models");
@@ -623,16 +661,34 @@ var init_runtime_table = __esm({
623
661
  });
624
662
 
625
663
  // src/lib/agent-config.ts
626
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
664
+ var agent_config_exports = {};
665
+ __export(agent_config_exports, {
666
+ AGENT_CONFIG_PATH: () => AGENT_CONFIG_PATH,
667
+ DEFAULT_MODELS: () => DEFAULT_MODELS,
668
+ KNOWN_RUNTIMES: () => KNOWN_RUNTIMES,
669
+ RUNTIME_LABELS: () => RUNTIME_LABELS,
670
+ clearAgentRuntime: () => clearAgentRuntime,
671
+ getAgentRuntime: () => getAgentRuntime,
672
+ loadAgentConfig: () => loadAgentConfig,
673
+ saveAgentConfig: () => saveAgentConfig,
674
+ setAgentRuntime: () => setAgentRuntime
675
+ });
676
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync5 } from "fs";
627
677
  import path3 from "path";
628
678
  function loadAgentConfig() {
629
- if (!existsSync4(AGENT_CONFIG_PATH)) return {};
679
+ if (!existsSync5(AGENT_CONFIG_PATH)) return {};
630
680
  try {
631
681
  return JSON.parse(readFileSync4(AGENT_CONFIG_PATH, "utf-8"));
632
682
  } catch {
633
683
  return {};
634
684
  }
635
685
  }
686
+ function saveAgentConfig(config) {
687
+ const dir = path3.dirname(AGENT_CONFIG_PATH);
688
+ ensurePrivateDirSync(dir);
689
+ writeFileSync2(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
690
+ enforcePrivateFileSync(AGENT_CONFIG_PATH);
691
+ }
636
692
  function getAgentRuntime(agentId) {
637
693
  const config = loadAgentConfig();
638
694
  const entry = config[agentId];
@@ -641,13 +697,48 @@ function getAgentRuntime(agentId) {
641
697
  if (orgDefault) return orgDefault;
642
698
  return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
643
699
  }
644
- var AGENT_CONFIG_PATH, DEFAULT_MODELS;
700
+ function setAgentRuntime(agentId, runtime, model) {
701
+ const knownModels = KNOWN_RUNTIMES[runtime];
702
+ if (!knownModels) {
703
+ return {
704
+ ok: false,
705
+ error: `Unknown runtime "${runtime}". Valid: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`
706
+ };
707
+ }
708
+ if (!knownModels.includes(model)) {
709
+ return {
710
+ ok: false,
711
+ error: `Unknown model "${model}" for runtime "${runtime}". Valid: ${knownModels.join(", ")}`
712
+ };
713
+ }
714
+ const config = loadAgentConfig();
715
+ config[agentId] = { runtime, model };
716
+ saveAgentConfig(config);
717
+ return { ok: true };
718
+ }
719
+ function clearAgentRuntime(agentId) {
720
+ const config = loadAgentConfig();
721
+ delete config[agentId];
722
+ saveAgentConfig(config);
723
+ }
724
+ var AGENT_CONFIG_PATH, KNOWN_RUNTIMES, RUNTIME_LABELS, DEFAULT_MODELS;
645
725
  var init_agent_config = __esm({
646
726
  "src/lib/agent-config.ts"() {
647
727
  "use strict";
648
728
  init_config();
649
729
  init_runtime_table();
730
+ init_secure_files();
650
731
  AGENT_CONFIG_PATH = path3.join(EXE_AI_DIR, "agent-config.json");
732
+ KNOWN_RUNTIMES = {
733
+ claude: ["claude-opus-4", "claude-sonnet-4", "claude-haiku-4.5"],
734
+ codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
735
+ opencode: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4", "google/gemini-2.5-pro", "deepseek/deepseek-r3", "minimax/minimax-m2.5"]
736
+ };
737
+ RUNTIME_LABELS = {
738
+ claude: "Claude Code (Anthropic)",
739
+ codex: "Codex (OpenAI)",
740
+ opencode: "OpenCode (open source)"
741
+ };
651
742
  DEFAULT_MODELS = {
652
743
  claude: "claude-opus-4",
653
744
  codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
@@ -665,16 +756,16 @@ __export(intercom_queue_exports, {
665
756
  queueIntercom: () => queueIntercom,
666
757
  readQueue: () => readQueue
667
758
  });
668
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
759
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
669
760
  import path4 from "path";
670
761
  import os3 from "os";
671
762
  function ensureDir() {
672
763
  const dir = path4.dirname(QUEUE_PATH);
673
- if (!existsSync5(dir)) mkdirSync3(dir, { recursive: true });
764
+ if (!existsSync6(dir)) mkdirSync3(dir, { recursive: true });
674
765
  }
675
766
  function readQueue() {
676
767
  try {
677
- if (!existsSync5(QUEUE_PATH)) return [];
768
+ if (!existsSync6(QUEUE_PATH)) return [];
678
769
  return JSON.parse(readFileSync5(QUEUE_PATH, "utf8"));
679
770
  } catch {
680
771
  return [];
@@ -852,6 +943,7 @@ __export(employees_exports, {
852
943
  getEmployeeByRole: () => getEmployeeByRole,
853
944
  getEmployeeNamesByRole: () => getEmployeeNamesByRole,
854
945
  hasRole: () => hasRole,
946
+ hireEmployee: () => hireEmployee,
855
947
  isCoordinatorName: () => isCoordinatorName,
856
948
  isCoordinatorRole: () => isCoordinatorRole,
857
949
  isMultiInstance: () => isMultiInstance,
@@ -864,7 +956,7 @@ __export(employees_exports, {
864
956
  validateEmployeeName: () => validateEmployeeName
865
957
  });
866
958
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
867
- import { existsSync as existsSync6, symlinkSync, readlinkSync, readFileSync as readFileSync6, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
959
+ import { existsSync as existsSync7, symlinkSync, readlinkSync, readFileSync as readFileSync6, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
868
960
  import { execSync as execSync4 } from "child_process";
869
961
  import path5 from "path";
870
962
  import os4 from "os";
@@ -903,7 +995,7 @@ function validateEmployeeName(name) {
903
995
  return { valid: true };
904
996
  }
905
997
  async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
906
- if (!existsSync6(employeesPath)) {
998
+ if (!existsSync7(employeesPath)) {
907
999
  return [];
908
1000
  }
909
1001
  const raw = await readFile2(employeesPath, "utf-8");
@@ -918,7 +1010,7 @@ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
918
1010
  await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
919
1011
  }
920
1012
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
921
- if (!existsSync6(employeesPath)) return [];
1013
+ if (!existsSync7(employeesPath)) return [];
922
1014
  try {
923
1015
  return JSON.parse(readFileSync6(employeesPath, "utf-8"));
924
1016
  } catch {
@@ -962,6 +1054,52 @@ function addEmployee(employees, employee) {
962
1054
  }
963
1055
  return [...employees, normalized];
964
1056
  }
1057
+ function appendToCoordinatorTeam(employee) {
1058
+ const coordinator = getCoordinatorEmployee(loadEmployeesSync());
1059
+ if (!coordinator) return;
1060
+ const idPath = path5.join(IDENTITY_DIR, `${coordinator.name}.md`);
1061
+ if (!existsSync7(idPath)) return;
1062
+ const content = readFileSync6(idPath, "utf-8");
1063
+ if (content.includes(`**${capitalize(employee.name)}`)) return;
1064
+ const teamMatch = content.match(TEAM_SECTION_RE);
1065
+ if (!teamMatch || teamMatch.index === void 0) return;
1066
+ const afterTeam = content.slice(teamMatch.index + teamMatch[0].length);
1067
+ const nextHeading = afterTeam.match(/\n## /);
1068
+ const entry = `
1069
+ **${capitalize(employee.name)} (${employee.role}):** Newly hired. Update this description as the role develops.
1070
+ `;
1071
+ let updated;
1072
+ if (nextHeading && nextHeading.index !== void 0) {
1073
+ const insertAt = teamMatch.index + teamMatch[0].length + nextHeading.index;
1074
+ updated = content.slice(0, insertAt) + entry + content.slice(insertAt);
1075
+ } else {
1076
+ updated = content.trimEnd() + "\n" + entry;
1077
+ }
1078
+ writeFileSync4(idPath, updated, "utf-8");
1079
+ }
1080
+ function capitalize(s) {
1081
+ return s.charAt(0).toUpperCase() + s.slice(1);
1082
+ }
1083
+ async function hireEmployee(employee) {
1084
+ const employees = await loadEmployees();
1085
+ const updated = addEmployee(employees, employee);
1086
+ await saveEmployees(updated);
1087
+ try {
1088
+ appendToCoordinatorTeam(employee);
1089
+ } catch {
1090
+ }
1091
+ try {
1092
+ const { loadAgentConfig: loadAgentConfig2, saveAgentConfig: saveAgentConfig2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
1093
+ const config = loadAgentConfig2();
1094
+ const name = employee.name.toLowerCase();
1095
+ if (!config[name] && config["default"]) {
1096
+ config[name] = { ...config["default"] };
1097
+ saveAgentConfig2(config);
1098
+ }
1099
+ } catch {
1100
+ }
1101
+ return updated;
1102
+ }
965
1103
  async function normalizeRosterCase(rosterPath) {
966
1104
  const employees = await loadEmployees(rosterPath);
967
1105
  let changed = false;
@@ -974,9 +1112,9 @@ async function normalizeRosterCase(rosterPath) {
974
1112
  const identityDir = path5.join(os4.homedir(), ".exe-os", "identity");
975
1113
  const oldPath = path5.join(identityDir, `${oldName}.md`);
976
1114
  const newPath = path5.join(identityDir, `${emp.name}.md`);
977
- if (existsSync6(oldPath) && !existsSync6(newPath)) {
1115
+ if (existsSync7(oldPath) && !existsSync7(newPath)) {
978
1116
  renameSync3(oldPath, newPath);
979
- } else if (existsSync6(oldPath) && oldPath !== newPath) {
1117
+ } else if (existsSync7(oldPath) && oldPath !== newPath) {
980
1118
  const content = readFileSync6(oldPath, "utf-8");
981
1119
  writeFileSync4(newPath, content, "utf-8");
982
1120
  if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
@@ -1019,7 +1157,7 @@ function registerBinSymlinks(name) {
1019
1157
  for (const suffix of ["", "-opencode"]) {
1020
1158
  const linkName = `${name}${suffix}`;
1021
1159
  const linkPath = path5.join(binDir, linkName);
1022
- if (existsSync6(linkPath)) {
1160
+ if (existsSync7(linkPath)) {
1023
1161
  skipped.push(linkName);
1024
1162
  continue;
1025
1163
  }
@@ -1032,7 +1170,7 @@ function registerBinSymlinks(name) {
1032
1170
  }
1033
1171
  return { created, skipped, errors };
1034
1172
  }
1035
- var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
1173
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR, TEAM_SECTION_RE;
1036
1174
  var init_employees = __esm({
1037
1175
  "src/lib/employees.ts"() {
1038
1176
  "use strict";
@@ -1041,16 +1179,639 @@ var init_employees = __esm({
1041
1179
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
1042
1180
  COORDINATOR_ROLE = "COO";
1043
1181
  MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
1182
+ IDENTITY_DIR = path5.join(EXE_AI_DIR, "identity");
1183
+ TEAM_SECTION_RE = /^## Team\b.*$/m;
1184
+ }
1185
+ });
1186
+
1187
+ // src/lib/database-adapter.ts
1188
+ import os5 from "os";
1189
+ import path6 from "path";
1190
+ import { createRequire } from "module";
1191
+ import { pathToFileURL } from "url";
1192
+ function quotedIdentifier(identifier) {
1193
+ return `"${identifier.replace(/"/g, '""')}"`;
1194
+ }
1195
+ function unqualifiedTableName(name) {
1196
+ const raw = name.trim().replace(/^"|"$/g, "");
1197
+ const parts = raw.split(".");
1198
+ return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
1199
+ }
1200
+ function stripTrailingSemicolon(sql) {
1201
+ return sql.trim().replace(/;+\s*$/u, "");
1202
+ }
1203
+ function appendClause(sql, clause) {
1204
+ const trimmed = stripTrailingSemicolon(sql);
1205
+ const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
1206
+ if (!returningMatch) {
1207
+ return `${trimmed}${clause}`;
1208
+ }
1209
+ const idx = returningMatch.index;
1210
+ return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
1211
+ }
1212
+ function normalizeStatement(stmt) {
1213
+ if (typeof stmt === "string") {
1214
+ return { kind: "positional", sql: stmt, args: [] };
1215
+ }
1216
+ const sql = stmt.sql;
1217
+ if (Array.isArray(stmt.args) || stmt.args === void 0) {
1218
+ return { kind: "positional", sql, args: stmt.args ?? [] };
1219
+ }
1220
+ return { kind: "named", sql, args: stmt.args };
1221
+ }
1222
+ function rewriteBooleanLiterals(sql) {
1223
+ let out = sql;
1224
+ for (const column of BOOLEAN_COLUMN_NAMES) {
1225
+ const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
1226
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
1227
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
1228
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
1229
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
1230
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
1231
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
1232
+ }
1233
+ return out;
1234
+ }
1235
+ function rewriteInsertOrIgnore(sql) {
1236
+ if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
1237
+ return sql;
1238
+ }
1239
+ const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
1240
+ return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
1241
+ }
1242
+ function rewriteInsertOrReplace(sql) {
1243
+ const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
1244
+ if (!match) {
1245
+ return sql;
1246
+ }
1247
+ const rawTable = match[1];
1248
+ const rawColumns = match[2];
1249
+ const remainder = match[3];
1250
+ const tableName = unqualifiedTableName(rawTable);
1251
+ const conflictKeys = UPSERT_KEYS[tableName];
1252
+ if (!conflictKeys?.length) {
1253
+ return sql;
1254
+ }
1255
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
1256
+ const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
1257
+ const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
1258
+ const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
1259
+ return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
1260
+ }
1261
+ function rewriteSql(sql) {
1262
+ let out = sql;
1263
+ out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
1264
+ out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
1265
+ out = rewriteBooleanLiterals(out);
1266
+ out = rewriteInsertOrReplace(out);
1267
+ out = rewriteInsertOrIgnore(out);
1268
+ return stripTrailingSemicolon(out);
1269
+ }
1270
+ function toBoolean(value) {
1271
+ if (value === null || value === void 0) return value;
1272
+ if (typeof value === "boolean") return value;
1273
+ if (typeof value === "number") return value !== 0;
1274
+ if (typeof value === "bigint") return value !== 0n;
1275
+ if (typeof value === "string") {
1276
+ const normalized = value.trim().toLowerCase();
1277
+ if (normalized === "0" || normalized === "false") return false;
1278
+ if (normalized === "1" || normalized === "true") return true;
1279
+ }
1280
+ return Boolean(value);
1281
+ }
1282
+ function countQuestionMarks(sql, end) {
1283
+ let count = 0;
1284
+ let inSingle = false;
1285
+ let inDouble = false;
1286
+ let inLineComment = false;
1287
+ let inBlockComment = false;
1288
+ for (let i = 0; i < end; i++) {
1289
+ const ch = sql[i];
1290
+ const next = sql[i + 1];
1291
+ if (inLineComment) {
1292
+ if (ch === "\n") inLineComment = false;
1293
+ continue;
1294
+ }
1295
+ if (inBlockComment) {
1296
+ if (ch === "*" && next === "/") {
1297
+ inBlockComment = false;
1298
+ i += 1;
1299
+ }
1300
+ continue;
1301
+ }
1302
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
1303
+ inLineComment = true;
1304
+ i += 1;
1305
+ continue;
1306
+ }
1307
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
1308
+ inBlockComment = true;
1309
+ i += 1;
1310
+ continue;
1311
+ }
1312
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
1313
+ inSingle = !inSingle;
1314
+ continue;
1315
+ }
1316
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
1317
+ inDouble = !inDouble;
1318
+ continue;
1319
+ }
1320
+ if (!inSingle && !inDouble && ch === "?") {
1321
+ count += 1;
1322
+ }
1323
+ }
1324
+ return count;
1325
+ }
1326
+ function findBooleanPlaceholderIndexes(sql) {
1327
+ const indexes = /* @__PURE__ */ new Set();
1328
+ for (const column of BOOLEAN_COLUMN_NAMES) {
1329
+ const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
1330
+ for (const match of sql.matchAll(pattern)) {
1331
+ const matchText = match[0];
1332
+ const qIndex = match.index + matchText.lastIndexOf("?");
1333
+ indexes.add(countQuestionMarks(sql, qIndex + 1));
1334
+ }
1335
+ }
1336
+ return indexes;
1337
+ }
1338
+ function coerceInsertBooleanArgs(sql, args) {
1339
+ const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
1340
+ if (!match) return;
1341
+ const rawTable = match[1];
1342
+ const rawColumns = match[2];
1343
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
1344
+ if (!boolColumns?.size) return;
1345
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
1346
+ for (const [index, column] of columns.entries()) {
1347
+ if (boolColumns.has(column) && index < args.length) {
1348
+ args[index] = toBoolean(args[index]);
1349
+ }
1350
+ }
1351
+ }
1352
+ function coerceUpdateBooleanArgs(sql, args) {
1353
+ const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
1354
+ if (!match) return;
1355
+ const rawTable = match[1];
1356
+ const setClause = match[2];
1357
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
1358
+ if (!boolColumns?.size) return;
1359
+ const assignments = setClause.split(",");
1360
+ let placeholderIndex = 0;
1361
+ for (const assignment of assignments) {
1362
+ if (!assignment.includes("?")) continue;
1363
+ placeholderIndex += 1;
1364
+ const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
1365
+ if (colMatch && boolColumns.has(colMatch[1])) {
1366
+ args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
1367
+ }
1368
+ }
1369
+ }
1370
+ function coerceBooleanArgs(sql, args) {
1371
+ const nextArgs = [...args];
1372
+ coerceInsertBooleanArgs(sql, nextArgs);
1373
+ coerceUpdateBooleanArgs(sql, nextArgs);
1374
+ const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
1375
+ for (const index of placeholderIndexes) {
1376
+ if (index > 0 && index <= nextArgs.length) {
1377
+ nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
1378
+ }
1379
+ }
1380
+ return nextArgs;
1381
+ }
1382
+ function convertQuestionMarksToDollarParams(sql) {
1383
+ let out = "";
1384
+ let placeholder = 0;
1385
+ let inSingle = false;
1386
+ let inDouble = false;
1387
+ let inLineComment = false;
1388
+ let inBlockComment = false;
1389
+ for (let i = 0; i < sql.length; i++) {
1390
+ const ch = sql[i];
1391
+ const next = sql[i + 1];
1392
+ if (inLineComment) {
1393
+ out += ch;
1394
+ if (ch === "\n") inLineComment = false;
1395
+ continue;
1396
+ }
1397
+ if (inBlockComment) {
1398
+ out += ch;
1399
+ if (ch === "*" && next === "/") {
1400
+ out += next;
1401
+ inBlockComment = false;
1402
+ i += 1;
1403
+ }
1404
+ continue;
1405
+ }
1406
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
1407
+ out += ch + next;
1408
+ inLineComment = true;
1409
+ i += 1;
1410
+ continue;
1411
+ }
1412
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
1413
+ out += ch + next;
1414
+ inBlockComment = true;
1415
+ i += 1;
1416
+ continue;
1417
+ }
1418
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
1419
+ inSingle = !inSingle;
1420
+ out += ch;
1421
+ continue;
1422
+ }
1423
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
1424
+ inDouble = !inDouble;
1425
+ out += ch;
1426
+ continue;
1427
+ }
1428
+ if (!inSingle && !inDouble && ch === "?") {
1429
+ placeholder += 1;
1430
+ out += `$${placeholder}`;
1431
+ continue;
1432
+ }
1433
+ out += ch;
1434
+ }
1435
+ return out;
1436
+ }
1437
+ function translateStatementForPostgres(stmt) {
1438
+ const normalized = normalizeStatement(stmt);
1439
+ if (normalized.kind === "named") {
1440
+ throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
1441
+ }
1442
+ const rewrittenSql = rewriteSql(normalized.sql);
1443
+ const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
1444
+ return {
1445
+ sql: convertQuestionMarksToDollarParams(rewrittenSql),
1446
+ args: coercedArgs
1447
+ };
1448
+ }
1449
+ function shouldBypassPostgres(stmt) {
1450
+ const normalized = normalizeStatement(stmt);
1451
+ if (normalized.kind === "named") {
1452
+ return true;
1453
+ }
1454
+ return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
1455
+ }
1456
+ function shouldFallbackOnError(error) {
1457
+ const message = error instanceof Error ? error.message : String(error);
1458
+ return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
1459
+ }
1460
+ function isReadQuery(sql) {
1461
+ const trimmed = sql.trimStart();
1462
+ return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
1463
+ }
1464
+ function buildRow(row, columns) {
1465
+ const values = columns.map((column) => row[column]);
1466
+ return Object.assign(values, row);
1467
+ }
1468
+ function buildResultSet(rows, rowsAffected = 0) {
1469
+ const columns = rows[0] ? Object.keys(rows[0]) : [];
1470
+ const resultRows = rows.map((row) => buildRow(row, columns));
1471
+ return {
1472
+ columns,
1473
+ columnTypes: columns.map(() => ""),
1474
+ rows: resultRows,
1475
+ rowsAffected,
1476
+ lastInsertRowid: void 0,
1477
+ toJSON() {
1478
+ return {
1479
+ columns,
1480
+ columnTypes: columns.map(() => ""),
1481
+ rows,
1482
+ rowsAffected,
1483
+ lastInsertRowid: void 0
1484
+ };
1485
+ }
1486
+ };
1487
+ }
1488
+ async function loadPrismaClient() {
1489
+ if (!prismaClientPromise) {
1490
+ prismaClientPromise = (async () => {
1491
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
1492
+ if (explicitPath) {
1493
+ const module2 = await import(pathToFileURL(explicitPath).href);
1494
+ const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
1495
+ if (!PrismaClient2) {
1496
+ throw new Error(`No PrismaClient export found at ${explicitPath}`);
1497
+ }
1498
+ return new PrismaClient2();
1499
+ }
1500
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path6.join(os5.homedir(), "exe-db");
1501
+ const requireFromExeDb = createRequire(path6.join(exeDbRoot, "package.json"));
1502
+ const prismaEntry = requireFromExeDb.resolve("@prisma/client");
1503
+ const module = await import(pathToFileURL(prismaEntry).href);
1504
+ const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
1505
+ if (!PrismaClient) {
1506
+ throw new Error(`No PrismaClient export found in ${prismaEntry}`);
1507
+ }
1508
+ return new PrismaClient();
1509
+ })();
1510
+ }
1511
+ return prismaClientPromise;
1512
+ }
1513
+ async function ensureCompatibilityViews(prisma) {
1514
+ if (!compatibilityBootstrapPromise) {
1515
+ compatibilityBootstrapPromise = (async () => {
1516
+ for (const mapping of VIEW_MAPPINGS) {
1517
+ const relation = mapping.source.replace(/"/g, "");
1518
+ const rows = await prisma.$queryRawUnsafe(
1519
+ "SELECT to_regclass($1) AS regclass",
1520
+ relation
1521
+ );
1522
+ if (!rows[0]?.regclass) {
1523
+ continue;
1524
+ }
1525
+ await prisma.$executeRawUnsafe(
1526
+ `CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
1527
+ );
1528
+ }
1529
+ })();
1530
+ }
1531
+ return compatibilityBootstrapPromise;
1532
+ }
1533
+ async function executeOnPrisma(executor, stmt) {
1534
+ const translated = translateStatementForPostgres(stmt);
1535
+ if (isReadQuery(translated.sql)) {
1536
+ const rows = await executor.$queryRawUnsafe(
1537
+ translated.sql,
1538
+ ...translated.args
1539
+ );
1540
+ return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
1541
+ }
1542
+ const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
1543
+ return buildResultSet([], rowsAffected);
1544
+ }
1545
+ function splitSqlStatements(sql) {
1546
+ const parts = [];
1547
+ let current = "";
1548
+ let inSingle = false;
1549
+ let inDouble = false;
1550
+ let inLineComment = false;
1551
+ let inBlockComment = false;
1552
+ for (let i = 0; i < sql.length; i++) {
1553
+ const ch = sql[i];
1554
+ const next = sql[i + 1];
1555
+ if (inLineComment) {
1556
+ current += ch;
1557
+ if (ch === "\n") inLineComment = false;
1558
+ continue;
1559
+ }
1560
+ if (inBlockComment) {
1561
+ current += ch;
1562
+ if (ch === "*" && next === "/") {
1563
+ current += next;
1564
+ inBlockComment = false;
1565
+ i += 1;
1566
+ }
1567
+ continue;
1568
+ }
1569
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
1570
+ current += ch + next;
1571
+ inLineComment = true;
1572
+ i += 1;
1573
+ continue;
1574
+ }
1575
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
1576
+ current += ch + next;
1577
+ inBlockComment = true;
1578
+ i += 1;
1579
+ continue;
1580
+ }
1581
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
1582
+ inSingle = !inSingle;
1583
+ current += ch;
1584
+ continue;
1585
+ }
1586
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
1587
+ inDouble = !inDouble;
1588
+ current += ch;
1589
+ continue;
1590
+ }
1591
+ if (!inSingle && !inDouble && ch === ";") {
1592
+ if (current.trim()) {
1593
+ parts.push(current.trim());
1594
+ }
1595
+ current = "";
1596
+ continue;
1597
+ }
1598
+ current += ch;
1599
+ }
1600
+ if (current.trim()) {
1601
+ parts.push(current.trim());
1602
+ }
1603
+ return parts;
1604
+ }
1605
+ async function createPrismaDbAdapter(fallbackClient) {
1606
+ const prisma = await loadPrismaClient();
1607
+ await ensureCompatibilityViews(prisma);
1608
+ let closed = false;
1609
+ let adapter;
1610
+ const fallbackExecute = async (stmt, error) => {
1611
+ if (!fallbackClient) {
1612
+ if (error) throw error;
1613
+ throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
1614
+ }
1615
+ if (error) {
1616
+ process.stderr.write(
1617
+ `[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
1618
+ `
1619
+ );
1620
+ }
1621
+ return fallbackClient.execute(stmt);
1622
+ };
1623
+ adapter = {
1624
+ async execute(stmt) {
1625
+ if (shouldBypassPostgres(stmt)) {
1626
+ return fallbackExecute(stmt);
1627
+ }
1628
+ try {
1629
+ return await executeOnPrisma(prisma, stmt);
1630
+ } catch (error) {
1631
+ if (shouldFallbackOnError(error)) {
1632
+ return fallbackExecute(stmt, error);
1633
+ }
1634
+ throw error;
1635
+ }
1636
+ },
1637
+ async batch(stmts, mode) {
1638
+ if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
1639
+ if (!fallbackClient) {
1640
+ throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
1641
+ }
1642
+ return fallbackClient.batch(stmts, mode);
1643
+ }
1644
+ try {
1645
+ if (prisma.$transaction) {
1646
+ return await prisma.$transaction(async (tx) => {
1647
+ const results2 = [];
1648
+ for (const stmt of stmts) {
1649
+ results2.push(await executeOnPrisma(tx, stmt));
1650
+ }
1651
+ return results2;
1652
+ });
1653
+ }
1654
+ const results = [];
1655
+ for (const stmt of stmts) {
1656
+ results.push(await executeOnPrisma(prisma, stmt));
1657
+ }
1658
+ return results;
1659
+ } catch (error) {
1660
+ if (fallbackClient && shouldFallbackOnError(error)) {
1661
+ process.stderr.write(
1662
+ `[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
1663
+ `
1664
+ );
1665
+ return fallbackClient.batch(stmts, mode);
1666
+ }
1667
+ throw error;
1668
+ }
1669
+ },
1670
+ async migrate(stmts) {
1671
+ if (fallbackClient) {
1672
+ return fallbackClient.migrate(stmts);
1673
+ }
1674
+ return adapter.batch(stmts, "deferred");
1675
+ },
1676
+ async transaction(mode) {
1677
+ if (!fallbackClient) {
1678
+ throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
1679
+ }
1680
+ return fallbackClient.transaction(mode);
1681
+ },
1682
+ async executeMultiple(sql) {
1683
+ if (fallbackClient && shouldBypassPostgres(sql)) {
1684
+ return fallbackClient.executeMultiple(sql);
1685
+ }
1686
+ for (const statement of splitSqlStatements(sql)) {
1687
+ await adapter.execute(statement);
1688
+ }
1689
+ },
1690
+ async sync() {
1691
+ if (fallbackClient) {
1692
+ return fallbackClient.sync();
1693
+ }
1694
+ return { frame_no: 0, frames_synced: 0 };
1695
+ },
1696
+ close() {
1697
+ closed = true;
1698
+ prismaClientPromise = null;
1699
+ compatibilityBootstrapPromise = null;
1700
+ void prisma.$disconnect?.();
1701
+ },
1702
+ get closed() {
1703
+ return closed;
1704
+ },
1705
+ get protocol() {
1706
+ return "prisma-postgres";
1707
+ }
1708
+ };
1709
+ return adapter;
1710
+ }
1711
+ var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
1712
+ var init_database_adapter = __esm({
1713
+ "src/lib/database-adapter.ts"() {
1714
+ "use strict";
1715
+ VIEW_MAPPINGS = [
1716
+ { view: "memories", source: "memory.memory_records" },
1717
+ { view: "tasks", source: "memory.tasks" },
1718
+ { view: "behaviors", source: "memory.behaviors" },
1719
+ { view: "entities", source: "memory.entities" },
1720
+ { view: "relationships", source: "memory.relationships" },
1721
+ { view: "entity_memories", source: "memory.entity_memories" },
1722
+ { view: "entity_aliases", source: "memory.entity_aliases" },
1723
+ { view: "notifications", source: "memory.notifications" },
1724
+ { view: "messages", source: "memory.messages" },
1725
+ { view: "users", source: "wiki.users" },
1726
+ { view: "workspaces", source: "wiki.workspaces" },
1727
+ { view: "workspace_users", source: "wiki.workspace_users" },
1728
+ { view: "documents", source: "wiki.workspace_documents" },
1729
+ { view: "chats", source: "wiki.workspace_chats" }
1730
+ ];
1731
+ UPSERT_KEYS = {
1732
+ memories: ["id"],
1733
+ tasks: ["id"],
1734
+ behaviors: ["id"],
1735
+ entities: ["id"],
1736
+ relationships: ["id"],
1737
+ entity_aliases: ["alias"],
1738
+ notifications: ["id"],
1739
+ messages: ["id"],
1740
+ users: ["id"],
1741
+ workspaces: ["id"],
1742
+ workspace_users: ["id"],
1743
+ documents: ["id"],
1744
+ chats: ["id"]
1745
+ };
1746
+ BOOLEAN_COLUMNS_BY_TABLE = {
1747
+ memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
1748
+ behaviors: /* @__PURE__ */ new Set(["active"]),
1749
+ notifications: /* @__PURE__ */ new Set(["read"]),
1750
+ users: /* @__PURE__ */ new Set(["has_personal_memory"])
1751
+ };
1752
+ BOOLEAN_COLUMN_NAMES = new Set(
1753
+ Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
1754
+ );
1755
+ IMMEDIATE_FALLBACK_PATTERNS = [
1756
+ /\bPRAGMA\b/i,
1757
+ /\bsqlite_master\b/i,
1758
+ /(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
1759
+ /\bMATCH\b/i,
1760
+ /\bvector_distance_cos\s*\(/i,
1761
+ /\bjson_extract\s*\(/i,
1762
+ /\bjulianday\s*\(/i,
1763
+ /\bstrftime\s*\(/i,
1764
+ /\blast_insert_rowid\s*\(/i
1765
+ ];
1766
+ prismaClientPromise = null;
1767
+ compatibilityBootstrapPromise = null;
1768
+ }
1769
+ });
1770
+
1771
+ // src/lib/daemon-auth.ts
1772
+ import crypto from "crypto";
1773
+ import path7 from "path";
1774
+ import { existsSync as existsSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
1775
+ function normalizeToken(token) {
1776
+ if (!token) return null;
1777
+ const trimmed = token.trim();
1778
+ return trimmed.length > 0 ? trimmed : null;
1779
+ }
1780
+ function readDaemonToken() {
1781
+ try {
1782
+ if (!existsSync8(DAEMON_TOKEN_PATH)) return null;
1783
+ return normalizeToken(readFileSync7(DAEMON_TOKEN_PATH, "utf8"));
1784
+ } catch {
1785
+ return null;
1786
+ }
1787
+ }
1788
+ function ensureDaemonToken(seed) {
1789
+ const existing = readDaemonToken();
1790
+ if (existing) return existing;
1791
+ const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
1792
+ ensurePrivateDirSync(EXE_AI_DIR);
1793
+ writeFileSync5(DAEMON_TOKEN_PATH, `${token}
1794
+ `, "utf8");
1795
+ enforcePrivateFileSync(DAEMON_TOKEN_PATH);
1796
+ return token;
1797
+ }
1798
+ var DAEMON_TOKEN_PATH;
1799
+ var init_daemon_auth = __esm({
1800
+ "src/lib/daemon-auth.ts"() {
1801
+ "use strict";
1802
+ init_config();
1803
+ init_secure_files();
1804
+ DAEMON_TOKEN_PATH = path7.join(EXE_AI_DIR, "exed.token");
1044
1805
  }
1045
1806
  });
1046
1807
 
1047
1808
  // src/lib/exe-daemon-client.ts
1048
1809
  import net from "net";
1049
- import os5 from "os";
1810
+ import os6 from "os";
1050
1811
  import { spawn } from "child_process";
1051
1812
  import { randomUUID } from "crypto";
1052
- import { existsSync as existsSync7, unlinkSync as unlinkSync2, readFileSync as readFileSync7, openSync, closeSync, statSync } from "fs";
1053
- import path6 from "path";
1813
+ import { existsSync as existsSync9, unlinkSync as unlinkSync2, readFileSync as readFileSync8, openSync, closeSync, statSync } from "fs";
1814
+ import path8 from "path";
1054
1815
  import { fileURLToPath } from "url";
1055
1816
  function handleData(chunk) {
1056
1817
  _buffer += chunk.toString();
@@ -1078,9 +1839,9 @@ function handleData(chunk) {
1078
1839
  }
1079
1840
  }
1080
1841
  function cleanupStaleFiles() {
1081
- if (existsSync7(PID_PATH)) {
1842
+ if (existsSync9(PID_PATH)) {
1082
1843
  try {
1083
- const pid = parseInt(readFileSync7(PID_PATH, "utf8").trim(), 10);
1844
+ const pid = parseInt(readFileSync8(PID_PATH, "utf8").trim(), 10);
1084
1845
  if (pid > 0) {
1085
1846
  try {
1086
1847
  process.kill(pid, 0);
@@ -1101,17 +1862,17 @@ function cleanupStaleFiles() {
1101
1862
  }
1102
1863
  }
1103
1864
  function findPackageRoot() {
1104
- let dir = path6.dirname(fileURLToPath(import.meta.url));
1105
- const { root } = path6.parse(dir);
1865
+ let dir = path8.dirname(fileURLToPath(import.meta.url));
1866
+ const { root } = path8.parse(dir);
1106
1867
  while (dir !== root) {
1107
- if (existsSync7(path6.join(dir, "package.json"))) return dir;
1108
- dir = path6.dirname(dir);
1868
+ if (existsSync9(path8.join(dir, "package.json"))) return dir;
1869
+ dir = path8.dirname(dir);
1109
1870
  }
1110
1871
  return null;
1111
1872
  }
1112
1873
  function spawnDaemon() {
1113
- const freeGB = os5.freemem() / (1024 * 1024 * 1024);
1114
- const totalGB = os5.totalmem() / (1024 * 1024 * 1024);
1874
+ const freeGB = os6.freemem() / (1024 * 1024 * 1024);
1875
+ const totalGB = os6.totalmem() / (1024 * 1024 * 1024);
1115
1876
  if (totalGB <= 8) {
1116
1877
  process.stderr.write(
1117
1878
  `[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
@@ -1131,16 +1892,17 @@ function spawnDaemon() {
1131
1892
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
1132
1893
  return;
1133
1894
  }
1134
- const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1135
- if (!existsSync7(daemonPath)) {
1895
+ const daemonPath = path8.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1896
+ if (!existsSync9(daemonPath)) {
1136
1897
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
1137
1898
  `);
1138
1899
  return;
1139
1900
  }
1140
1901
  const resolvedPath = daemonPath;
1902
+ const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
1141
1903
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
1142
1904
  `);
1143
- const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
1905
+ const logPath = path8.join(path8.dirname(SOCKET_PATH), "exed.log");
1144
1906
  let stderrFd = "ignore";
1145
1907
  try {
1146
1908
  stderrFd = openSync(logPath, "a");
@@ -1158,7 +1920,8 @@ function spawnDaemon() {
1158
1920
  TMUX_PANE: void 0,
1159
1921
  // Prevents resolveExeSession() from scoping to one session
1160
1922
  EXE_DAEMON_SOCK: SOCKET_PATH,
1161
- EXE_DAEMON_PID: PID_PATH
1923
+ EXE_DAEMON_PID: PID_PATH,
1924
+ [DAEMON_TOKEN_ENV]: daemonToken
1162
1925
  }
1163
1926
  });
1164
1927
  child.unref();
@@ -1265,13 +2028,14 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
1265
2028
  return;
1266
2029
  }
1267
2030
  const id = randomUUID();
2031
+ const token = process.env[DAEMON_TOKEN_ENV] ?? readDaemonToken();
1268
2032
  const timer = setTimeout(() => {
1269
2033
  _pending.delete(id);
1270
2034
  resolve({ error: "Request timeout" });
1271
2035
  }, timeoutMs);
1272
2036
  _pending.set(id, { resolve, timer });
1273
2037
  try {
1274
- _socket.write(JSON.stringify({ id, ...payload }) + "\n");
2038
+ _socket.write(JSON.stringify({ id, token, ...payload }) + "\n");
1275
2039
  } catch {
1276
2040
  clearTimeout(timer);
1277
2041
  _pending.delete(id);
@@ -1282,17 +2046,19 @@ function sendDaemonRequest(payload, timeoutMs = REQUEST_TIMEOUT_MS) {
1282
2046
  function isClientConnected() {
1283
2047
  return _connected;
1284
2048
  }
1285
- var SOCKET_PATH, PID_PATH, SPAWN_LOCK_PATH, SPAWN_LOCK_STALE_MS, CONNECT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, _socket, _connected, _buffer, _pending, MAX_BUFFER;
2049
+ 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;
1286
2050
  var init_exe_daemon_client = __esm({
1287
2051
  "src/lib/exe-daemon-client.ts"() {
1288
2052
  "use strict";
1289
2053
  init_config();
1290
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
1291
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
1292
- SPAWN_LOCK_PATH = path6.join(EXE_AI_DIR, "exed-spawn.lock");
2054
+ init_daemon_auth();
2055
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path8.join(EXE_AI_DIR, "exed.sock");
2056
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path8.join(EXE_AI_DIR, "exed.pid");
2057
+ SPAWN_LOCK_PATH = path8.join(EXE_AI_DIR, "exed-spawn.lock");
1293
2058
  SPAWN_LOCK_STALE_MS = 3e4;
1294
2059
  CONNECT_TIMEOUT_MS = 15e3;
1295
2060
  REQUEST_TIMEOUT_MS = 3e4;
2061
+ DAEMON_TOKEN_ENV = "EXE_DAEMON_TOKEN";
1296
2062
  _socket = null;
1297
2063
  _connected = false;
1298
2064
  _buffer = "";
@@ -1371,7 +2137,7 @@ __export(db_daemon_client_exports, {
1371
2137
  createDaemonDbClient: () => createDaemonDbClient,
1372
2138
  initDaemonDbClient: () => initDaemonDbClient
1373
2139
  });
1374
- function normalizeStatement(stmt) {
2140
+ function normalizeStatement2(stmt) {
1375
2141
  if (typeof stmt === "string") {
1376
2142
  return { sql: stmt, args: [] };
1377
2143
  }
@@ -1395,7 +2161,7 @@ function createDaemonDbClient(fallbackClient) {
1395
2161
  if (!_useDaemon || !isClientConnected()) {
1396
2162
  return fallbackClient.execute(stmt);
1397
2163
  }
1398
- const { sql, args } = normalizeStatement(stmt);
2164
+ const { sql, args } = normalizeStatement2(stmt);
1399
2165
  const response = await sendDaemonRequest({
1400
2166
  type: "db-execute",
1401
2167
  sql,
@@ -1420,7 +2186,7 @@ function createDaemonDbClient(fallbackClient) {
1420
2186
  if (!_useDaemon || !isClientConnected()) {
1421
2187
  return fallbackClient.batch(stmts, mode);
1422
2188
  }
1423
- const statements = stmts.map(normalizeStatement);
2189
+ const statements = stmts.map(normalizeStatement2);
1424
2190
  const response = await sendDaemonRequest({
1425
2191
  type: "db-batch",
1426
2192
  statements,
@@ -1515,6 +2281,18 @@ __export(database_exports, {
1515
2281
  });
1516
2282
  import { createClient } from "@libsql/client";
1517
2283
  async function initDatabase(config) {
2284
+ if (_walCheckpointTimer) {
2285
+ clearInterval(_walCheckpointTimer);
2286
+ _walCheckpointTimer = null;
2287
+ }
2288
+ if (_daemonClient) {
2289
+ _daemonClient.close();
2290
+ _daemonClient = null;
2291
+ }
2292
+ if (_adapterClient && _adapterClient !== _resilientClient) {
2293
+ _adapterClient.close();
2294
+ }
2295
+ _adapterClient = null;
1518
2296
  if (_client) {
1519
2297
  _client.close();
1520
2298
  _client = null;
@@ -1528,6 +2306,7 @@ async function initDatabase(config) {
1528
2306
  }
1529
2307
  _client = createClient(opts);
1530
2308
  _resilientClient = wrapWithRetry(_client);
2309
+ _adapterClient = _resilientClient;
1531
2310
  _client.execute("PRAGMA busy_timeout = 30000").catch(() => {
1532
2311
  });
1533
2312
  _client.execute("PRAGMA journal_mode = WAL").catch(() => {
@@ -1538,14 +2317,20 @@ async function initDatabase(config) {
1538
2317
  });
1539
2318
  }, 3e4);
1540
2319
  _walCheckpointTimer.unref();
2320
+ if (process.env.DATABASE_URL) {
2321
+ _adapterClient = await createPrismaDbAdapter(_resilientClient);
2322
+ }
1541
2323
  }
1542
2324
  function isInitialized() {
1543
- return _client !== null;
2325
+ return _adapterClient !== null || _client !== null;
1544
2326
  }
1545
2327
  function getClient() {
1546
- if (!_resilientClient) {
2328
+ if (!_adapterClient) {
1547
2329
  throw new Error("Database client not initialized. Call initDatabase() first.");
1548
2330
  }
2331
+ if (process.env.DATABASE_URL) {
2332
+ return _adapterClient;
2333
+ }
1549
2334
  if (process.env.EXE_IS_DAEMON === "1") {
1550
2335
  return _resilientClient;
1551
2336
  }
@@ -1555,6 +2340,7 @@ function getClient() {
1555
2340
  return _resilientClient;
1556
2341
  }
1557
2342
  async function initDaemonClient() {
2343
+ if (process.env.DATABASE_URL) return;
1558
2344
  if (process.env.EXE_IS_DAEMON === "1") return;
1559
2345
  if (!_resilientClient) return;
1560
2346
  try {
@@ -1851,6 +2637,7 @@ async function ensureSchema() {
1851
2637
  project TEXT NOT NULL,
1852
2638
  summary TEXT NOT NULL,
1853
2639
  task_file TEXT,
2640
+ session_scope TEXT,
1854
2641
  read INTEGER NOT NULL DEFAULT 0,
1855
2642
  created_at TEXT NOT NULL
1856
2643
  );
@@ -1859,7 +2646,7 @@ async function ensureSchema() {
1859
2646
  ON notifications(read);
1860
2647
 
1861
2648
  CREATE INDEX IF NOT EXISTS idx_notifications_agent
1862
- ON notifications(agent_id);
2649
+ ON notifications(agent_id, session_scope);
1863
2650
 
1864
2651
  CREATE INDEX IF NOT EXISTS idx_notifications_task_file
1865
2652
  ON notifications(task_file);
@@ -1897,6 +2684,7 @@ async function ensureSchema() {
1897
2684
  target_agent TEXT NOT NULL,
1898
2685
  target_project TEXT,
1899
2686
  target_device TEXT NOT NULL DEFAULT 'local',
2687
+ session_scope TEXT,
1900
2688
  content TEXT NOT NULL,
1901
2689
  priority TEXT DEFAULT 'normal',
1902
2690
  status TEXT DEFAULT 'pending',
@@ -1910,10 +2698,31 @@ async function ensureSchema() {
1910
2698
  );
1911
2699
 
1912
2700
  CREATE INDEX IF NOT EXISTS idx_messages_target
1913
- ON messages(target_agent, status);
2701
+ ON messages(target_agent, session_scope, status);
1914
2702
 
1915
2703
  CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
1916
- ON messages(target_agent, from_agent, server_seq);
2704
+ ON messages(target_agent, session_scope, from_agent, server_seq);
2705
+ `);
2706
+ try {
2707
+ await client.execute({
2708
+ sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
2709
+ args: []
2710
+ });
2711
+ } catch {
2712
+ }
2713
+ try {
2714
+ await client.execute({
2715
+ sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
2716
+ args: []
2717
+ });
2718
+ } catch {
2719
+ }
2720
+ await client.executeMultiple(`
2721
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
2722
+ ON notifications(agent_id, session_scope, read, created_at);
2723
+
2724
+ CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
2725
+ ON messages(target_agent, session_scope, status, created_at);
1917
2726
  `);
1918
2727
  try {
1919
2728
  await client.execute({
@@ -2497,28 +3306,45 @@ async function ensureSchema() {
2497
3306
  } catch {
2498
3307
  }
2499
3308
  }
3309
+ try {
3310
+ await client.execute({
3311
+ sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
3312
+ args: []
3313
+ });
3314
+ } catch {
3315
+ }
2500
3316
  }
2501
3317
  async function disposeDatabase() {
3318
+ if (_walCheckpointTimer) {
3319
+ clearInterval(_walCheckpointTimer);
3320
+ _walCheckpointTimer = null;
3321
+ }
2502
3322
  if (_daemonClient) {
2503
3323
  _daemonClient.close();
2504
3324
  _daemonClient = null;
2505
3325
  }
3326
+ if (_adapterClient && _adapterClient !== _resilientClient) {
3327
+ _adapterClient.close();
3328
+ }
3329
+ _adapterClient = null;
2506
3330
  if (_client) {
2507
3331
  _client.close();
2508
3332
  _client = null;
2509
3333
  _resilientClient = null;
2510
3334
  }
2511
3335
  }
2512
- var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso, disposeTurso;
3336
+ var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
2513
3337
  var init_database = __esm({
2514
3338
  "src/lib/database.ts"() {
2515
3339
  "use strict";
2516
3340
  init_db_retry();
2517
3341
  init_employees();
3342
+ init_database_adapter();
2518
3343
  _client = null;
2519
3344
  _resilientClient = null;
2520
3345
  _walCheckpointTimer = null;
2521
3346
  _daemonClient = null;
3347
+ _adapterClient = null;
2522
3348
  initTurso = initDatabase;
2523
3349
  disposeTurso = disposeDatabase;
2524
3350
  }
@@ -2541,9 +3367,12 @@ __export(license_exports, {
2541
3367
  stopLicenseRevalidation: () => stopLicenseRevalidation,
2542
3368
  validateLicense: () => validateLicense
2543
3369
  });
2544
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, existsSync as existsSync8, mkdirSync as mkdirSync4 } from "fs";
3370
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, existsSync as existsSync10, mkdirSync as mkdirSync4 } from "fs";
2545
3371
  import { randomUUID as randomUUID2 } from "crypto";
2546
- import path7 from "path";
3372
+ import { createRequire as createRequire2 } from "module";
3373
+ import { pathToFileURL as pathToFileURL2 } from "url";
3374
+ import os7 from "os";
3375
+ import path9 from "path";
2547
3376
  import { jwtVerify, importSPKI } from "jose";
2548
3377
  async function fetchRetry(url, init) {
2549
3378
  try {
@@ -2554,37 +3383,37 @@ async function fetchRetry(url, init) {
2554
3383
  }
2555
3384
  }
2556
3385
  function loadDeviceId() {
2557
- const deviceJsonPath = path7.join(EXE_AI_DIR, "device.json");
3386
+ const deviceJsonPath = path9.join(EXE_AI_DIR, "device.json");
2558
3387
  try {
2559
- if (existsSync8(deviceJsonPath)) {
2560
- const data = JSON.parse(readFileSync8(deviceJsonPath, "utf8"));
3388
+ if (existsSync10(deviceJsonPath)) {
3389
+ const data = JSON.parse(readFileSync9(deviceJsonPath, "utf8"));
2561
3390
  if (data.deviceId) return data.deviceId;
2562
3391
  }
2563
3392
  } catch {
2564
3393
  }
2565
3394
  try {
2566
- if (existsSync8(DEVICE_ID_PATH)) {
2567
- const id2 = readFileSync8(DEVICE_ID_PATH, "utf8").trim();
3395
+ if (existsSync10(DEVICE_ID_PATH)) {
3396
+ const id2 = readFileSync9(DEVICE_ID_PATH, "utf8").trim();
2568
3397
  if (id2) return id2;
2569
3398
  }
2570
3399
  } catch {
2571
3400
  }
2572
3401
  const id = randomUUID2();
2573
3402
  mkdirSync4(EXE_AI_DIR, { recursive: true });
2574
- writeFileSync5(DEVICE_ID_PATH, id, "utf8");
3403
+ writeFileSync6(DEVICE_ID_PATH, id, "utf8");
2575
3404
  return id;
2576
3405
  }
2577
3406
  function loadLicense() {
2578
3407
  try {
2579
- if (!existsSync8(LICENSE_PATH)) return null;
2580
- return readFileSync8(LICENSE_PATH, "utf8").trim();
3408
+ if (!existsSync10(LICENSE_PATH)) return null;
3409
+ return readFileSync9(LICENSE_PATH, "utf8").trim();
2581
3410
  } catch {
2582
3411
  return null;
2583
3412
  }
2584
3413
  }
2585
3414
  function saveLicense(apiKey) {
2586
3415
  mkdirSync4(EXE_AI_DIR, { recursive: true });
2587
- writeFileSync5(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
3416
+ writeFileSync6(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
2588
3417
  }
2589
3418
  async function verifyLicenseJwt(token) {
2590
3419
  try {
@@ -2610,8 +3439,8 @@ async function verifyLicenseJwt(token) {
2610
3439
  }
2611
3440
  async function getCachedLicense() {
2612
3441
  try {
2613
- if (!existsSync8(CACHE_PATH)) return null;
2614
- const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
3442
+ if (!existsSync10(CACHE_PATH)) return null;
3443
+ const raw = JSON.parse(readFileSync9(CACHE_PATH, "utf8"));
2615
3444
  if (!raw.token || typeof raw.token !== "string") return null;
2616
3445
  return await verifyLicenseJwt(raw.token);
2617
3446
  } catch {
@@ -2620,8 +3449,8 @@ async function getCachedLicense() {
2620
3449
  }
2621
3450
  function readCachedToken() {
2622
3451
  try {
2623
- if (!existsSync8(CACHE_PATH)) return null;
2624
- const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
3452
+ if (!existsSync10(CACHE_PATH)) return null;
3453
+ const raw = JSON.parse(readFileSync9(CACHE_PATH, "utf8"));
2625
3454
  return typeof raw.token === "string" ? raw.token : null;
2626
3455
  } catch {
2627
3456
  return null;
@@ -2655,56 +3484,130 @@ function getRawCachedPlan() {
2655
3484
  }
2656
3485
  function cacheResponse(token) {
2657
3486
  try {
2658
- writeFileSync5(CACHE_PATH, JSON.stringify({ token }), "utf8");
3487
+ writeFileSync6(CACHE_PATH, JSON.stringify({ token }), "utf8");
2659
3488
  } catch {
2660
3489
  }
2661
3490
  }
2662
- async function validateLicense(apiKey, deviceId) {
2663
- const did = deviceId ?? loadDeviceId();
3491
+ function loadPrismaForLicense() {
3492
+ if (_prismaFailed) return null;
3493
+ const dbUrl = process.env.DATABASE_URL;
3494
+ if (!dbUrl) {
3495
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path9.join(os7.homedir(), "exe-db");
3496
+ if (!existsSync10(path9.join(exeDbRoot, "package.json"))) {
3497
+ _prismaFailed = true;
3498
+ return null;
3499
+ }
3500
+ }
3501
+ if (!_prismaPromise) {
3502
+ _prismaPromise = (async () => {
3503
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
3504
+ if (explicitPath) {
3505
+ const mod2 = await import(pathToFileURL2(explicitPath).href);
3506
+ const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
3507
+ if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
3508
+ return new Ctor2();
3509
+ }
3510
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path9.join(os7.homedir(), "exe-db");
3511
+ const req = createRequire2(path9.join(exeDbRoot, "package.json"));
3512
+ const entry = req.resolve("@prisma/client");
3513
+ const mod = await import(pathToFileURL2(entry).href);
3514
+ const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
3515
+ if (!Ctor) throw new Error(`No PrismaClient in ${entry}`);
3516
+ return new Ctor();
3517
+ })().catch((err) => {
3518
+ _prismaFailed = true;
3519
+ _prismaPromise = null;
3520
+ throw err;
3521
+ });
3522
+ }
3523
+ return _prismaPromise;
3524
+ }
3525
+ async function validateViaPostgres(apiKey) {
3526
+ const loader = loadPrismaForLicense();
3527
+ if (!loader) return null;
3528
+ try {
3529
+ const prisma = await loader;
3530
+ const rows = await prisma.$queryRawUnsafe(
3531
+ `SELECT plan, email, status, device_limit, employee_limit, memory_limit, expires_at
3532
+ FROM billing.licenses WHERE key = $1 LIMIT 1`,
3533
+ apiKey
3534
+ );
3535
+ if (!rows || rows.length === 0) return null;
3536
+ const row = rows[0];
3537
+ if (row.status !== "active") return null;
3538
+ if (row.expires_at && new Date(row.expires_at) < /* @__PURE__ */ new Date()) return null;
3539
+ const plan = row.plan;
3540
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
3541
+ return {
3542
+ valid: true,
3543
+ plan,
3544
+ email: row.email,
3545
+ expiresAt: row.expires_at ? new Date(row.expires_at).toISOString() : null,
3546
+ deviceLimit: row.device_limit ?? limits.devices,
3547
+ employeeLimit: row.employee_limit ?? limits.employees,
3548
+ memoryLimit: row.memory_limit ?? limits.memories
3549
+ };
3550
+ } catch {
3551
+ return null;
3552
+ }
3553
+ }
3554
+ async function validateViaCFWorker(apiKey, deviceId) {
2664
3555
  try {
2665
3556
  const res = await fetchRetry(`${API_BASE}/auth/activate`, {
2666
3557
  method: "POST",
2667
3558
  headers: { "Content-Type": "application/json" },
2668
- body: JSON.stringify({ apiKey, deviceId: did }),
3559
+ body: JSON.stringify({ apiKey, deviceId }),
2669
3560
  signal: AbortSignal.timeout(1e4)
2670
3561
  });
2671
- if (res.ok) {
2672
- const data = await res.json();
2673
- if (data.error === "device_limit_exceeded") {
2674
- const cached2 = await getCachedLicense();
2675
- if (cached2) return cached2;
2676
- const raw2 = getRawCachedPlan();
2677
- if (raw2) return { ...raw2, valid: false };
2678
- return { ...FREE_LICENSE, valid: false, plan: "free" };
2679
- }
2680
- if (data.token) {
2681
- cacheResponse(data.token);
2682
- const verified = await verifyLicenseJwt(data.token);
2683
- if (verified) return verified;
2684
- }
2685
- const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
2686
- return {
2687
- valid: data.valid,
2688
- plan: data.plan,
2689
- email: data.email,
2690
- expiresAt: data.expiresAt,
2691
- deviceLimit: limits.devices,
2692
- employeeLimit: limits.employees,
2693
- memoryLimit: limits.memories
2694
- };
3562
+ if (!res.ok) return null;
3563
+ const data = await res.json();
3564
+ if (data.error === "device_limit_exceeded") return null;
3565
+ if (!data.valid) return null;
3566
+ if (data.token) {
3567
+ cacheResponse(data.token);
3568
+ const verified = await verifyLicenseJwt(data.token);
3569
+ if (verified) return verified;
3570
+ }
3571
+ const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
3572
+ return {
3573
+ valid: data.valid,
3574
+ plan: data.plan,
3575
+ email: data.email,
3576
+ expiresAt: data.expiresAt,
3577
+ deviceLimit: limits.devices,
3578
+ employeeLimit: limits.employees,
3579
+ memoryLimit: limits.memories
3580
+ };
3581
+ } catch {
3582
+ return null;
3583
+ }
3584
+ }
3585
+ async function validateLicense(apiKey, deviceId) {
3586
+ const did = deviceId ?? loadDeviceId();
3587
+ const pgResult = await validateViaPostgres(apiKey);
3588
+ if (pgResult) {
3589
+ try {
3590
+ writeFileSync6(CACHE_PATH, JSON.stringify({ pgLicense: pgResult, ts: Date.now() }), "utf8");
3591
+ } catch {
3592
+ }
3593
+ return pgResult;
3594
+ }
3595
+ const cfResult = await validateViaCFWorker(apiKey, did);
3596
+ if (cfResult) return cfResult;
3597
+ const cached = await getCachedLicense();
3598
+ if (cached) return cached;
3599
+ try {
3600
+ if (existsSync10(CACHE_PATH)) {
3601
+ const raw = JSON.parse(readFileSync9(CACHE_PATH, "utf8"));
3602
+ if (raw.pgLicense && raw.ts && Date.now() - raw.ts < 7 * 24 * 60 * 60 * 1e3) {
3603
+ return raw.pgLicense;
3604
+ }
2695
3605
  }
2696
- const cached = await getCachedLicense();
2697
- if (cached) return cached;
2698
- const raw = getRawCachedPlan();
2699
- if (raw) return raw;
2700
- return { ...FREE_LICENSE, valid: false, plan: "free" };
2701
3606
  } catch {
2702
- const cached = await getCachedLicense();
2703
- if (cached) return cached;
2704
- const rawFallback = getRawCachedPlan();
2705
- if (rawFallback) return rawFallback;
2706
- return { ...FREE_LICENSE, valid: false, error: "offline" };
2707
3607
  }
3608
+ const rawFallback = getRawCachedPlan();
3609
+ if (rawFallback) return rawFallback;
3610
+ return { ...FREE_LICENSE, valid: false };
2708
3611
  }
2709
3612
  function getCacheAgeMs() {
2710
3613
  try {
@@ -2719,9 +3622,9 @@ async function checkLicense() {
2719
3622
  let key = loadLicense();
2720
3623
  if (!key) {
2721
3624
  try {
2722
- const configPath = path7.join(EXE_AI_DIR, "config.json");
2723
- if (existsSync8(configPath)) {
2724
- const raw = JSON.parse(readFileSync8(configPath, "utf8"));
3625
+ const configPath = path9.join(EXE_AI_DIR, "config.json");
3626
+ if (existsSync10(configPath)) {
3627
+ const raw = JSON.parse(readFileSync9(configPath, "utf8"));
2725
3628
  const cloud = raw.cloud;
2726
3629
  if (cloud?.apiKey) {
2727
3630
  key = cloud.apiKey;
@@ -2875,14 +3778,14 @@ function stopLicenseRevalidation() {
2875
3778
  _revalTimer = null;
2876
3779
  }
2877
3780
  }
2878
- var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, RETRY_DELAY_MS, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE, CACHE_MAX_AGE_MS, _revalTimer;
3781
+ var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, RETRY_DELAY_MS, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE, _prismaPromise, _prismaFailed, CACHE_MAX_AGE_MS, _revalTimer;
2879
3782
  var init_license = __esm({
2880
3783
  "src/lib/license.ts"() {
2881
3784
  "use strict";
2882
3785
  init_config();
2883
- LICENSE_PATH = path7.join(EXE_AI_DIR, "license.key");
2884
- CACHE_PATH = path7.join(EXE_AI_DIR, "license-cache.json");
2885
- DEVICE_ID_PATH = path7.join(EXE_AI_DIR, "device-id");
3786
+ LICENSE_PATH = path9.join(EXE_AI_DIR, "license.key");
3787
+ CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
3788
+ DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
2886
3789
  API_BASE = "https://askexe.com/cloud";
2887
3790
  RETRY_DELAY_MS = 500;
2888
3791
  LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
@@ -2906,18 +3809,20 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
2906
3809
  employeeLimit: 1,
2907
3810
  memoryLimit: 5e3
2908
3811
  };
3812
+ _prismaPromise = null;
3813
+ _prismaFailed = false;
2909
3814
  CACHE_MAX_AGE_MS = 36e5;
2910
3815
  _revalTimer = null;
2911
3816
  }
2912
3817
  });
2913
3818
 
2914
3819
  // src/lib/plan-limits.ts
2915
- import { readFileSync as readFileSync9, existsSync as existsSync9 } from "fs";
2916
- import path8 from "path";
3820
+ import { readFileSync as readFileSync10, existsSync as existsSync11 } from "fs";
3821
+ import path10 from "path";
2917
3822
  function getLicenseSync() {
2918
3823
  try {
2919
- if (!existsSync9(CACHE_PATH2)) return freeLicense();
2920
- const raw = JSON.parse(readFileSync9(CACHE_PATH2, "utf8"));
3824
+ if (!existsSync11(CACHE_PATH2)) return freeLicense();
3825
+ const raw = JSON.parse(readFileSync10(CACHE_PATH2, "utf8"));
2921
3826
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
2922
3827
  const parts = raw.token.split(".");
2923
3828
  if (parts.length !== 3) return freeLicense();
@@ -2955,8 +3860,8 @@ function assertEmployeeLimitSync(rosterPath) {
2955
3860
  const filePath = rosterPath ?? EMPLOYEES_PATH;
2956
3861
  let count = 0;
2957
3862
  try {
2958
- if (existsSync9(filePath)) {
2959
- const raw = readFileSync9(filePath, "utf8");
3863
+ if (existsSync11(filePath)) {
3864
+ const raw = readFileSync10(filePath, "utf8");
2960
3865
  const employees = JSON.parse(raw);
2961
3866
  count = Array.isArray(employees) ? employees.length : 0;
2962
3867
  }
@@ -2985,29 +3890,30 @@ var init_plan_limits = __esm({
2985
3890
  this.name = "PlanLimitError";
2986
3891
  }
2987
3892
  };
2988
- CACHE_PATH2 = path8.join(EXE_AI_DIR, "license-cache.json");
3893
+ CACHE_PATH2 = path10.join(EXE_AI_DIR, "license-cache.json");
2989
3894
  }
2990
3895
  });
2991
3896
 
2992
3897
  // src/lib/notifications.ts
2993
- import crypto from "crypto";
2994
- import path9 from "path";
2995
- import os6 from "os";
3898
+ import crypto2 from "crypto";
3899
+ import path11 from "path";
3900
+ import os8 from "os";
2996
3901
  import {
2997
- readFileSync as readFileSync10,
3902
+ readFileSync as readFileSync11,
2998
3903
  readdirSync,
2999
3904
  unlinkSync as unlinkSync3,
3000
- existsSync as existsSync10,
3905
+ existsSync as existsSync12,
3001
3906
  rmdirSync
3002
3907
  } from "fs";
3003
3908
  async function writeNotification(notification) {
3004
3909
  try {
3005
3910
  const client = getClient();
3006
- const id = crypto.randomUUID();
3911
+ const id = crypto2.randomUUID();
3007
3912
  const now = (/* @__PURE__ */ new Date()).toISOString();
3913
+ const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
3008
3914
  await client.execute({
3009
- sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
3010
- VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)`,
3915
+ sql: `INSERT INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, session_scope, read, created_at)
3916
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`,
3011
3917
  args: [
3012
3918
  id,
3013
3919
  notification.agentId,
@@ -3016,6 +3922,7 @@ async function writeNotification(notification) {
3016
3922
  notification.project,
3017
3923
  notification.summary,
3018
3924
  notification.taskFile ?? null,
3925
+ sessionScope,
3019
3926
  now
3020
3927
  ]
3021
3928
  });
@@ -3024,12 +3931,14 @@ async function writeNotification(notification) {
3024
3931
  `);
3025
3932
  }
3026
3933
  }
3027
- async function markAsReadByTaskFile(taskFile) {
3934
+ async function markAsReadByTaskFile(taskFile, sessionScope) {
3028
3935
  try {
3029
3936
  const client = getClient();
3937
+ const scope = strictSessionScopeFilter(sessionScope);
3030
3938
  await client.execute({
3031
- sql: "UPDATE notifications SET read = 1 WHERE task_file = ? AND read = 0",
3032
- args: [taskFile]
3939
+ sql: `UPDATE notifications SET read = 1
3940
+ WHERE task_file = ? AND read = 0${scope.sql}`,
3941
+ args: [taskFile, ...scope.args]
3033
3942
  });
3034
3943
  } catch {
3035
3944
  }
@@ -3038,11 +3947,12 @@ var init_notifications = __esm({
3038
3947
  "src/lib/notifications.ts"() {
3039
3948
  "use strict";
3040
3949
  init_database();
3950
+ init_task_scope();
3041
3951
  }
3042
3952
  });
3043
3953
 
3044
3954
  // src/lib/session-kill-telemetry.ts
3045
- import crypto2 from "crypto";
3955
+ import crypto3 from "crypto";
3046
3956
  async function recordSessionKill(input) {
3047
3957
  try {
3048
3958
  const client = getClient();
@@ -3052,7 +3962,7 @@ async function recordSessionKill(input) {
3052
3962
  ticks_idle, estimated_tokens_saved)
3053
3963
  VALUES (?, ?, ?, ?, ?, ?, ?)`,
3054
3964
  args: [
3055
- crypto2.randomUUID(),
3965
+ crypto3.randomUUID(),
3056
3966
  input.sessionName,
3057
3967
  input.agentId,
3058
3968
  (/* @__PURE__ */ new Date()).toISOString(),
@@ -3147,12 +4057,12 @@ __export(tasks_crud_exports, {
3147
4057
  updateTaskStatus: () => updateTaskStatus,
3148
4058
  writeCheckpoint: () => writeCheckpoint
3149
4059
  });
3150
- import crypto3 from "crypto";
3151
- import path10 from "path";
3152
- import os7 from "os";
4060
+ import crypto4 from "crypto";
4061
+ import path12 from "path";
4062
+ import os9 from "os";
3153
4063
  import { execSync as execSync5 } from "child_process";
3154
4064
  import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
3155
- import { existsSync as existsSync11, readFileSync as readFileSync11 } from "fs";
4065
+ import { existsSync as existsSync13, readFileSync as readFileSync12 } from "fs";
3156
4066
  async function writeCheckpoint(input) {
3157
4067
  const client = getClient();
3158
4068
  const row = await resolveTask(client, input.taskId);
@@ -3268,7 +4178,7 @@ async function resolveTask(client, identifier, scopeSession) {
3268
4178
  }
3269
4179
  async function createTaskCore(input) {
3270
4180
  const client = getClient();
3271
- const id = crypto3.randomUUID();
4181
+ const id = crypto4.randomUUID();
3272
4182
  const now = (/* @__PURE__ */ new Date()).toISOString();
3273
4183
  const slug = slugify(input.title);
3274
4184
  let earlySessionScope = null;
@@ -3327,8 +4237,8 @@ ${laneWarning}` : laneWarning;
3327
4237
  }
3328
4238
  if (input.baseDir) {
3329
4239
  try {
3330
- await mkdir3(path10.join(input.baseDir, "exe", "output"), { recursive: true });
3331
- await mkdir3(path10.join(input.baseDir, "exe", "research"), { recursive: true });
4240
+ await mkdir3(path12.join(input.baseDir, "exe", "output"), { recursive: true });
4241
+ await mkdir3(path12.join(input.baseDir, "exe", "research"), { recursive: true });
3332
4242
  await ensureArchitectureDoc(input.baseDir, input.projectName);
3333
4243
  await ensureGitignoreExe(input.baseDir);
3334
4244
  } catch {
@@ -3364,13 +4274,19 @@ ${laneWarning}` : laneWarning;
3364
4274
  });
3365
4275
  if (input.baseDir) {
3366
4276
  try {
3367
- const EXE_OS_DIR = path10.join(os7.homedir(), ".exe-os");
3368
- const mdPath = path10.join(EXE_OS_DIR, taskFile);
3369
- const mdDir = path10.dirname(mdPath);
3370
- if (!existsSync11(mdDir)) await mkdir3(mdDir, { recursive: true });
4277
+ const EXE_OS_DIR = path12.join(os9.homedir(), ".exe-os");
4278
+ const mdPath = path12.join(EXE_OS_DIR, taskFile);
4279
+ const mdDir = path12.dirname(mdPath);
4280
+ if (!existsSync13(mdDir)) await mkdir3(mdDir, { recursive: true });
3371
4281
  const reviewer = input.reviewer ?? input.assignedBy;
3372
4282
  const mdContent = `# ${input.title}
3373
4283
 
4284
+ ## MANDATORY: When done
4285
+
4286
+ You MUST call update_task with status "done" and a result summary when finished.
4287
+ If you skip this, your reviewer will not know you're done and your work won't be reviewed.
4288
+ Do NOT let a failed commit or any error prevent you from calling update_task(done).
4289
+
3374
4290
  **ID:** ${id}
3375
4291
  **Status:** ${initialStatus}
3376
4292
  **Priority:** ${input.priority}
@@ -3384,12 +4300,6 @@ ${laneWarning}` : laneWarning;
3384
4300
  ## Context
3385
4301
 
3386
4302
  ${input.context}
3387
-
3388
- ## MANDATORY: When done
3389
-
3390
- You MUST call update_task with status "done" and a result summary when finished.
3391
- If you skip this, your reviewer will not know you're done and your work won't be reviewed.
3392
- Do NOT let a failed commit or any error prevent you from calling update_task(done).
3393
4303
  `;
3394
4304
  await writeFile3(mdPath, mdContent, "utf-8");
3395
4305
  } catch (err) {
@@ -3638,7 +4548,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
3638
4548
  await client.execute("PRAGMA wal_checkpoint(PASSIVE)");
3639
4549
  } catch {
3640
4550
  }
3641
- if (input.status === "done" || input.status === "cancelled") {
4551
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
3642
4552
  try {
3643
4553
  const { clearQueueForAgent: clearQueueForAgent2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
3644
4554
  clearQueueForAgent2(String(row.assigned_to));
@@ -3667,9 +4577,9 @@ async function deleteTaskCore(taskId, _baseDir) {
3667
4577
  return { taskFile, assignedTo, assignedBy, taskSlug };
3668
4578
  }
3669
4579
  async function ensureArchitectureDoc(baseDir, projectName) {
3670
- const archPath = path10.join(baseDir, "exe", "ARCHITECTURE.md");
4580
+ const archPath = path12.join(baseDir, "exe", "ARCHITECTURE.md");
3671
4581
  try {
3672
- if (existsSync11(archPath)) return;
4582
+ if (existsSync13(archPath)) return;
3673
4583
  const template = [
3674
4584
  `# ${projectName} \u2014 System Architecture`,
3675
4585
  "",
@@ -3702,10 +4612,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
3702
4612
  }
3703
4613
  }
3704
4614
  async function ensureGitignoreExe(baseDir) {
3705
- const gitignorePath = path10.join(baseDir, ".gitignore");
4615
+ const gitignorePath = path12.join(baseDir, ".gitignore");
3706
4616
  try {
3707
- if (existsSync11(gitignorePath)) {
3708
- const content = readFileSync11(gitignorePath, "utf-8");
4617
+ if (existsSync13(gitignorePath)) {
4618
+ const content = readFileSync12(gitignorePath, "utf-8");
3709
4619
  if (/^\/?exe\/?$/m.test(content)) return;
3710
4620
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
3711
4621
  } else {
@@ -3736,58 +4646,42 @@ var init_tasks_crud = __esm({
3736
4646
  });
3737
4647
 
3738
4648
  // src/lib/tasks-review.ts
3739
- import path11 from "path";
3740
- import { existsSync as existsSync12, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
4649
+ import path13 from "path";
4650
+ import { existsSync as existsSync14, readdirSync as readdirSync2, unlinkSync as unlinkSync4 } from "fs";
3741
4651
  async function countPendingReviews(sessionScope) {
3742
4652
  const client = getClient();
3743
- if (sessionScope) {
3744
- const result2 = await client.execute({
3745
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review' AND (session_scope = ? OR session_scope IS NULL)",
3746
- args: [sessionScope]
3747
- });
3748
- return Number(result2.rows[0]?.cnt) || 0;
3749
- }
4653
+ const scope = strictSessionScopeFilter(
4654
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
4655
+ );
3750
4656
  const result = await client.execute({
3751
- sql: "SELECT COUNT(*) as cnt FROM tasks WHERE status = 'needs_review'",
3752
- args: []
4657
+ sql: `SELECT COUNT(*) as cnt FROM tasks
4658
+ WHERE status = 'needs_review'${scope.sql}`,
4659
+ args: [...scope.args]
3753
4660
  });
3754
4661
  return Number(result.rows[0]?.cnt) || 0;
3755
4662
  }
3756
4663
  async function countNewPendingReviewsSince(sinceIso, sessionScope) {
3757
4664
  const client = getClient();
3758
- if (sessionScope) {
3759
- const result2 = await client.execute({
3760
- sql: `SELECT COUNT(*) as cnt FROM tasks
3761
- WHERE status = 'needs_review' AND updated_at > ?
3762
- AND session_scope = ?`,
3763
- args: [sinceIso, sessionScope]
3764
- });
3765
- return Number(result2.rows[0]?.cnt) || 0;
3766
- }
4665
+ const scope = strictSessionScopeFilter(
4666
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
4667
+ );
3767
4668
  const result = await client.execute({
3768
4669
  sql: `SELECT COUNT(*) as cnt FROM tasks
3769
- WHERE status = 'needs_review' AND updated_at > ?`,
3770
- args: [sinceIso]
4670
+ WHERE status = 'needs_review' AND updated_at > ?${scope.sql}`,
4671
+ args: [sinceIso, ...scope.args]
3771
4672
  });
3772
4673
  return Number(result.rows[0]?.cnt) || 0;
3773
4674
  }
3774
4675
  async function listPendingReviews(limit, sessionScope) {
3775
4676
  const client = getClient();
3776
- if (sessionScope) {
3777
- const result2 = await client.execute({
3778
- sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
3779
- WHERE status = 'needs_review'
3780
- AND session_scope = ?
3781
- ORDER BY updated_at ASC LIMIT ?`,
3782
- args: [sessionScope, limit]
3783
- });
3784
- return result2.rows;
3785
- }
4677
+ const scope = strictSessionScopeFilter(
4678
+ sessionScope === void 0 ? getCurrentSessionScope() : sessionScope
4679
+ );
3786
4680
  const result = await client.execute({
3787
4681
  sql: `SELECT title, assigned_to, project_name, updated_at FROM tasks
3788
- WHERE status = 'needs_review'
4682
+ WHERE status = 'needs_review'${scope.sql}
3789
4683
  ORDER BY updated_at ASC LIMIT ?`,
3790
- args: [limit]
4684
+ args: [...scope.args, limit]
3791
4685
  });
3792
4686
  return result.rows;
3793
4687
  }
@@ -3799,7 +4693,7 @@ async function cleanupOrphanedReviews() {
3799
4693
  WHERE status IN ('open', 'needs_review', 'in_progress')
3800
4694
  AND assigned_by = 'system'
3801
4695
  AND title LIKE 'Review:%'
3802
- AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled'))`,
4696
+ AND parent_task_id IN (SELECT id FROM tasks WHERE status IN ('done', 'cancelled', 'closed'))`,
3803
4697
  args: [now]
3804
4698
  });
3805
4699
  const r1b = await client.execute({
@@ -3918,11 +4812,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
3918
4812
  );
3919
4813
  }
3920
4814
  try {
3921
- const cacheDir = path11.join(EXE_AI_DIR, "session-cache");
3922
- if (existsSync12(cacheDir)) {
4815
+ const cacheDir = path13.join(EXE_AI_DIR, "session-cache");
4816
+ if (existsSync14(cacheDir)) {
3923
4817
  for (const f of readdirSync2(cacheDir)) {
3924
4818
  if (f.startsWith("review-notified-")) {
3925
- unlinkSync4(path11.join(cacheDir, f));
4819
+ unlinkSync4(path13.join(cacheDir, f));
3926
4820
  }
3927
4821
  }
3928
4822
  }
@@ -3939,11 +4833,12 @@ var init_tasks_review = __esm({
3939
4833
  init_tmux_routing();
3940
4834
  init_session_key();
3941
4835
  init_state_bus();
4836
+ init_task_scope();
3942
4837
  }
3943
4838
  });
3944
4839
 
3945
4840
  // src/lib/tasks-chain.ts
3946
- import path12 from "path";
4841
+ import path14 from "path";
3947
4842
  import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
3948
4843
  async function cascadeUnblock(taskId, baseDir, now) {
3949
4844
  const client = getClient();
@@ -3960,7 +4855,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
3960
4855
  });
3961
4856
  for (const ur of unblockedRows.rows) {
3962
4857
  try {
3963
- const ubFile = path12.join(baseDir, String(ur.task_file));
4858
+ const ubFile = path14.join(baseDir, String(ur.task_file));
3964
4859
  let ubContent = await readFile3(ubFile, "utf-8");
3965
4860
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
3966
4861
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -3995,7 +4890,7 @@ async function checkSubtaskCompletion(parentTaskId, projectName) {
3995
4890
  const scScope = sessionScopeFilter();
3996
4891
  const remaining = await client.execute({
3997
4892
  sql: `SELECT COUNT(*) as cnt FROM tasks
3998
- WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled')${scScope.sql}`,
4893
+ WHERE parent_task_id = ? AND status NOT IN ('done', 'cancelled', 'closed')${scScope.sql}`,
3999
4894
  args: [parentTaskId, ...scScope.args]
4000
4895
  });
4001
4896
  const cnt = Number(remaining.rows[0]?.cnt ?? 1);
@@ -4029,7 +4924,7 @@ var init_tasks_chain = __esm({
4029
4924
 
4030
4925
  // src/lib/project-name.ts
4031
4926
  import { execSync as execSync6 } from "child_process";
4032
- import path13 from "path";
4927
+ import path15 from "path";
4033
4928
  function getProjectName(cwd2) {
4034
4929
  const dir = cwd2 ?? process.cwd();
4035
4930
  if (_cached2 && _cachedCwd === dir) return _cached2;
@@ -4042,7 +4937,7 @@ function getProjectName(cwd2) {
4042
4937
  timeout: 2e3,
4043
4938
  stdio: ["pipe", "pipe", "pipe"]
4044
4939
  }).trim();
4045
- repoRoot = path13.dirname(gitCommonDir);
4940
+ repoRoot = path15.dirname(gitCommonDir);
4046
4941
  } catch {
4047
4942
  repoRoot = execSync6("git rev-parse --show-toplevel", {
4048
4943
  cwd: dir,
@@ -4051,11 +4946,11 @@ function getProjectName(cwd2) {
4051
4946
  stdio: ["pipe", "pipe", "pipe"]
4052
4947
  }).trim();
4053
4948
  }
4054
- _cached2 = path13.basename(repoRoot);
4949
+ _cached2 = path15.basename(repoRoot);
4055
4950
  _cachedCwd = dir;
4056
4951
  return _cached2;
4057
4952
  } catch {
4058
- _cached2 = path13.basename(dir);
4953
+ _cached2 = path15.basename(dir);
4059
4954
  _cachedCwd = dir;
4060
4955
  return _cached2;
4061
4956
  }
@@ -4198,10 +5093,10 @@ var init_tasks_notify = __esm({
4198
5093
  });
4199
5094
 
4200
5095
  // src/lib/behaviors.ts
4201
- import crypto4 from "crypto";
5096
+ import crypto5 from "crypto";
4202
5097
  async function storeBehavior(opts) {
4203
5098
  const client = getClient();
4204
- const id = crypto4.randomUUID();
5099
+ const id = crypto5.randomUUID();
4205
5100
  const now = (/* @__PURE__ */ new Date()).toISOString();
4206
5101
  await client.execute({
4207
5102
  sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
@@ -4230,7 +5125,7 @@ __export(skill_learning_exports, {
4230
5125
  storeTrajectory: () => storeTrajectory,
4231
5126
  sweepTrajectories: () => sweepTrajectories
4232
5127
  });
4233
- import crypto5 from "crypto";
5128
+ import crypto6 from "crypto";
4234
5129
  async function extractTrajectory(taskId, agentId) {
4235
5130
  const client = getClient();
4236
5131
  const result = await client.execute({
@@ -4259,11 +5154,11 @@ async function extractTrajectory(taskId, agentId) {
4259
5154
  return signature;
4260
5155
  }
4261
5156
  function hashSignature(signature) {
4262
- return crypto5.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
5157
+ return crypto6.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
4263
5158
  }
4264
5159
  async function storeTrajectory(opts) {
4265
5160
  const client = getClient();
4266
- const id = crypto5.randomUUID();
5161
+ const id = crypto6.randomUUID();
4267
5162
  const now = (/* @__PURE__ */ new Date()).toISOString();
4268
5163
  const signatureHash = hashSignature(opts.signature);
4269
5164
  await client.execute({
@@ -4528,8 +5423,8 @@ __export(tasks_exports, {
4528
5423
  updateTaskStatus: () => updateTaskStatus,
4529
5424
  writeCheckpoint: () => writeCheckpoint
4530
5425
  });
4531
- import path14 from "path";
4532
- import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
5426
+ import path16 from "path";
5427
+ import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
4533
5428
  async function createTask(input) {
4534
5429
  const result = await createTaskCore(input);
4535
5430
  if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -4548,12 +5443,12 @@ async function updateTask(input) {
4548
5443
  const { row, taskFile, now, taskId } = await updateTaskStatus(input);
4549
5444
  try {
4550
5445
  const agent = String(row.assigned_to);
4551
- const cacheDir = path14.join(EXE_AI_DIR, "session-cache");
4552
- const cachePath = path14.join(cacheDir, `current-task-${agent}.json`);
5446
+ const cacheDir = path16.join(EXE_AI_DIR, "session-cache");
5447
+ const cachePath = path16.join(cacheDir, `current-task-${agent}.json`);
4553
5448
  if (input.status === "in_progress") {
4554
5449
  mkdirSync5(cacheDir, { recursive: true });
4555
- writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
4556
- } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
5450
+ writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
5451
+ } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled" || input.status === "closed") {
4557
5452
  try {
4558
5453
  unlinkSync5(cachePath);
4559
5454
  } catch {
@@ -4561,10 +5456,10 @@ async function updateTask(input) {
4561
5456
  }
4562
5457
  } catch {
4563
5458
  }
4564
- if (input.status === "done") {
5459
+ if (input.status === "done" || input.status === "closed") {
4565
5460
  await cleanupReviewFile(row, taskFile, input.baseDir);
4566
5461
  }
4567
- if (input.status === "done" || input.status === "cancelled") {
5462
+ if (input.status === "done" || input.status === "cancelled" || input.status === "closed") {
4568
5463
  try {
4569
5464
  const client = getClient();
4570
5465
  const taskTitle = String(row.title);
@@ -4580,7 +5475,7 @@ async function updateTask(input) {
4580
5475
  if (!isCoordinatorName(assignedAgent)) {
4581
5476
  try {
4582
5477
  const draftClient = getClient();
4583
- if (input.status === "done") {
5478
+ if (input.status === "done" || input.status === "closed") {
4584
5479
  await draftClient.execute({
4585
5480
  sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
4586
5481
  args: [assignedAgent]
@@ -4597,7 +5492,7 @@ async function updateTask(input) {
4597
5492
  try {
4598
5493
  const client = getClient();
4599
5494
  const cascaded = await client.execute({
4600
- sql: `UPDATE tasks SET status = 'done', updated_at = ?
5495
+ sql: `UPDATE tasks SET status = 'closed', updated_at = ?
4601
5496
  WHERE parent_task_id = ? AND status = 'needs_review'`,
4602
5497
  args: [now, taskId]
4603
5498
  });
@@ -4610,14 +5505,14 @@ async function updateTask(input) {
4610
5505
  } catch {
4611
5506
  }
4612
5507
  }
4613
- const isTerminal = input.status === "done" || input.status === "needs_review";
5508
+ const isTerminal = input.status === "done" || input.status === "needs_review" || input.status === "closed";
4614
5509
  if (isTerminal) {
4615
5510
  const isCoordinator = isCoordinatorName(String(row.assigned_to));
4616
5511
  if (!isCoordinator) {
4617
5512
  notifyTaskDone();
4618
5513
  }
4619
5514
  await markTaskNotificationsRead(taskFile);
4620
- if (input.status === "done") {
5515
+ if (input.status === "done" || input.status === "closed") {
4621
5516
  try {
4622
5517
  await cascadeUnblock(taskId, input.baseDir, now);
4623
5518
  } catch {
@@ -4637,7 +5532,7 @@ async function updateTask(input) {
4637
5532
  }
4638
5533
  }
4639
5534
  }
4640
- if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
5535
+ if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
4641
5536
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
4642
5537
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
4643
5538
  taskId,
@@ -5009,6 +5904,7 @@ __export(tmux_routing_exports, {
5009
5904
  isEmployeeAlive: () => isEmployeeAlive,
5010
5905
  isExeSession: () => isExeSession,
5011
5906
  isSessionBusy: () => isSessionBusy,
5907
+ notifyCoordinatorTaskCompletion: () => notifyCoordinatorTaskCompletion,
5012
5908
  notifyParentExe: () => notifyParentExe,
5013
5909
  parseParentExe: () => parseParentExe,
5014
5910
  registerParentExe: () => registerParentExe,
@@ -5019,13 +5915,13 @@ __export(tmux_routing_exports, {
5019
5915
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
5020
5916
  });
5021
5917
  import { execFileSync as execFileSync3, execSync as execSync7 } from "child_process";
5022
- import { readFileSync as readFileSync12, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync13, appendFileSync, readdirSync as readdirSync3 } from "fs";
5023
- import path15 from "path";
5024
- import os8 from "os";
5918
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync8, mkdirSync as mkdirSync6, existsSync as existsSync15, appendFileSync, readdirSync as readdirSync3 } from "fs";
5919
+ import path17 from "path";
5920
+ import os10 from "os";
5025
5921
  import { fileURLToPath as fileURLToPath2 } from "url";
5026
5922
  import { unlinkSync as unlinkSync6 } from "fs";
5027
5923
  function spawnLockPath(sessionName) {
5028
- return path15.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
5924
+ return path17.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
5029
5925
  }
5030
5926
  function isProcessAlive(pid) {
5031
5927
  try {
@@ -5036,13 +5932,13 @@ function isProcessAlive(pid) {
5036
5932
  }
5037
5933
  }
5038
5934
  function acquireSpawnLock2(sessionName) {
5039
- if (!existsSync13(SPAWN_LOCK_DIR)) {
5935
+ if (!existsSync15(SPAWN_LOCK_DIR)) {
5040
5936
  mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
5041
5937
  }
5042
5938
  const lockFile = spawnLockPath(sessionName);
5043
- if (existsSync13(lockFile)) {
5939
+ if (existsSync15(lockFile)) {
5044
5940
  try {
5045
- const lock = JSON.parse(readFileSync12(lockFile, "utf8"));
5941
+ const lock = JSON.parse(readFileSync13(lockFile, "utf8"));
5046
5942
  const age = Date.now() - lock.timestamp;
5047
5943
  if (isProcessAlive(lock.pid) && age < 6e4) {
5048
5944
  return false;
@@ -5050,7 +5946,7 @@ function acquireSpawnLock2(sessionName) {
5050
5946
  } catch {
5051
5947
  }
5052
5948
  }
5053
- writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
5949
+ writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
5054
5950
  return true;
5055
5951
  }
5056
5952
  function releaseSpawnLock2(sessionName) {
@@ -5062,13 +5958,13 @@ function releaseSpawnLock2(sessionName) {
5062
5958
  function resolveBehaviorsExporterScript() {
5063
5959
  try {
5064
5960
  const thisFile = fileURLToPath2(import.meta.url);
5065
- const scriptPath = path15.join(
5066
- path15.dirname(thisFile),
5961
+ const scriptPath = path17.join(
5962
+ path17.dirname(thisFile),
5067
5963
  "..",
5068
5964
  "bin",
5069
5965
  "exe-export-behaviors.js"
5070
5966
  );
5071
- return existsSync13(scriptPath) ? scriptPath : null;
5967
+ return existsSync15(scriptPath) ? scriptPath : null;
5072
5968
  } catch {
5073
5969
  return null;
5074
5970
  }
@@ -5134,12 +6030,12 @@ function extractRootExe(name) {
5134
6030
  return parts.length > 0 ? parts[parts.length - 1] : null;
5135
6031
  }
5136
6032
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
5137
- if (!existsSync13(SESSION_CACHE)) {
6033
+ if (!existsSync15(SESSION_CACHE)) {
5138
6034
  mkdirSync6(SESSION_CACHE, { recursive: true });
5139
6035
  }
5140
6036
  const rootExe = extractRootExe(parentExe) ?? parentExe;
5141
- const filePath = path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
5142
- writeFileSync7(filePath, JSON.stringify({
6037
+ const filePath = path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
6038
+ writeFileSync8(filePath, JSON.stringify({
5143
6039
  parentExe: rootExe,
5144
6040
  dispatchedBy: dispatchedBy || rootExe,
5145
6041
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -5147,7 +6043,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
5147
6043
  }
5148
6044
  function getParentExe(sessionKey) {
5149
6045
  try {
5150
- const data = JSON.parse(readFileSync12(path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
6046
+ const data = JSON.parse(readFileSync13(path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
5151
6047
  return data.parentExe || null;
5152
6048
  } catch {
5153
6049
  return null;
@@ -5155,8 +6051,8 @@ function getParentExe(sessionKey) {
5155
6051
  }
5156
6052
  function getDispatchedBy(sessionKey) {
5157
6053
  try {
5158
- const data = JSON.parse(readFileSync12(
5159
- path15.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
6054
+ const data = JSON.parse(readFileSync13(
6055
+ path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
5160
6056
  "utf8"
5161
6057
  ));
5162
6058
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -5226,8 +6122,8 @@ async function verifyPaneAtCapacity(sessionName) {
5226
6122
  }
5227
6123
  function readDebounceState() {
5228
6124
  try {
5229
- if (!existsSync13(DEBOUNCE_FILE)) return {};
5230
- const raw = JSON.parse(readFileSync12(DEBOUNCE_FILE, "utf8"));
6125
+ if (!existsSync15(DEBOUNCE_FILE)) return {};
6126
+ const raw = JSON.parse(readFileSync13(DEBOUNCE_FILE, "utf8"));
5231
6127
  const state = {};
5232
6128
  for (const [key, val] of Object.entries(raw)) {
5233
6129
  if (typeof val === "number") {
@@ -5243,8 +6139,8 @@ function readDebounceState() {
5243
6139
  }
5244
6140
  function writeDebounceState(state) {
5245
6141
  try {
5246
- if (!existsSync13(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
5247
- writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
6142
+ if (!existsSync15(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
6143
+ writeFileSync8(DEBOUNCE_FILE, JSON.stringify(state));
5248
6144
  } catch {
5249
6145
  }
5250
6146
  }
@@ -5342,8 +6238,8 @@ function sendIntercom(targetSession) {
5342
6238
  try {
5343
6239
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
5344
6240
  const agent = baseAgentName(rawAgent);
5345
- const markerPath = path15.join(SESSION_CACHE, `current-task-${agent}.json`);
5346
- if (existsSync13(markerPath)) {
6241
+ const markerPath = path17.join(SESSION_CACHE, `current-task-${agent}.json`);
6242
+ if (existsSync15(markerPath)) {
5347
6243
  logIntercom(`SKIP \u2192 ${targetSession} (has in_progress task marker \u2014 will auto-chain)`);
5348
6244
  return "debounced";
5349
6245
  }
@@ -5352,8 +6248,8 @@ function sendIntercom(targetSession) {
5352
6248
  try {
5353
6249
  const rawAgent = targetSession.split("-")[0] ?? targetSession;
5354
6250
  const agent = baseAgentName(rawAgent);
5355
- const taskDir = path15.join(process.cwd(), "exe", agent);
5356
- if (existsSync13(taskDir)) {
6251
+ const taskDir = path17.join(process.cwd(), "exe", agent);
6252
+ if (existsSync15(taskDir)) {
5357
6253
  const files = readdirSync3(taskDir).filter(
5358
6254
  (f) => f.endsWith(".md") && f !== "DONE.txt"
5359
6255
  );
@@ -5413,6 +6309,21 @@ function notifyParentExe(sessionKey) {
5413
6309
  }
5414
6310
  return true;
5415
6311
  }
6312
+ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitle) {
6313
+ const transport = getTransport();
6314
+ try {
6315
+ const sessions = transport.listSessions();
6316
+ if (!sessions.includes(coordinatorSession)) return false;
6317
+ execSync7(
6318
+ `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
6319
+ { timeout: 3e3 }
6320
+ );
6321
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}")`);
6322
+ return true;
6323
+ } catch {
6324
+ return false;
6325
+ }
6326
+ }
5416
6327
  function ensureEmployee(employeeName, exeSession, projectDir, opts) {
5417
6328
  if (isCoordinatorName(employeeName)) {
5418
6329
  return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
@@ -5486,26 +6397,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5486
6397
  const transport = getTransport();
5487
6398
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
5488
6399
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
5489
- const logDir = path15.join(os8.homedir(), ".exe-os", "session-logs");
5490
- const logFile = path15.join(logDir, `${instanceLabel}-${Date.now()}.log`);
5491
- if (!existsSync13(logDir)) {
6400
+ const logDir = path17.join(os10.homedir(), ".exe-os", "session-logs");
6401
+ const logFile = path17.join(logDir, `${instanceLabel}-${Date.now()}.log`);
6402
+ if (!existsSync15(logDir)) {
5492
6403
  mkdirSync6(logDir, { recursive: true });
5493
6404
  }
5494
6405
  transport.kill(sessionName);
5495
6406
  let cleanupSuffix = "";
5496
6407
  try {
5497
6408
  const thisFile = fileURLToPath2(import.meta.url);
5498
- const cleanupScript = path15.join(path15.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
5499
- if (existsSync13(cleanupScript)) {
6409
+ const cleanupScript = path17.join(path17.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
6410
+ if (existsSync15(cleanupScript)) {
5500
6411
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
5501
6412
  }
5502
6413
  } catch {
5503
6414
  }
5504
6415
  try {
5505
- const claudeJsonPath = path15.join(os8.homedir(), ".claude.json");
6416
+ const claudeJsonPath = path17.join(os10.homedir(), ".claude.json");
5506
6417
  let claudeJson = {};
5507
6418
  try {
5508
- claudeJson = JSON.parse(readFileSync12(claudeJsonPath, "utf8"));
6419
+ claudeJson = JSON.parse(readFileSync13(claudeJsonPath, "utf8"));
5509
6420
  } catch {
5510
6421
  }
5511
6422
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -5513,17 +6424,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5513
6424
  const trustDir = opts?.cwd ?? projectDir;
5514
6425
  if (!projects[trustDir]) projects[trustDir] = {};
5515
6426
  projects[trustDir].hasTrustDialogAccepted = true;
5516
- writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
6427
+ writeFileSync8(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
5517
6428
  } catch {
5518
6429
  }
5519
6430
  try {
5520
- const settingsDir = path15.join(os8.homedir(), ".claude", "projects");
6431
+ const settingsDir = path17.join(os10.homedir(), ".claude", "projects");
5521
6432
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
5522
- const projSettingsDir = path15.join(settingsDir, normalizedKey);
5523
- const settingsPath = path15.join(projSettingsDir, "settings.json");
6433
+ const projSettingsDir = path17.join(settingsDir, normalizedKey);
6434
+ const settingsPath = path17.join(projSettingsDir, "settings.json");
5524
6435
  let settings = {};
5525
6436
  try {
5526
- settings = JSON.parse(readFileSync12(settingsPath, "utf8"));
6437
+ settings = JSON.parse(readFileSync13(settingsPath, "utf8"));
5527
6438
  } catch {
5528
6439
  }
5529
6440
  const perms = settings.permissions ?? {};
@@ -5552,7 +6463,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5552
6463
  perms.allow = allow;
5553
6464
  settings.permissions = perms;
5554
6465
  mkdirSync6(projSettingsDir, { recursive: true });
5555
- writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + "\n");
6466
+ writeFileSync8(settingsPath, JSON.stringify(settings, null, 2) + "\n");
5556
6467
  }
5557
6468
  } catch {
5558
6469
  }
@@ -5567,8 +6478,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5567
6478
  let behaviorsFlag = "";
5568
6479
  let legacyFallbackWarned = false;
5569
6480
  if (!useExeAgent && !useBinSymlink) {
5570
- const identityPath = path15.join(
5571
- os8.homedir(),
6481
+ const identityPath = path17.join(
6482
+ os10.homedir(),
5572
6483
  ".exe-os",
5573
6484
  "identity",
5574
6485
  `${employeeName}.md`
@@ -5577,13 +6488,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5577
6488
  const hasAgentFlag = claudeSupportsAgentFlag();
5578
6489
  if (hasAgentFlag) {
5579
6490
  identityFlag = ` --agent ${employeeName}`;
5580
- } else if (existsSync13(identityPath)) {
6491
+ } else if (existsSync15(identityPath)) {
5581
6492
  identityFlag = ` --append-system-prompt-file ${identityPath}`;
5582
6493
  legacyFallbackWarned = true;
5583
6494
  }
5584
6495
  const behaviorsFile = exportBehaviorsSync(
5585
6496
  employeeName,
5586
- path15.basename(spawnCwd),
6497
+ path17.basename(spawnCwd),
5587
6498
  sessionName
5588
6499
  );
5589
6500
  if (behaviorsFile) {
@@ -5598,16 +6509,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5598
6509
  }
5599
6510
  let sessionContextFlag = "";
5600
6511
  try {
5601
- const ctxDir = path15.join(os8.homedir(), ".exe-os", "session-cache");
6512
+ const ctxDir = path17.join(os10.homedir(), ".exe-os", "session-cache");
5602
6513
  mkdirSync6(ctxDir, { recursive: true });
5603
- const ctxFile = path15.join(ctxDir, `session-context-${sessionName}.md`);
6514
+ const ctxFile = path17.join(ctxDir, `session-context-${sessionName}.md`);
5604
6515
  const ctxContent = [
5605
6516
  `## Session Context`,
5606
6517
  `You are running in tmux session: ${sessionName}.`,
5607
6518
  `Your parent coordinator session is ${exeSession}.`,
5608
6519
  `Your employees (if any) use the -${exeSession} suffix.`
5609
6520
  ].join("\n");
5610
- writeFileSync7(ctxFile, ctxContent);
6521
+ writeFileSync8(ctxFile, ctxContent);
5611
6522
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
5612
6523
  } catch {
5613
6524
  }
@@ -5684,8 +6595,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5684
6595
  transport.pipeLog(sessionName, logFile);
5685
6596
  try {
5686
6597
  const mySession = getMySession();
5687
- const dispatchInfo = path15.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
5688
- writeFileSync7(dispatchInfo, JSON.stringify({
6598
+ const dispatchInfo = path17.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
6599
+ writeFileSync8(dispatchInfo, JSON.stringify({
5689
6600
  dispatchedBy: mySession,
5690
6601
  rootExe: exeSession,
5691
6602
  provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
@@ -5759,15 +6670,15 @@ var init_tmux_routing = __esm({
5759
6670
  init_intercom_queue();
5760
6671
  init_plan_limits();
5761
6672
  init_employees();
5762
- SPAWN_LOCK_DIR = path15.join(os8.homedir(), ".exe-os", "spawn-locks");
5763
- SESSION_CACHE = path15.join(os8.homedir(), ".exe-os", "session-cache");
6673
+ SPAWN_LOCK_DIR = path17.join(os10.homedir(), ".exe-os", "spawn-locks");
6674
+ SESSION_CACHE = path17.join(os10.homedir(), ".exe-os", "session-cache");
5764
6675
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
5765
6676
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
5766
6677
  VERIFY_PANE_LINES = 200;
5767
6678
  INTERCOM_DEBOUNCE_MS = 3e4;
5768
6679
  CODEX_DEBOUNCE_MS = 12e4;
5769
- INTERCOM_LOG2 = path15.join(os8.homedir(), ".exe-os", "intercom.log");
5770
- DEBOUNCE_FILE = path15.join(SESSION_CACHE, "intercom-debounce.json");
6680
+ INTERCOM_LOG2 = path17.join(os10.homedir(), ".exe-os", "intercom.log");
6681
+ DEBOUNCE_FILE = path17.join(SESSION_CACHE, "intercom-debounce.json");
5771
6682
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
5772
6683
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…|• Working|• Ran |• Explored|• Called|esc to interrupt/;
5773
6684
  }
@@ -5777,7 +6688,8 @@ var init_tmux_routing = __esm({
5777
6688
  var task_scope_exports = {};
5778
6689
  __export(task_scope_exports, {
5779
6690
  getCurrentSessionScope: () => getCurrentSessionScope,
5780
- sessionScopeFilter: () => sessionScopeFilter
6691
+ sessionScopeFilter: () => sessionScopeFilter,
6692
+ strictSessionScopeFilter: () => strictSessionScopeFilter
5781
6693
  });
5782
6694
  function getCurrentSessionScope() {
5783
6695
  try {
@@ -5795,6 +6707,15 @@ function sessionScopeFilter(sessionScope, tableAlias) {
5795
6707
  args: [scope]
5796
6708
  };
5797
6709
  }
6710
+ function strictSessionScopeFilter(sessionScope, tableAlias) {
6711
+ const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
6712
+ if (!scope) return { sql: "", args: [] };
6713
+ const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
6714
+ return {
6715
+ sql: ` AND ${col} = ?`,
6716
+ args: [scope]
6717
+ };
6718
+ }
5798
6719
  var init_task_scope = __esm({
5799
6720
  "src/lib/task-scope.ts"() {
5800
6721
  "use strict";
@@ -7653,10 +8574,10 @@ var init_hooks = __esm({
7653
8574
  });
7654
8575
 
7655
8576
  // src/runtime/safety-checks.ts
7656
- import path16 from "path";
7657
- import os9 from "os";
8577
+ import path18 from "path";
8578
+ import os11 from "os";
7658
8579
  function checkPathSafety(filePath) {
7659
- const resolved = path16.resolve(filePath);
8580
+ const resolved = path18.resolve(filePath);
7660
8581
  for (const { pattern, reason } of BYPASS_IMMUNE_PATTERNS) {
7661
8582
  const matches = typeof pattern === "function" ? pattern(resolved) : pattern.test(resolved);
7662
8583
  if (matches) {
@@ -7666,7 +8587,7 @@ function checkPathSafety(filePath) {
7666
8587
  return { safe: true, bypassImmune: true };
7667
8588
  }
7668
8589
  function checkReadPathSafety(filePath) {
7669
- const resolved = path16.resolve(filePath);
8590
+ const resolved = path18.resolve(filePath);
7670
8591
  const credPatterns = BYPASS_IMMUNE_PATTERNS.filter(
7671
8592
  (p) => typeof p.pattern !== "function" && (p.reason.includes("secrets") || p.reason.includes("Private key") || p.reason.includes("Credential"))
7672
8593
  );
@@ -7681,7 +8602,7 @@ var HOME, BYPASS_IMMUNE_PATTERNS;
7681
8602
  var init_safety_checks = __esm({
7682
8603
  "src/runtime/safety-checks.ts"() {
7683
8604
  "use strict";
7684
- HOME = os9.homedir();
8605
+ HOME = os11.homedir();
7685
8606
  BYPASS_IMMUNE_PATTERNS = [
7686
8607
  {
7687
8608
  pattern: /\/\.git\/hooks\//,
@@ -7692,11 +8613,11 @@ var init_safety_checks = __esm({
7692
8613
  reason: "Git config can set hooks and command execution"
7693
8614
  },
7694
8615
  {
7695
- pattern: (p) => p.startsWith(path16.join(HOME, ".claude")),
8616
+ pattern: (p) => p.startsWith(path18.join(HOME, ".claude")),
7696
8617
  reason: "Claude configuration files are protected"
7697
8618
  },
7698
8619
  {
7699
- pattern: (p) => p.startsWith(path16.join(HOME, ".exe-os")),
8620
+ pattern: (p) => p.startsWith(path18.join(HOME, ".exe-os")),
7700
8621
  reason: "exe-os configuration files are protected"
7701
8622
  },
7702
8623
  {
@@ -7713,7 +8634,7 @@ var init_safety_checks = __esm({
7713
8634
  },
7714
8635
  {
7715
8636
  pattern: (p) => {
7716
- const name = path16.basename(p);
8637
+ const name = path18.basename(p);
7717
8638
  return [".bashrc", ".zshrc", ".profile", ".bash_profile", ".zprofile", ".zshenv"].includes(name);
7718
8639
  },
7719
8640
  reason: "Shell configuration files can execute arbitrary code on login"
@@ -7740,7 +8661,7 @@ __export(file_read_exports, {
7740
8661
  FileReadTool: () => FileReadTool
7741
8662
  });
7742
8663
  import fs3 from "fs/promises";
7743
- import path17 from "path";
8664
+ import path19 from "path";
7744
8665
  import { z } from "zod";
7745
8666
  function isBinary(buf) {
7746
8667
  for (let i = 0; i < buf.length; i++) {
@@ -7776,7 +8697,7 @@ var init_file_read = __esm({
7776
8697
  return { behavior: "allow" };
7777
8698
  },
7778
8699
  async call(input, context) {
7779
- const filePath = path17.isAbsolute(input.file_path) ? input.file_path : path17.resolve(context.cwd, input.file_path);
8700
+ const filePath = path19.isAbsolute(input.file_path) ? input.file_path : path19.resolve(context.cwd, input.file_path);
7780
8701
  let stat;
7781
8702
  try {
7782
8703
  stat = await fs3.stat(filePath);
@@ -7816,7 +8737,7 @@ __export(glob_exports, {
7816
8737
  GlobTool: () => GlobTool
7817
8738
  });
7818
8739
  import fs4 from "fs/promises";
7819
- import path18 from "path";
8740
+ import path20 from "path";
7820
8741
  import { z as z2 } from "zod";
7821
8742
  async function walkDir(dir, maxDepth = 10) {
7822
8743
  const results = [];
@@ -7832,7 +8753,7 @@ async function walkDir(dir, maxDepth = 10) {
7832
8753
  if (entry.isDirectory() && (entry.name === "node_modules" || entry.name === ".git")) {
7833
8754
  continue;
7834
8755
  }
7835
- const fullPath = path18.join(current, entry.name);
8756
+ const fullPath = path20.join(current, entry.name);
7836
8757
  if (entry.isDirectory()) {
7837
8758
  await walk(fullPath, depth + 1);
7838
8759
  } else {
@@ -7866,11 +8787,11 @@ var init_glob = __esm({
7866
8787
  inputSchema: inputSchema2,
7867
8788
  isReadOnly: true,
7868
8789
  async call(input, context) {
7869
- const baseDir = input.path ? path18.isAbsolute(input.path) ? input.path : path18.resolve(context.cwd, input.path) : context.cwd;
8790
+ const baseDir = input.path ? path20.isAbsolute(input.path) ? input.path : path20.resolve(context.cwd, input.path) : context.cwd;
7870
8791
  try {
7871
8792
  const entries = await walkDir(baseDir);
7872
8793
  const matched = entries.filter(
7873
- (e) => simpleGlobMatch(path18.relative(baseDir, e.path), input.pattern)
8794
+ (e) => simpleGlobMatch(path20.relative(baseDir, e.path), input.pattern)
7874
8795
  );
7875
8796
  matched.sort((a, b) => b.mtime - a.mtime);
7876
8797
  if (matched.length === 0) {
@@ -7896,7 +8817,7 @@ __export(grep_exports, {
7896
8817
  });
7897
8818
  import { spawn as spawn2 } from "child_process";
7898
8819
  import fs5 from "fs/promises";
7899
- import path19 from "path";
8820
+ import path21 from "path";
7900
8821
  import { z as z3 } from "zod";
7901
8822
  function runRipgrep(input, searchPath, context) {
7902
8823
  return new Promise((resolve, reject) => {
@@ -7950,7 +8871,7 @@ async function nodeGrep(input, searchPath) {
7950
8871
  }
7951
8872
  for (const entry of entries) {
7952
8873
  if (entry.name === "node_modules" || entry.name === ".git") continue;
7953
- const fullPath = path19.join(dir, entry.name);
8874
+ const fullPath = path21.join(dir, entry.name);
7954
8875
  if (entry.isDirectory()) {
7955
8876
  await walk(fullPath);
7956
8877
  } else {
@@ -7996,7 +8917,7 @@ var init_grep = __esm({
7996
8917
  inputSchema: inputSchema3,
7997
8918
  isReadOnly: true,
7998
8919
  async call(input, context) {
7999
- const searchPath = input.path ? path19.isAbsolute(input.path) ? input.path : path19.resolve(context.cwd, input.path) : context.cwd;
8920
+ const searchPath = input.path ? path21.isAbsolute(input.path) ? input.path : path21.resolve(context.cwd, input.path) : context.cwd;
8000
8921
  try {
8001
8922
  const result = await runRipgrep(input, searchPath, context);
8002
8923
  return result;
@@ -8021,7 +8942,7 @@ __export(file_write_exports, {
8021
8942
  FileWriteTool: () => FileWriteTool
8022
8943
  });
8023
8944
  import fs6 from "fs/promises";
8024
- import path20 from "path";
8945
+ import path22 from "path";
8025
8946
  import { z as z4 } from "zod";
8026
8947
  var inputSchema4, FileWriteTool;
8027
8948
  var init_file_write = __esm({
@@ -8049,8 +8970,8 @@ var init_file_write = __esm({
8049
8970
  return { behavior: "allow" };
8050
8971
  },
8051
8972
  async call(input, context) {
8052
- const filePath = path20.isAbsolute(input.file_path) ? input.file_path : path20.resolve(context.cwd, input.file_path);
8053
- const dir = path20.dirname(filePath);
8973
+ const filePath = path22.isAbsolute(input.file_path) ? input.file_path : path22.resolve(context.cwd, input.file_path);
8974
+ const dir = path22.dirname(filePath);
8054
8975
  await fs6.mkdir(dir, { recursive: true });
8055
8976
  await fs6.writeFile(filePath, input.content, "utf-8");
8056
8977
  return {
@@ -8068,7 +8989,7 @@ __export(file_edit_exports, {
8068
8989
  FileEditTool: () => FileEditTool
8069
8990
  });
8070
8991
  import fs7 from "fs/promises";
8071
- import path21 from "path";
8992
+ import path23 from "path";
8072
8993
  import { z as z5 } from "zod";
8073
8994
  function countOccurrences(haystack, needle) {
8074
8995
  let count = 0;
@@ -8109,7 +9030,7 @@ var init_file_edit = __esm({
8109
9030
  return { behavior: "allow" };
8110
9031
  },
8111
9032
  async call(input, context) {
8112
- const filePath = path21.isAbsolute(input.file_path) ? input.file_path : path21.resolve(context.cwd, input.file_path);
9033
+ const filePath = path23.isAbsolute(input.file_path) ? input.file_path : path23.resolve(context.cwd, input.file_path);
8113
9034
  let content;
8114
9035
  try {
8115
9036
  content = await fs7.readFile(filePath, "utf-8");
@@ -8716,6 +9637,133 @@ ${task.context}`,
8716
9637
  }
8717
9638
  });
8718
9639
 
9640
+ // src/lib/tui-data.ts
9641
+ var tui_data_exports = {};
9642
+ __export(tui_data_exports, {
9643
+ loadMemoryDashboard: () => loadMemoryDashboard,
9644
+ loadTaskList: () => loadTaskList,
9645
+ loadTeamMetrics: () => loadTeamMetrics,
9646
+ searchWikiMemoryRows: () => searchWikiMemoryRows
9647
+ });
9648
+ async function loadMemoryDashboard(limit) {
9649
+ const client = getClient();
9650
+ const [countResult, recentResult, agentResult] = await Promise.all([
9651
+ client.execute("SELECT COUNT(*) as cnt FROM memories"),
9652
+ client.execute({
9653
+ sql: "SELECT agent_id, tool_name, project_name, raw_text, timestamp FROM memories ORDER BY timestamp DESC LIMIT ?",
9654
+ args: [limit]
9655
+ }),
9656
+ client.execute("SELECT agent_id, COUNT(*) as cnt FROM memories GROUP BY agent_id ORDER BY cnt DESC")
9657
+ ]);
9658
+ return {
9659
+ total: Number(countResult.rows[0]?.cnt ?? 0),
9660
+ recent: recentResult.rows.map((row) => ({
9661
+ agentId: String(row.agent_id ?? "unknown"),
9662
+ toolName: String(row.tool_name ?? ""),
9663
+ projectName: String(row.project_name ?? ""),
9664
+ rawText: String(row.raw_text ?? ""),
9665
+ timestamp: String(row.timestamp ?? "")
9666
+ })),
9667
+ byAgent: agentResult.rows.map((row) => ({
9668
+ agentId: String(row.agent_id ?? "unknown"),
9669
+ count: Number(row.cnt ?? 0)
9670
+ }))
9671
+ };
9672
+ }
9673
+ async function loadTeamMetrics(employeeNames) {
9674
+ const client = getClient();
9675
+ const memoryCounts = /* @__PURE__ */ new Map();
9676
+ const projectsByEmployee = /* @__PURE__ */ new Map();
9677
+ const currentTaskByEmployee = /* @__PURE__ */ new Map();
9678
+ const scope = sessionScopeFilter();
9679
+ const memResult = await client.execute("SELECT agent_id, COUNT(*) as cnt FROM memories GROUP BY agent_id");
9680
+ for (const row of memResult.rows) {
9681
+ memoryCounts.set(String(row.agent_id), Number(row.cnt));
9682
+ }
9683
+ for (const employeeName of employeeNames) {
9684
+ const [projectResult, taskResult] = await Promise.all([
9685
+ client.execute({
9686
+ sql: `SELECT DISTINCT project_name,
9687
+ MAX(CASE WHEN status = 'in_progress' THEN 1 WHEN status = 'open' THEN 2 ELSE 3 END) as urgency
9688
+ FROM tasks
9689
+ WHERE assigned_to = ? AND status IN ('open','in_progress','done')${scope.sql}
9690
+ GROUP BY project_name
9691
+ ORDER BY urgency ASC
9692
+ LIMIT 5`,
9693
+ args: [employeeName, ...scope.args]
9694
+ }),
9695
+ client.execute({
9696
+ sql: `SELECT title FROM tasks
9697
+ WHERE assigned_to = ? AND status = 'in_progress'${scope.sql}
9698
+ ORDER BY updated_at DESC
9699
+ LIMIT 1`,
9700
+ args: [employeeName, ...scope.args]
9701
+ })
9702
+ ]);
9703
+ const projects = projectResult.rows.map((row) => {
9704
+ const urgency = Number(row.urgency);
9705
+ return {
9706
+ name: String(row.project_name),
9707
+ status: urgency === 1 ? "active" : urgency === 2 ? "has_tasks" : "idle"
9708
+ };
9709
+ });
9710
+ if (projects.length > 0) projectsByEmployee.set(employeeName, projects);
9711
+ if (taskResult.rows.length > 0) {
9712
+ currentTaskByEmployee.set(employeeName, String(taskResult.rows[0].title));
9713
+ }
9714
+ }
9715
+ return { memoryCounts, projectsByEmployee, currentTaskByEmployee };
9716
+ }
9717
+ async function loadTaskList() {
9718
+ const client = getClient();
9719
+ const scope = sessionScopeFilter();
9720
+ const result = await client.execute({
9721
+ sql: `SELECT id, title, priority, assigned_to, assigned_by, status, project_name, created_at, result
9722
+ FROM tasks
9723
+ WHERE 1=1${scope.sql}
9724
+ ORDER BY
9725
+ CASE status WHEN 'in_progress' THEN 0 WHEN 'open' THEN 1 WHEN 'blocked' THEN 2 WHEN 'needs_review' THEN 3 WHEN 'done' THEN 4 ELSE 5 END,
9726
+ CASE priority WHEN 'p0' THEN 0 WHEN 'p1' THEN 1 WHEN 'p2' THEN 2 ELSE 3 END,
9727
+ created_at DESC`,
9728
+ args: [...scope.args]
9729
+ });
9730
+ return result.rows.map((row) => ({
9731
+ id: String(row.id ?? ""),
9732
+ title: String(row.title ?? ""),
9733
+ priority: String(row.priority ?? "p2").toUpperCase(),
9734
+ assignedTo: String(row.assigned_to ?? ""),
9735
+ assignedBy: String(row.assigned_by ?? ""),
9736
+ status: String(row.status ?? "open"),
9737
+ projectName: String(row.project_name ?? ""),
9738
+ createdAt: String(row.created_at ?? ""),
9739
+ result: String(row.result ?? "")
9740
+ }));
9741
+ }
9742
+ async function searchWikiMemoryRows(query) {
9743
+ const client = getClient();
9744
+ const result = await client.execute({
9745
+ sql: `SELECT id, agent_id, raw_text, timestamp, project_name
9746
+ FROM memories
9747
+ WHERE raw_text LIKE ? AND COALESCE(status, 'active') = 'active'
9748
+ ORDER BY timestamp DESC LIMIT 20`,
9749
+ args: [`%${query}%`]
9750
+ });
9751
+ return result.rows.map((row) => ({
9752
+ id: String(row.id),
9753
+ agentId: String(row.agent_id),
9754
+ rawText: String(row.raw_text),
9755
+ timestamp: String(row.timestamp),
9756
+ projectName: String(row.project_name ?? "")
9757
+ }));
9758
+ }
9759
+ var init_tui_data = __esm({
9760
+ "src/lib/tui-data.ts"() {
9761
+ "use strict";
9762
+ init_database();
9763
+ init_task_scope();
9764
+ }
9765
+ });
9766
+
8719
9767
  // src/lib/message-queue.ts
8720
9768
  var DEFAULT_MAX_SIZE, DEFAULT_TTL_MS, MessageQueue;
8721
9769
  var init_message_queue = __esm({
@@ -8774,14 +9822,14 @@ __export(keychain_exports, {
8774
9822
  setMasterKey: () => setMasterKey
8775
9823
  });
8776
9824
  import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
8777
- import { existsSync as existsSync14 } from "fs";
8778
- import path24 from "path";
8779
- import os10 from "os";
9825
+ import { existsSync as existsSync16 } from "fs";
9826
+ import path26 from "path";
9827
+ import os12 from "os";
8780
9828
  function getKeyDir() {
8781
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path24.join(os10.homedir(), ".exe-os");
9829
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path26.join(os12.homedir(), ".exe-os");
8782
9830
  }
8783
9831
  function getKeyPath() {
8784
- return path24.join(getKeyDir(), "master.key");
9832
+ return path26.join(getKeyDir(), "master.key");
8785
9833
  }
8786
9834
  async function tryKeytar() {
8787
9835
  try {
@@ -8802,9 +9850,9 @@ async function getMasterKey() {
8802
9850
  }
8803
9851
  }
8804
9852
  const keyPath = getKeyPath();
8805
- if (!existsSync14(keyPath)) {
9853
+ if (!existsSync16(keyPath)) {
8806
9854
  process.stderr.write(
8807
- `[keychain] Key not found at ${keyPath} (HOME=${os10.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
9855
+ `[keychain] Key not found at ${keyPath} (HOME=${os12.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
8808
9856
  `
8809
9857
  );
8810
9858
  return null;
@@ -8845,7 +9893,7 @@ async function deleteMasterKey() {
8845
9893
  }
8846
9894
  }
8847
9895
  const keyPath = getKeyPath();
8848
- if (existsSync14(keyPath)) {
9896
+ if (existsSync16(keyPath)) {
8849
9897
  await unlink(keyPath);
8850
9898
  }
8851
9899
  }
@@ -8894,16 +9942,16 @@ __export(ws_auth_exports, {
8894
9942
  deriveWsAuthToken: () => deriveWsAuthToken,
8895
9943
  hashAuthToken: () => hashAuthToken
8896
9944
  });
8897
- import crypto6 from "crypto";
9945
+ import crypto7 from "crypto";
8898
9946
  function deriveWsAuthToken(masterKey) {
8899
- return Buffer.from(crypto6.hkdfSync("sha256", masterKey, "", WS_AUTH_HKDF_INFO, 32));
9947
+ return Buffer.from(crypto7.hkdfSync("sha256", masterKey, "", WS_AUTH_HKDF_INFO, 32));
8900
9948
  }
8901
9949
  function deriveOrgId(masterKey) {
8902
- const raw = Buffer.from(crypto6.hkdfSync("sha256", masterKey, "", ORG_ID_HKDF_INFO, 32));
8903
- return crypto6.createHash("sha256").update(raw).digest("hex").slice(0, 32);
9950
+ const raw = Buffer.from(crypto7.hkdfSync("sha256", masterKey, "", ORG_ID_HKDF_INFO, 32));
9951
+ return crypto7.createHash("sha256").update(raw).digest("hex").slice(0, 32);
8904
9952
  }
8905
9953
  function hashAuthToken(token) {
8906
- return crypto6.createHash("sha256").update(token).digest("hex");
9954
+ return crypto7.createHash("sha256").update(token).digest("hex");
8907
9955
  }
8908
9956
  var WS_AUTH_HKDF_INFO, ORG_ID_HKDF_INFO;
8909
9957
  var init_ws_auth = __esm({
@@ -9145,8 +10193,8 @@ __export(wiki_client_exports, {
9145
10193
  listDocuments: () => listDocuments,
9146
10194
  listWorkspaces: () => listWorkspaces
9147
10195
  });
9148
- async function wikiFetch(config, path26, method = "GET", body) {
9149
- const url = `${config.baseUrl}/api/v1${path26}`;
10196
+ async function wikiFetch(config, path28, method = "GET", body) {
10197
+ const url = `${config.baseUrl}/api/v1${path28}`;
9150
10198
  const headers = {
9151
10199
  Authorization: `Bearer ${config.apiKey}`,
9152
10200
  "Content-Type": "application/json"
@@ -9179,7 +10227,7 @@ async function wikiFetch(config, path26, method = "GET", body) {
9179
10227
  }
9180
10228
  }
9181
10229
  if (!response.ok) {
9182
- throw new Error(`Wiki API ${method} ${path26}: ${response.status} ${response.statusText}`);
10230
+ throw new Error(`Wiki API ${method} ${path28}: ${response.status} ${response.statusText}`);
9183
10231
  }
9184
10232
  return response.json();
9185
10233
  } finally {
@@ -9292,6 +10340,7 @@ var shard_manager_exports = {};
9292
10340
  __export(shard_manager_exports, {
9293
10341
  disposeShards: () => disposeShards,
9294
10342
  ensureShardSchema: () => ensureShardSchema,
10343
+ getOpenShardCount: () => getOpenShardCount,
9295
10344
  getReadyShardClient: () => getReadyShardClient,
9296
10345
  getShardClient: () => getShardClient,
9297
10346
  getShardsDir: () => getShardsDir,
@@ -9300,15 +10349,18 @@ __export(shard_manager_exports, {
9300
10349
  listShards: () => listShards,
9301
10350
  shardExists: () => shardExists
9302
10351
  });
9303
- import path25 from "path";
9304
- import { existsSync as existsSync15, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
10352
+ import path27 from "path";
10353
+ import { existsSync as existsSync17, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
9305
10354
  import { createClient as createClient2 } from "@libsql/client";
9306
10355
  function initShardManager(encryptionKey) {
9307
10356
  _encryptionKey = encryptionKey;
9308
- if (!existsSync15(SHARDS_DIR)) {
10357
+ if (!existsSync17(SHARDS_DIR)) {
9309
10358
  mkdirSync7(SHARDS_DIR, { recursive: true });
9310
10359
  }
9311
10360
  _shardingEnabled = true;
10361
+ if (_evictionTimer) clearInterval(_evictionTimer);
10362
+ _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
10363
+ _evictionTimer.unref();
9312
10364
  }
9313
10365
  function isShardingEnabled() {
9314
10366
  return _shardingEnabled;
@@ -9325,21 +10377,28 @@ function getShardClient(projectName) {
9325
10377
  throw new Error(`Invalid project name for shard: "${projectName}"`);
9326
10378
  }
9327
10379
  const cached = _shards.get(safeName);
9328
- if (cached) return cached;
9329
- const dbPath = path25.join(SHARDS_DIR, `${safeName}.db`);
10380
+ if (cached) {
10381
+ _shardLastAccess.set(safeName, Date.now());
10382
+ return cached;
10383
+ }
10384
+ while (_shards.size >= MAX_OPEN_SHARDS) {
10385
+ evictLRU();
10386
+ }
10387
+ const dbPath = path27.join(SHARDS_DIR, `${safeName}.db`);
9330
10388
  const client = createClient2({
9331
10389
  url: `file:${dbPath}`,
9332
10390
  encryptionKey: _encryptionKey
9333
10391
  });
9334
10392
  _shards.set(safeName, client);
10393
+ _shardLastAccess.set(safeName, Date.now());
9335
10394
  return client;
9336
10395
  }
9337
10396
  function shardExists(projectName) {
9338
10397
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
9339
- return existsSync15(path25.join(SHARDS_DIR, `${safeName}.db`));
10398
+ return existsSync17(path27.join(SHARDS_DIR, `${safeName}.db`));
9340
10399
  }
9341
10400
  function listShards() {
9342
- if (!existsSync15(SHARDS_DIR)) return [];
10401
+ if (!existsSync17(SHARDS_DIR)) return [];
9343
10402
  return readdirSync4(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
9344
10403
  }
9345
10404
  async function ensureShardSchema(client) {
@@ -9391,6 +10450,8 @@ async function ensureShardSchema(client) {
9391
10450
  for (const col of [
9392
10451
  "ALTER TABLE memories ADD COLUMN task_id TEXT",
9393
10452
  "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
10453
+ "ALTER TABLE memories ADD COLUMN author_device_id TEXT",
10454
+ "ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
9394
10455
  "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
9395
10456
  "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
9396
10457
  "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
@@ -9413,7 +10474,23 @@ async function ensureShardSchema(client) {
9413
10474
  // MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
9414
10475
  "ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
9415
10476
  "ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
9416
- "ALTER TABLE memories ADD COLUMN trajectory TEXT"
10477
+ "ALTER TABLE memories ADD COLUMN trajectory TEXT",
10478
+ // Metadata enrichment columns (must match database.ts)
10479
+ "ALTER TABLE memories ADD COLUMN intent TEXT",
10480
+ "ALTER TABLE memories ADD COLUMN outcome TEXT",
10481
+ "ALTER TABLE memories ADD COLUMN domain TEXT",
10482
+ "ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
10483
+ "ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
10484
+ "ALTER TABLE memories ADD COLUMN chain_position TEXT",
10485
+ "ALTER TABLE memories ADD COLUMN review_status TEXT",
10486
+ "ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
10487
+ "ALTER TABLE memories ADD COLUMN file_paths TEXT",
10488
+ "ALTER TABLE memories ADD COLUMN commit_hash TEXT",
10489
+ "ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
10490
+ "ALTER TABLE memories ADD COLUMN token_cost REAL",
10491
+ "ALTER TABLE memories ADD COLUMN audience TEXT",
10492
+ "ALTER TABLE memories ADD COLUMN language_type TEXT",
10493
+ "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
9417
10494
  ]) {
9418
10495
  try {
9419
10496
  await client.execute(col);
@@ -9512,21 +10589,69 @@ async function getReadyShardClient(projectName) {
9512
10589
  await ensureShardSchema(client);
9513
10590
  return client;
9514
10591
  }
10592
+ function evictLRU() {
10593
+ let oldest = null;
10594
+ let oldestTime = Infinity;
10595
+ for (const [name, time] of _shardLastAccess) {
10596
+ if (time < oldestTime) {
10597
+ oldestTime = time;
10598
+ oldest = name;
10599
+ }
10600
+ }
10601
+ if (oldest) {
10602
+ const client = _shards.get(oldest);
10603
+ if (client) {
10604
+ client.close();
10605
+ }
10606
+ _shards.delete(oldest);
10607
+ _shardLastAccess.delete(oldest);
10608
+ }
10609
+ }
10610
+ function evictIdleShards() {
10611
+ const now = Date.now();
10612
+ const toEvict = [];
10613
+ for (const [name, lastAccess] of _shardLastAccess) {
10614
+ if (now - lastAccess > SHARD_IDLE_MS) {
10615
+ toEvict.push(name);
10616
+ }
10617
+ }
10618
+ for (const name of toEvict) {
10619
+ const client = _shards.get(name);
10620
+ if (client) {
10621
+ client.close();
10622
+ }
10623
+ _shards.delete(name);
10624
+ _shardLastAccess.delete(name);
10625
+ }
10626
+ }
10627
+ function getOpenShardCount() {
10628
+ return _shards.size;
10629
+ }
9515
10630
  function disposeShards() {
10631
+ if (_evictionTimer) {
10632
+ clearInterval(_evictionTimer);
10633
+ _evictionTimer = null;
10634
+ }
9516
10635
  for (const [, client] of _shards) {
9517
10636
  client.close();
9518
10637
  }
9519
10638
  _shards.clear();
10639
+ _shardLastAccess.clear();
9520
10640
  _shardingEnabled = false;
9521
10641
  _encryptionKey = null;
9522
10642
  }
9523
- var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
10643
+ var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
9524
10644
  var init_shard_manager = __esm({
9525
10645
  "src/lib/shard-manager.ts"() {
9526
10646
  "use strict";
9527
10647
  init_config();
9528
- SHARDS_DIR = path25.join(EXE_AI_DIR, "shards");
10648
+ SHARDS_DIR = path27.join(EXE_AI_DIR, "shards");
10649
+ SHARD_IDLE_MS = 5 * 60 * 1e3;
10650
+ MAX_OPEN_SHARDS = 10;
10651
+ EVICTION_INTERVAL_MS = 60 * 1e3;
9529
10652
  _shards = /* @__PURE__ */ new Map();
10653
+ _shardLastAccess = /* @__PURE__ */ new Map();
10654
+ _evictionTimer = null;
9530
10655
  _encryptionKey = null;
9531
10656
  _shardingEnabled = false;
9532
10657
  }
@@ -14097,8 +15222,8 @@ function Text({ color, backgroundColor, dimColor = false, bold = false, italic =
14097
15222
  }
14098
15223
 
14099
15224
  // src/tui/ink/components/ErrorOverview.js
14100
- var cleanupPath = (path26) => {
14101
- return path26?.replace(`file://${cwd()}/`, "");
15225
+ var cleanupPath = (path28) => {
15226
+ return path28?.replace(`file://${cwd()}/`, "");
14102
15227
  };
14103
15228
  var stackUtils = new StackUtils({
14104
15229
  cwd: cwd(),
@@ -16030,11 +17155,11 @@ function Footer() {
16030
17155
  } catch {
16031
17156
  }
16032
17157
  try {
16033
- const { existsSync: existsSync16 } = await import("fs");
17158
+ const { existsSync: existsSync18 } = await import("fs");
16034
17159
  const { join } = await import("path");
16035
17160
  const home = process.env.HOME ?? "";
16036
17161
  const pidPath = join(home, ".exe-os", "exed.pid");
16037
- setDaemon(existsSync16(pidPath) ? "running" : "stopped");
17162
+ setDaemon(existsSync18(pidPath) ? "running" : "stopped");
16038
17163
  } catch {
16039
17164
  setDaemon("unknown");
16040
17165
  }
@@ -16116,7 +17241,7 @@ function Footer() {
16116
17241
  // src/tui/views/CommandCenter.tsx
16117
17242
  import { useState as useState6, useEffect as useEffect8, useMemo as useMemo4, useCallback as useCallback4, useRef as useRef4 } from "react";
16118
17243
  import TextInput from "ink-text-input";
16119
- import path22 from "path";
17244
+ import path24 from "path";
16120
17245
  import { homedir } from "os";
16121
17246
 
16122
17247
  // src/tui/components/StatusDot.tsx
@@ -16903,15 +18028,15 @@ function CommandCenterView({
16903
18028
  const { createPermissionsFromPreset: createPermissionsFromPreset2, EMPLOYEE_PERMISSIONS: EMPLOYEE_PERMISSIONS2 } = await Promise.resolve().then(() => (init_permissions(), permissions_exports));
16904
18029
  const { getPresetByRole: getPresetByRole2 } = await Promise.resolve().then(() => (init_permission_presets(), permission_presets_exports));
16905
18030
  const { createDefaultHooks: createDefaultHooks2 } = await Promise.resolve().then(() => (init_hooks(), hooks_exports));
16906
- const { readFileSync: readFileSync13, existsSync: existsSync16 } = await import("fs");
18031
+ const { readFileSync: readFileSync14, existsSync: existsSync18 } = await import("fs");
16907
18032
  const { join } = await import("path");
16908
18033
  const { homedir: homedir3 } = await import("os");
16909
18034
  const configPath = join(homedir3(), ".exe-os", "config.json");
16910
18035
  let failoverChain = ["anthropic", "opencode", "gemini", "openai"];
16911
18036
  let providerConfigs = {};
16912
- if (existsSync16(configPath)) {
18037
+ if (existsSync18(configPath)) {
16913
18038
  try {
16914
- const raw = JSON.parse(readFileSync13(configPath, "utf8"));
18039
+ const raw = JSON.parse(readFileSync14(configPath, "utf8"));
16915
18040
  if (Array.isArray(raw.failoverChain)) failoverChain = raw.failoverChain;
16916
18041
  if (raw.providers && typeof raw.providers === "object") {
16917
18042
  providerConfigs = raw.providers;
@@ -16972,7 +18097,7 @@ function CommandCenterView({
16972
18097
  const markerDir = join(homedir3(), ".exe-os", "session-cache");
16973
18098
  const agentFiles = (await import("fs")).readdirSync(markerDir).filter((f) => f.startsWith("active-agent-"));
16974
18099
  for (const f of agentFiles) {
16975
- const data = JSON.parse(readFileSync13(join(markerDir, f), "utf8"));
18100
+ const data = JSON.parse(readFileSync14(join(markerDir, f), "utf8"));
16976
18101
  if (data.agentRole) {
16977
18102
  agentRole = data.agentRole;
16978
18103
  break;
@@ -17117,7 +18242,7 @@ function CommandCenterView({
17117
18242
  const demoEntries = DEMO_PROJECTS.map((p) => ({
17118
18243
  projectName: p.projectName,
17119
18244
  exeSession: p.exeSession,
17120
- projectDir: path22.join(homedir(), p.projectName),
18245
+ projectDir: path24.join(homedir(), p.projectName),
17121
18246
  employeeCount: p.employees.length,
17122
18247
  activeCount: p.employees.filter((e) => e.status === "active").length,
17123
18248
  memoryCount: p.employees.length * 4e3,
@@ -17155,7 +18280,7 @@ function CommandCenterView({
17155
18280
  const { listSessions: listSessions2 } = await Promise.resolve().then(() => (init_session_registry(), session_registry_exports));
17156
18281
  const { listTmuxSessions: listTmuxSessions2, inTmux: inTmux2 } = await Promise.resolve().then(() => (init_tmux_status(), tmux_status_exports));
17157
18282
  const { loadEmployees: loadEmployees2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
17158
- const { existsSync: existsSync16 } = await import("fs");
18283
+ const { existsSync: existsSync18 } = await import("fs");
17159
18284
  const { join } = await import("path");
17160
18285
  const client = getClient2();
17161
18286
  if (!client) {
@@ -17226,7 +18351,7 @@ function CommandCenterView({
17226
18351
  }
17227
18352
  const memoryCount = memoryCounts.get(name) ?? 0;
17228
18353
  const openTaskCount = openTaskCounts.get(name) ?? 0;
17229
- const hasGit = projectDir ? existsSync16(join(projectDir, ".git")) : false;
18354
+ const hasGit = projectDir ? existsSync18(join(projectDir, ".git")) : false;
17230
18355
  const type = hasGit ? "code" : memoryCount > 0 ? "code" : "automation";
17231
18356
  projectList.push({
17232
18357
  projectName: name,
@@ -17251,7 +18376,7 @@ function CommandCenterView({
17251
18376
  setHealth((h) => ({ ...h, memories: Number(totalResult.rows[0]?.cnt ?? 0) }));
17252
18377
  try {
17253
18378
  const pidPath = join(process.env.HOME ?? "", ".exe-os", "exed.pid");
17254
- setHealth((h) => ({ ...h, daemon: existsSync16(pidPath) ? "running" : "stopped" }));
18379
+ setHealth((h) => ({ ...h, daemon: existsSync18(pidPath) ? "running" : "stopped" }));
17255
18380
  } catch {
17256
18381
  }
17257
18382
  const activityResult = await client.execute(
@@ -17466,7 +18591,7 @@ function ChatMessageRow({ msg }) {
17466
18591
 
17467
18592
  // src/tui/views/Sessions.tsx
17468
18593
  import React19, { useState as useState9, useEffect as useEffect11, useCallback as useCallback6 } from "react";
17469
- import path23 from "path";
18594
+ import path25 from "path";
17470
18595
  import { homedir as homedir2 } from "os";
17471
18596
 
17472
18597
  // src/tui/components/TmuxPane.tsx
@@ -17770,7 +18895,7 @@ function SessionsView({
17770
18895
  if (demo) {
17771
18896
  setProjects(DEMO_PROJECTS.map((p) => ({
17772
18897
  ...p,
17773
- projectDir: path23.join(homedir2(), p.projectName),
18898
+ projectDir: path25.join(homedir2(), p.projectName),
17774
18899
  employees: p.employees.map((e) => ({ ...e, attached: e.status === "active" }))
17775
18900
  })));
17776
18901
  return;
@@ -18166,7 +19291,6 @@ function SessionsView({
18166
19291
 
18167
19292
  // src/tui/views/Tasks.tsx
18168
19293
  import React20, { useState as useState10, useEffect as useEffect12, useMemo as useMemo5 } from "react";
18169
- init_task_scope();
18170
19294
  import { Fragment as Fragment3, jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
18171
19295
  var STATUS_FILTERS = ["all", "open", "in_progress", "done", "blocked", "needs_review"];
18172
19296
  var PRIORITY_COLORS = {
@@ -18299,37 +19423,22 @@ function TasksView({ onBack }) {
18299
19423
  const { withTrace: withTrace2 } = await Promise.resolve().then(() => (init_telemetry(), telemetry_exports));
18300
19424
  return withTrace2("tui.tasks.loadTasks", async () => {
18301
19425
  try {
18302
- const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
18303
- const client = getClient2();
18304
- if (client) {
18305
- const tScope = sessionScopeFilter();
18306
- const result = await client.execute({
18307
- sql: `SELECT id, title, priority, assigned_to, assigned_by, status, project_name, created_at, result
18308
- FROM tasks
18309
- WHERE 1=1${tScope.sql}
18310
- ORDER BY
18311
- CASE status WHEN 'in_progress' THEN 0 WHEN 'open' THEN 1 WHEN 'blocked' THEN 2 WHEN 'needs_review' THEN 3 WHEN 'done' THEN 4 ELSE 5 END,
18312
- CASE priority WHEN 'p0' THEN 0 WHEN 'p1' THEN 1 WHEN 'p2' THEN 2 ELSE 3 END,
18313
- created_at DESC`,
18314
- args: [...tScope.args]
18315
- });
18316
- setAllTasks(
18317
- result.rows.map((r) => ({
18318
- id: String(r.id ?? ""),
18319
- priority: String(r.priority ?? "p2").toUpperCase(),
18320
- title: String(r.title ?? ""),
18321
- assignee: String(r.assigned_to ?? ""),
18322
- assignedBy: String(r.assigned_by ?? ""),
18323
- status: String(r.status ?? "open"),
18324
- project: String(r.project_name ?? ""),
18325
- createdAt: String(r.created_at ?? ""),
18326
- result: String(r.result ?? "")
18327
- }))
18328
- );
18329
- setDbError(null);
18330
- } else {
18331
- setDbError("Database client not initialized. Run exe-os setup.");
18332
- }
19426
+ const { loadTaskList: loadTaskList2 } = await Promise.resolve().then(() => (init_tui_data(), tui_data_exports));
19427
+ const tasks = await loadTaskList2();
19428
+ setAllTasks(
19429
+ tasks.map((task) => ({
19430
+ id: task.id,
19431
+ priority: task.priority,
19432
+ title: task.title,
19433
+ assignee: task.assignedTo,
19434
+ assignedBy: task.assignedBy,
19435
+ status: task.status,
19436
+ project: task.projectName,
19437
+ createdAt: task.createdAt,
19438
+ result: task.result
19439
+ }))
19440
+ );
19441
+ setDbError(null);
18333
19442
  } catch (err) {
18334
19443
  setDbError(err instanceof Error ? err.message : "Unknown error");
18335
19444
  } finally {
@@ -18667,12 +19776,12 @@ async function loadGatewayConfig() {
18667
19776
  state.running = false;
18668
19777
  }
18669
19778
  try {
18670
- const { existsSync: existsSync16, readFileSync: readFileSync13 } = await import("fs");
19779
+ const { existsSync: existsSync18, readFileSync: readFileSync14 } = await import("fs");
18671
19780
  const { join } = await import("path");
18672
19781
  const home = process.env.HOME ?? "";
18673
19782
  const configPath = join(home, ".exe-os", "gateway.json");
18674
- if (existsSync16(configPath)) {
18675
- const raw = JSON.parse(readFileSync13(configPath, "utf8"));
19783
+ if (existsSync18(configPath)) {
19784
+ const raw = JSON.parse(readFileSync14(configPath, "utf8"));
18676
19785
  state.port = raw.port ?? 3100;
18677
19786
  state.gatewayUrl = raw.gatewayUrl ?? "";
18678
19787
  if (raw.adapters) {
@@ -19072,7 +20181,6 @@ function getAgentStatus(agentId) {
19072
20181
  }
19073
20182
 
19074
20183
  // src/tui/views/Team.tsx
19075
- init_task_scope();
19076
20184
  import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
19077
20185
  var DEPRECATED_PROJECTS = /* @__PURE__ */ new Set(["exe-ai-employees"]);
19078
20186
  function roleBadgeColor(role) {
@@ -19145,41 +20253,16 @@ function TeamView({ onBack, onViewSessions }) {
19145
20253
  let projectsByEmployee = /* @__PURE__ */ new Map();
19146
20254
  let currentTaskByEmployee = /* @__PURE__ */ new Map();
19147
20255
  try {
19148
- const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
19149
- const client = getClient2();
19150
- if (client) {
19151
- const memResult = await client.execute("SELECT agent_id, COUNT(*) as cnt FROM memories GROUP BY agent_id");
19152
- for (const row of memResult.rows) {
19153
- memoryCounts.set(String(row.agent_id), Number(row.cnt));
19154
- }
19155
- const tmScope = sessionScopeFilter();
19156
- for (const emp of roster) {
19157
- const projResult = await client.execute({
19158
- sql: `SELECT DISTINCT project_name,
19159
- MAX(CASE WHEN status = 'in_progress' THEN 1 WHEN status = 'open' THEN 2 ELSE 3 END) as urgency
19160
- FROM tasks WHERE assigned_to = ? AND status IN ('open','in_progress','done')${tmScope.sql}
19161
- GROUP BY project_name ORDER BY urgency ASC LIMIT 5`,
19162
- args: [emp.name, ...tmScope.args]
19163
- });
19164
- const projects = projResult.rows.filter((r) => !DEPRECATED_PROJECTS.has(String(r.project_name))).map((r) => {
19165
- const urgency = Number(r.urgency);
19166
- let pStatus = "idle";
19167
- if (urgency === 1) pStatus = "active";
19168
- else if (urgency === 2) pStatus = "has_tasks";
19169
- return { name: String(r.project_name), status: pStatus };
19170
- });
19171
- if (projects.length > 0) projectsByEmployee.set(emp.name, projects);
19172
- }
19173
- for (const emp of roster) {
19174
- const taskResult = await client.execute({
19175
- sql: `SELECT title FROM tasks WHERE assigned_to = ? AND status = 'in_progress'${tmScope.sql} ORDER BY updated_at DESC LIMIT 1`,
19176
- args: [emp.name, ...tmScope.args]
19177
- });
19178
- if (taskResult.rows.length > 0) {
19179
- currentTaskByEmployee.set(emp.name, String(taskResult.rows[0].title));
19180
- }
19181
- }
19182
- }
20256
+ const { loadTeamMetrics: loadTeamMetrics2 } = await Promise.resolve().then(() => (init_tui_data(), tui_data_exports));
20257
+ const teamMetrics = await loadTeamMetrics2(roster.map((emp) => emp.name));
20258
+ memoryCounts = teamMetrics.memoryCounts;
20259
+ projectsByEmployee = new Map(
20260
+ Array.from(teamMetrics.projectsByEmployee.entries()).map(([name, projects]) => [
20261
+ name,
20262
+ projects.filter((project) => !DEPRECATED_PROJECTS.has(project.name))
20263
+ ])
20264
+ );
20265
+ currentTaskByEmployee = teamMetrics.currentTaskByEmployee;
19183
20266
  } catch {
19184
20267
  }
19185
20268
  const teamData = roster.map((emp) => {
@@ -19198,12 +20281,12 @@ function TeamView({ onBack, onViewSessions }) {
19198
20281
  setMembers(teamData);
19199
20282
  setDbError(null);
19200
20283
  try {
19201
- const { existsSync: existsSync16, readFileSync: readFileSync13 } = await import("fs");
20284
+ const { existsSync: existsSync18, readFileSync: readFileSync14 } = await import("fs");
19202
20285
  const { join } = await import("path");
19203
20286
  const home = process.env.HOME ?? "";
19204
20287
  const gatewayConfig = join(home, ".exe-os", "gateway.json");
19205
- if (existsSync16(gatewayConfig)) {
19206
- const raw = JSON.parse(readFileSync13(gatewayConfig, "utf8"));
20288
+ if (existsSync18(gatewayConfig)) {
20289
+ const raw = JSON.parse(readFileSync14(gatewayConfig, "utf8"));
19207
20290
  if (raw.agents && raw.agents.length > 0) {
19208
20291
  setExternals(raw.agents.map((a) => ({
19209
20292
  name: a.name,
@@ -19517,24 +20600,8 @@ function WikiView({ onBack }) {
19517
20600
  }
19518
20601
  setSearchLoading(true);
19519
20602
  try {
19520
- const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
19521
- const client = getClient2();
19522
- const result = await client.execute({
19523
- sql: `SELECT id, agent_id, raw_text, timestamp, project_name
19524
- FROM memories
19525
- WHERE raw_text LIKE ? AND COALESCE(status, 'active') = 'active'
19526
- ORDER BY timestamp DESC LIMIT 20`,
19527
- args: [`%${query}%`]
19528
- });
19529
- setSearchResults(
19530
- result.rows.map((r) => ({
19531
- id: String(r.id),
19532
- agentId: String(r.agent_id),
19533
- rawText: String(r.raw_text),
19534
- timestamp: String(r.timestamp),
19535
- projectName: String(r.project_name ?? "")
19536
- }))
19537
- );
20603
+ const { searchWikiMemoryRows: searchWikiMemoryRows2 } = await Promise.resolve().then(() => (init_tui_data(), tui_data_exports));
20604
+ setSearchResults(await searchWikiMemoryRows2(query));
19538
20605
  } catch {
19539
20606
  setSearchResults([]);
19540
20607
  } finally {
@@ -19867,12 +20934,12 @@ function SettingsView({ onBack }) {
19867
20934
  }
19868
20935
  setProviders(providerList);
19869
20936
  try {
19870
- const { existsSync: existsSync16 } = await import("fs");
20937
+ const { existsSync: existsSync18 } = await import("fs");
19871
20938
  const { join } = await import("path");
19872
20939
  const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
19873
20940
  const cfg = await loadConfig2();
19874
20941
  const home = process.env.HOME ?? "";
19875
- const hasKey = existsSync16(join(home, ".exe-os", "master.key"));
20942
+ const hasKey = existsSync18(join(home, ".exe-os", "master.key"));
19876
20943
  if (cfg.cloud) {
19877
20944
  setCloud({
19878
20945
  configured: true,
@@ -19885,22 +20952,22 @@ function SettingsView({ onBack }) {
19885
20952
  const pidPath = join(home, ".exe-os", "exed.pid");
19886
20953
  let daemon = "unknown";
19887
20954
  try {
19888
- daemon = existsSync16(pidPath) ? "running" : "stopped";
20955
+ daemon = existsSync18(pidPath) ? "running" : "stopped";
19889
20956
  } catch {
19890
20957
  }
19891
20958
  let version = "unknown";
19892
20959
  try {
19893
- const { readFileSync: readFileSync13 } = await import("fs");
19894
- const { createRequire } = await import("module");
19895
- const require2 = createRequire(import.meta.url);
20960
+ const { readFileSync: readFileSync14 } = await import("fs");
20961
+ const { createRequire: createRequire3 } = await import("module");
20962
+ const require2 = createRequire3(import.meta.url);
19896
20963
  const pkgPath = require2.resolve("@askexenow/exe-os/package.json");
19897
- const pkg = JSON.parse(readFileSync13(pkgPath, "utf8"));
20964
+ const pkg = JSON.parse(readFileSync14(pkgPath, "utf8"));
19898
20965
  version = pkg.version;
19899
20966
  } catch {
19900
20967
  try {
19901
- const { readFileSync: readFileSync13 } = await import("fs");
20968
+ const { readFileSync: readFileSync14 } = await import("fs");
19902
20969
  const { join: joinPath } = await import("path");
19903
- const pkg = JSON.parse(readFileSync13(joinPath(process.cwd(), "package.json"), "utf8"));
20970
+ const pkg = JSON.parse(readFileSync14(joinPath(process.cwd(), "package.json"), "utf8"));
19904
20971
  version = pkg.version;
19905
20972
  } catch {
19906
20973
  }