@askexenow/exe-os 0.9.111 → 0.9.113

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 (95) hide show
  1. package/README.md +9 -7
  2. package/dist/bin/agentic-ontology-backfill.js +62 -12
  3. package/dist/bin/agentic-reflection-backfill.js +37 -2
  4. package/dist/bin/agentic-semantic-label.js +37 -2
  5. package/dist/bin/backfill-conversations.js +61 -11
  6. package/dist/bin/backfill-responses.js +62 -12
  7. package/dist/bin/backfill-vectors.js +37 -2
  8. package/dist/bin/bulk-sync-postgres.js +63 -13
  9. package/dist/bin/cleanup-stale-review-tasks.js +83 -16
  10. package/dist/bin/cli.js +312 -80
  11. package/dist/bin/exe-agent-config.js +7 -1
  12. package/dist/bin/exe-agent.js +29 -3
  13. package/dist/bin/exe-assign.js +62 -12
  14. package/dist/bin/exe-boot.js +500 -151
  15. package/dist/bin/exe-call.js +46 -5
  16. package/dist/bin/exe-cloud.js +101 -16
  17. package/dist/bin/exe-dispatch.js +827 -27
  18. package/dist/bin/exe-doctor.js +61 -11
  19. package/dist/bin/exe-export-behaviors.js +67 -14
  20. package/dist/bin/exe-forget.js +62 -12
  21. package/dist/bin/exe-gateway.js +147 -27
  22. package/dist/bin/exe-heartbeat.js +83 -16
  23. package/dist/bin/exe-kill.js +62 -12
  24. package/dist/bin/exe-launch-agent.js +83 -15
  25. package/dist/bin/exe-new-employee.js +176 -8
  26. package/dist/bin/exe-pending-messages.js +83 -16
  27. package/dist/bin/exe-pending-notifications.js +83 -16
  28. package/dist/bin/exe-pending-reviews.js +83 -16
  29. package/dist/bin/exe-rename.js +62 -12
  30. package/dist/bin/exe-review.js +62 -12
  31. package/dist/bin/exe-search.js +62 -12
  32. package/dist/bin/exe-session-cleanup.js +949 -149
  33. package/dist/bin/exe-settings.js +10 -4
  34. package/dist/bin/exe-start-codex.js +537 -248
  35. package/dist/bin/exe-start-opencode.js +547 -168
  36. package/dist/bin/exe-status.js +83 -16
  37. package/dist/bin/exe-support.js +1 -1
  38. package/dist/bin/exe-team.js +62 -12
  39. package/dist/bin/git-sweep.js +827 -27
  40. package/dist/bin/graph-backfill.js +62 -12
  41. package/dist/bin/graph-export.js +62 -12
  42. package/dist/bin/install.js +62 -4
  43. package/dist/bin/intercom-check.js +949 -149
  44. package/dist/bin/pre-publish.js +14 -2
  45. package/dist/bin/scan-tasks.js +827 -27
  46. package/dist/bin/setup.js +99 -14
  47. package/dist/bin/shard-migrate.js +62 -12
  48. package/dist/bin/stack-update.js +1 -1
  49. package/dist/bin/update.js +3 -3
  50. package/dist/gateway/index.js +586 -26
  51. package/dist/hooks/bug-report-worker.js +586 -26
  52. package/dist/hooks/codex-stop-task-finalizer.js +977 -143
  53. package/dist/hooks/commit-complete.js +827 -27
  54. package/dist/hooks/error-recall.js +62 -12
  55. package/dist/hooks/ingest.js +4579 -249
  56. package/dist/hooks/instructions-loaded.js +62 -12
  57. package/dist/hooks/notification.js +62 -12
  58. package/dist/hooks/post-compact.js +83 -16
  59. package/dist/hooks/post-tool-combined.js +83 -16
  60. package/dist/hooks/pre-compact.js +907 -107
  61. package/dist/hooks/pre-tool-use.js +98 -16
  62. package/dist/hooks/prompt-submit.js +596 -30
  63. package/dist/hooks/session-end.js +909 -112
  64. package/dist/hooks/session-start.js +112 -17
  65. package/dist/hooks/stop.js +82 -15
  66. package/dist/hooks/subagent-stop.js +83 -16
  67. package/dist/hooks/summary-worker.js +81 -8
  68. package/dist/index.js +595 -29
  69. package/dist/lib/agent-config.js +16 -1
  70. package/dist/lib/cloud-sync.js +45 -1
  71. package/dist/lib/consolidation.js +16 -1
  72. package/dist/lib/database.js +23 -0
  73. package/dist/lib/db.js +23 -0
  74. package/dist/lib/device-registry.js +23 -0
  75. package/dist/lib/employee-templates.js +30 -4
  76. package/dist/lib/employees.js +16 -1
  77. package/dist/lib/exe-daemon.js +482 -52
  78. package/dist/lib/hybrid-search.js +62 -12
  79. package/dist/lib/license.js +3 -3
  80. package/dist/lib/messaging.js +21 -4
  81. package/dist/lib/schedules.js +37 -2
  82. package/dist/lib/skill-learning.js +910 -41
  83. package/dist/lib/status-brief.js +14 -1
  84. package/dist/lib/store.js +62 -12
  85. package/dist/lib/tasks.js +843 -93
  86. package/dist/lib/tmux-routing.js +766 -16
  87. package/dist/mcp/server.js +238 -41
  88. package/dist/mcp/tools/create-task.js +525 -15
  89. package/dist/mcp/tools/deactivate-behavior.js +33 -24
  90. package/dist/mcp/tools/list-tasks.js +21 -4
  91. package/dist/mcp/tools/send-message.js +21 -4
  92. package/dist/mcp/tools/update-task.js +840 -93
  93. package/dist/runtime/index.js +913 -107
  94. package/dist/tui/App.js +227 -58
  95. package/package.json +1 -1
@@ -651,6 +651,19 @@ var init_runtime_table = __esm({
651
651
  });
652
652
 
653
653
  // src/lib/agent-config.ts
654
+ var agent_config_exports = {};
655
+ __export(agent_config_exports, {
656
+ AGENT_CONFIG_PATH: () => AGENT_CONFIG_PATH,
657
+ DEFAULT_MODELS: () => DEFAULT_MODELS,
658
+ KNOWN_RUNTIMES: () => KNOWN_RUNTIMES,
659
+ RUNTIME_LABELS: () => RUNTIME_LABELS,
660
+ clearAgentRuntime: () => clearAgentRuntime,
661
+ getAgentRuntime: () => getAgentRuntime,
662
+ loadAgentConfig: () => loadAgentConfig,
663
+ saveAgentConfig: () => saveAgentConfig,
664
+ setAgentMcps: () => setAgentMcps,
665
+ setAgentRuntime: () => setAgentRuntime
666
+ });
654
667
  import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync4 } from "fs";
655
668
  import path3 from "path";
656
669
  function loadAgentConfig() {
@@ -661,6 +674,12 @@ function loadAgentConfig() {
661
674
  return {};
662
675
  }
663
676
  }
677
+ function saveAgentConfig(config) {
678
+ const dir = path3.dirname(AGENT_CONFIG_PATH);
679
+ ensurePrivateDirSync(dir);
680
+ writeFileSync2(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
681
+ enforcePrivateFileSync(AGENT_CONFIG_PATH);
682
+ }
664
683
  function getAgentRuntime(agentId) {
665
684
  const config = loadAgentConfig();
666
685
  const entry = config[agentId];
@@ -669,7 +688,47 @@ function getAgentRuntime(agentId) {
669
688
  if (orgDefault) return orgDefault;
670
689
  return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
671
690
  }
672
- var AGENT_CONFIG_PATH, DEFAULT_MODELS;
691
+ function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
692
+ const knownModels = KNOWN_RUNTIMES[runtime];
693
+ if (!knownModels) {
694
+ return {
695
+ ok: false,
696
+ error: `Unknown runtime "${runtime}". Valid: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`
697
+ };
698
+ }
699
+ if (!knownModels.includes(model)) {
700
+ return {
701
+ ok: false,
702
+ error: `Unknown model "${model}" for runtime "${runtime}". Valid: ${knownModels.join(", ")}`
703
+ };
704
+ }
705
+ const config = loadAgentConfig();
706
+ const existing = config[agentId];
707
+ const entry = { runtime, model };
708
+ if (reasoning_effort) entry.reasoning_effort = reasoning_effort;
709
+ if (mcps !== void 0) {
710
+ entry.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
711
+ } else if (existing?.mcps) {
712
+ entry.mcps = existing.mcps;
713
+ }
714
+ config[agentId] = entry;
715
+ saveAgentConfig(config);
716
+ return { ok: true };
717
+ }
718
+ function setAgentMcps(agentId, mcps) {
719
+ const config = loadAgentConfig();
720
+ const existing = config[agentId] ?? getAgentRuntime(agentId);
721
+ existing.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
722
+ config[agentId] = existing;
723
+ saveAgentConfig(config);
724
+ return { ok: true };
725
+ }
726
+ function clearAgentRuntime(agentId) {
727
+ const config = loadAgentConfig();
728
+ delete config[agentId];
729
+ saveAgentConfig(config);
730
+ }
731
+ var AGENT_CONFIG_PATH, KNOWN_RUNTIMES, RUNTIME_LABELS, DEFAULT_MODELS;
673
732
  var init_agent_config = __esm({
674
733
  "src/lib/agent-config.ts"() {
675
734
  "use strict";
@@ -677,6 +736,16 @@ var init_agent_config = __esm({
677
736
  init_runtime_table();
678
737
  init_secure_files();
679
738
  AGENT_CONFIG_PATH = path3.join(EXE_AI_DIR, "agent-config.json");
739
+ KNOWN_RUNTIMES = {
740
+ claude: ["claude-opus-4.6", "claude-opus-4", "claude-sonnet-4.6", "claude-sonnet-4", "claude-haiku-4.5"],
741
+ codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
742
+ opencode: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4", "google/gemini-2.5-pro", "deepseek/deepseek-r3", "minimax/minimax-m2.5"]
743
+ };
744
+ RUNTIME_LABELS = {
745
+ claude: "Claude Code (Anthropic)",
746
+ codex: "Codex (OpenAI)",
747
+ opencode: "OpenCode (open source)"
748
+ };
680
749
  DEFAULT_MODELS = {
681
750
  claude: "claude-opus-4.6",
682
751
  codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
@@ -867,6 +936,32 @@ var init_db_retry = __esm({
867
936
  });
868
937
 
869
938
  // src/lib/employees.ts
939
+ var employees_exports = {};
940
+ __export(employees_exports, {
941
+ COORDINATOR_ROLE: () => COORDINATOR_ROLE,
942
+ DEFAULT_COORDINATOR_TEMPLATE_NAME: () => DEFAULT_COORDINATOR_TEMPLATE_NAME,
943
+ EMPLOYEES_PATH: () => EMPLOYEES_PATH,
944
+ addEmployee: () => addEmployee,
945
+ baseAgentName: () => baseAgentName,
946
+ canCoordinate: () => canCoordinate,
947
+ getCoordinatorEmployee: () => getCoordinatorEmployee,
948
+ getCoordinatorName: () => getCoordinatorName,
949
+ getEmployee: () => getEmployee,
950
+ getEmployeeByRole: () => getEmployeeByRole,
951
+ getEmployeeNamesByRole: () => getEmployeeNamesByRole,
952
+ hasRole: () => hasRole,
953
+ hireEmployee: () => hireEmployee,
954
+ isCoordinatorName: () => isCoordinatorName,
955
+ isCoordinatorRole: () => isCoordinatorRole,
956
+ isMultiInstance: () => isMultiInstance,
957
+ loadEmployees: () => loadEmployees,
958
+ loadEmployeesSync: () => loadEmployeesSync,
959
+ normalizeRole: () => normalizeRole,
960
+ normalizeRosterCase: () => normalizeRosterCase,
961
+ registerBinSymlinks: () => registerBinSymlinks,
962
+ saveEmployees: () => saveEmployees,
963
+ validateEmployeeName: () => validateEmployeeName
964
+ });
870
965
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
871
966
  import { existsSync as existsSync6, symlinkSync, readlinkSync, readFileSync as readFileSync5, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
872
967
  import { execSync as execSync3 } from "child_process";
@@ -888,6 +983,24 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
888
983
  if (!agentName) return false;
889
984
  return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
890
985
  }
986
+ function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
987
+ return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
988
+ }
989
+ function validateEmployeeName(name) {
990
+ if (!name) {
991
+ return { valid: false, error: "Name is required" };
992
+ }
993
+ if (name.length > 32) {
994
+ return { valid: false, error: "Name must be 32 characters or fewer" };
995
+ }
996
+ if (!/^[a-z][a-z0-9]*$/.test(name)) {
997
+ return {
998
+ valid: false,
999
+ error: "Name must start with a letter and contain only lowercase alphanumeric characters"
1000
+ };
1001
+ }
1002
+ return { valid: true };
1003
+ }
891
1004
  async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
892
1005
  if (!existsSync6(employeesPath)) {
893
1006
  return [];
@@ -899,6 +1012,10 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
899
1012
  return [];
900
1013
  }
901
1014
  }
1015
+ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
1016
+ await mkdir2(path5.dirname(employeesPath), { recursive: true });
1017
+ await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
1018
+ }
902
1019
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
903
1020
  if (!existsSync6(employeesPath)) return [];
904
1021
  try {
@@ -910,6 +1027,19 @@ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
910
1027
  function getEmployee(employees, name) {
911
1028
  return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
912
1029
  }
1030
+ function getEmployeeByRole(employees, role) {
1031
+ const lower = role.toLowerCase();
1032
+ return employees.find((e) => e.role.toLowerCase() === lower);
1033
+ }
1034
+ function getEmployeeNamesByRole(employees, role) {
1035
+ const lower = role.toLowerCase();
1036
+ return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
1037
+ }
1038
+ function hasRole(agentName, role) {
1039
+ const employees = loadEmployeesSync();
1040
+ const emp = getEmployee(employees, agentName);
1041
+ return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
1042
+ }
913
1043
  function baseAgentName(name, employees) {
914
1044
  const match = name.match(/^([a-zA-Z]+)\d+$/);
915
1045
  if (!match) return name;
@@ -924,7 +1054,131 @@ function isMultiInstance(agentName, employees) {
924
1054
  if (!emp) return false;
925
1055
  return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
926
1056
  }
927
- var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR;
1057
+ function addEmployee(employees, employee) {
1058
+ const { systemPrompt: _legacyPrompt, ...rest } = employee;
1059
+ const normalized = { ...rest, name: employee.name.toLowerCase() };
1060
+ if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
1061
+ throw new Error(`Employee '${normalized.name}' already exists`);
1062
+ }
1063
+ return [...employees, normalized];
1064
+ }
1065
+ function appendToCoordinatorTeam(employee) {
1066
+ const coordinator = getCoordinatorEmployee(loadEmployeesSync());
1067
+ if (!coordinator) return;
1068
+ const idPath = path5.join(IDENTITY_DIR, `${coordinator.name}.md`);
1069
+ if (!existsSync6(idPath)) return;
1070
+ const content = readFileSync5(idPath, "utf-8");
1071
+ if (content.includes(`**${capitalize(employee.name)}`)) return;
1072
+ const teamMatch = content.match(TEAM_SECTION_RE);
1073
+ if (!teamMatch || teamMatch.index === void 0) return;
1074
+ const afterTeam = content.slice(teamMatch.index + teamMatch[0].length);
1075
+ const nextHeading = afterTeam.match(/\n## /);
1076
+ const entry = `
1077
+ **${capitalize(employee.name)} (${employee.role}):** Newly hired. Update this description as the role develops.
1078
+ `;
1079
+ let updated;
1080
+ if (nextHeading && nextHeading.index !== void 0) {
1081
+ const insertAt = teamMatch.index + teamMatch[0].length + nextHeading.index;
1082
+ updated = content.slice(0, insertAt) + entry + content.slice(insertAt);
1083
+ } else {
1084
+ updated = content.trimEnd() + "\n" + entry;
1085
+ }
1086
+ writeFileSync4(idPath, updated, "utf-8");
1087
+ }
1088
+ function capitalize(s) {
1089
+ return s.charAt(0).toUpperCase() + s.slice(1);
1090
+ }
1091
+ async function hireEmployee(employee) {
1092
+ const employees = await loadEmployees();
1093
+ const updated = addEmployee(employees, employee);
1094
+ await saveEmployees(updated);
1095
+ try {
1096
+ appendToCoordinatorTeam(employee);
1097
+ } catch {
1098
+ }
1099
+ try {
1100
+ const { loadAgentConfig: loadAgentConfig2, saveAgentConfig: saveAgentConfig2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
1101
+ const config = loadAgentConfig2();
1102
+ const name = employee.name.toLowerCase();
1103
+ if (!config[name] && config["default"]) {
1104
+ config[name] = { ...config["default"] };
1105
+ saveAgentConfig2(config);
1106
+ }
1107
+ } catch {
1108
+ }
1109
+ return updated;
1110
+ }
1111
+ async function normalizeRosterCase(rosterPath) {
1112
+ const employees = await loadEmployees(rosterPath);
1113
+ let changed = false;
1114
+ for (const emp of employees) {
1115
+ if (emp.name !== emp.name.toLowerCase()) {
1116
+ const oldName = emp.name;
1117
+ emp.name = emp.name.toLowerCase();
1118
+ changed = true;
1119
+ try {
1120
+ const identityDir = path5.join(os4.homedir(), ".exe-os", "identity");
1121
+ const oldPath = path5.join(identityDir, `${oldName}.md`);
1122
+ const newPath = path5.join(identityDir, `${emp.name}.md`);
1123
+ if (existsSync6(oldPath) && !existsSync6(newPath)) {
1124
+ renameSync3(oldPath, newPath);
1125
+ } else if (existsSync6(oldPath) && oldPath !== newPath) {
1126
+ const content = readFileSync5(oldPath, "utf-8");
1127
+ writeFileSync4(newPath, content, "utf-8");
1128
+ if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
1129
+ unlinkSync(oldPath);
1130
+ }
1131
+ }
1132
+ } catch {
1133
+ }
1134
+ }
1135
+ }
1136
+ if (changed) {
1137
+ await saveEmployees(employees, rosterPath);
1138
+ }
1139
+ return changed;
1140
+ }
1141
+ function findExeBin() {
1142
+ try {
1143
+ return execSync3(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
1144
+ } catch {
1145
+ return null;
1146
+ }
1147
+ }
1148
+ function registerBinSymlinks(name) {
1149
+ const created = [];
1150
+ const skipped = [];
1151
+ const errors = [];
1152
+ const exeBinPath = findExeBin();
1153
+ if (!exeBinPath) {
1154
+ errors.push("Could not find 'exe-os' in PATH");
1155
+ return { created, skipped, errors };
1156
+ }
1157
+ const binDir = path5.dirname(exeBinPath);
1158
+ let target;
1159
+ try {
1160
+ target = readlinkSync(exeBinPath);
1161
+ } catch {
1162
+ errors.push("Could not read 'exe' symlink");
1163
+ return { created, skipped, errors };
1164
+ }
1165
+ for (const suffix of ["", "-opencode"]) {
1166
+ const linkName = `${name}${suffix}`;
1167
+ const linkPath = path5.join(binDir, linkName);
1168
+ if (existsSync6(linkPath)) {
1169
+ skipped.push(linkName);
1170
+ continue;
1171
+ }
1172
+ try {
1173
+ symlinkSync(target, linkPath);
1174
+ created.push(linkName);
1175
+ } catch (err) {
1176
+ errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
1177
+ }
1178
+ }
1179
+ return { created, skipped, errors };
1180
+ }
1181
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR, TEAM_SECTION_RE;
928
1182
  var init_employees = __esm({
929
1183
  "src/lib/employees.ts"() {
930
1184
  "use strict";
@@ -934,6 +1188,7 @@ var init_employees = __esm({
934
1188
  COORDINATOR_ROLE = "COO";
935
1189
  MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
936
1190
  IDENTITY_DIR = path5.join(EXE_AI_DIR, "identity");
1191
+ TEAM_SECTION_RE = /^## Team\b.*$/m;
937
1192
  }
938
1193
  });
939
1194
 
@@ -2549,6 +2804,13 @@ async function ensureSchema() {
2549
2804
  } catch (e) {
2550
2805
  logCatchDebug("migration", e);
2551
2806
  }
2807
+ for (const col of ["created_by_agent TEXT", "created_by_device TEXT", "source_session_id TEXT"]) {
2808
+ try {
2809
+ await client.execute({ sql: `ALTER TABLE behaviors ADD COLUMN ${col}`, args: [] });
2810
+ } catch (e) {
2811
+ logCatchDebug("migration", e);
2812
+ }
2813
+ }
2552
2814
  try {
2553
2815
  await client.execute({
2554
2816
  sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
@@ -3765,6 +4027,22 @@ async function ensureSchema() {
3765
4027
  } catch (e) {
3766
4028
  logCatchDebug("migration", e);
3767
4029
  }
4030
+ try {
4031
+ await client.execute({
4032
+ sql: `ALTER TABLE memories ADD COLUMN visibility TEXT DEFAULT 'private'`,
4033
+ args: []
4034
+ });
4035
+ } catch (e) {
4036
+ logCatchDebug("migration", e);
4037
+ }
4038
+ try {
4039
+ await client.execute({
4040
+ sql: `ALTER TABLE memories ADD COLUMN strength REAL DEFAULT 1.0`,
4041
+ args: []
4042
+ });
4043
+ } catch (e) {
4044
+ logCatchDebug("migration", e);
4045
+ }
3768
4046
  }
3769
4047
  async function disposeDatabase() {
3770
4048
  if (_walCheckpointTimer) {
@@ -3816,6 +4094,23 @@ var init_database = __esm({
3816
4094
  });
3817
4095
 
3818
4096
  // src/lib/license.ts
4097
+ var license_exports = {};
4098
+ __export(license_exports, {
4099
+ LICENSE_PUBLIC_KEY_PEM: () => LICENSE_PUBLIC_KEY_PEM,
4100
+ PLAN_LIMITS: () => PLAN_LIMITS,
4101
+ assertVpsLicense: () => assertVpsLicense,
4102
+ checkLicense: () => checkLicense,
4103
+ getCachedLicense: () => getCachedLicense,
4104
+ isFeatureAllowed: () => isFeatureAllowed,
4105
+ loadDeviceId: () => loadDeviceId,
4106
+ loadLicense: () => loadLicense,
4107
+ mirrorLicenseKey: () => mirrorLicenseKey,
4108
+ readCachedLicenseToken: () => readCachedLicenseToken,
4109
+ saveLicense: () => saveLicense,
4110
+ startLicenseRevalidation: () => startLicenseRevalidation,
4111
+ stopLicenseRevalidation: () => stopLicenseRevalidation,
4112
+ validateLicense: () => validateLicense
4113
+ });
3819
4114
  import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync10, mkdirSync as mkdirSync5 } from "fs";
3820
4115
  import { randomUUID as randomUUID2 } from "crypto";
3821
4116
  import { createRequire as createRequire2 } from "module";
@@ -3823,7 +4118,411 @@ import { pathToFileURL as pathToFileURL2 } from "url";
3823
4118
  import os7 from "os";
3824
4119
  import path9 from "path";
3825
4120
  import { jwtVerify, importSPKI } from "jose";
3826
- var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, PLAN_LIMITS;
4121
+ async function fetchRetry(url, init) {
4122
+ try {
4123
+ return await fetch(url, init);
4124
+ } catch {
4125
+ await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
4126
+ return fetch(url, { ...init, signal: AbortSignal.timeout(1e4) });
4127
+ }
4128
+ }
4129
+ function loadDeviceId() {
4130
+ const deviceJsonPath = path9.join(EXE_AI_DIR, "device.json");
4131
+ try {
4132
+ if (existsSync10(deviceJsonPath)) {
4133
+ const data = JSON.parse(readFileSync8(deviceJsonPath, "utf8"));
4134
+ if (data.deviceId) return data.deviceId;
4135
+ }
4136
+ } catch {
4137
+ }
4138
+ try {
4139
+ if (existsSync10(DEVICE_ID_PATH)) {
4140
+ const id2 = readFileSync8(DEVICE_ID_PATH, "utf8").trim();
4141
+ if (id2) return id2;
4142
+ }
4143
+ } catch {
4144
+ }
4145
+ const id = randomUUID2();
4146
+ mkdirSync5(EXE_AI_DIR, { recursive: true });
4147
+ writeFileSync6(DEVICE_ID_PATH, id, "utf8");
4148
+ return id;
4149
+ }
4150
+ function loadLicense() {
4151
+ try {
4152
+ if (!existsSync10(LICENSE_PATH)) return null;
4153
+ return readFileSync8(LICENSE_PATH, "utf8").trim();
4154
+ } catch {
4155
+ return null;
4156
+ }
4157
+ }
4158
+ function saveLicense(apiKey) {
4159
+ mkdirSync5(EXE_AI_DIR, { recursive: true });
4160
+ writeFileSync6(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
4161
+ }
4162
+ async function verifyLicenseJwt(token) {
4163
+ try {
4164
+ const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
4165
+ const { payload } = await jwtVerify(token, key, {
4166
+ algorithms: [LICENSE_JWT_ALG]
4167
+ });
4168
+ const plan = payload.plan ?? "free";
4169
+ const email = payload.sub ?? "";
4170
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
4171
+ return {
4172
+ valid: true,
4173
+ plan,
4174
+ email,
4175
+ expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
4176
+ deviceLimit: limits.devices,
4177
+ employeeLimit: limits.employees,
4178
+ memoryLimit: limits.memories
4179
+ };
4180
+ } catch {
4181
+ return null;
4182
+ }
4183
+ }
4184
+ async function getCachedLicense() {
4185
+ try {
4186
+ if (!existsSync10(CACHE_PATH)) return null;
4187
+ const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
4188
+ if (!raw.token || typeof raw.token !== "string") return null;
4189
+ return await verifyLicenseJwt(raw.token);
4190
+ } catch {
4191
+ return null;
4192
+ }
4193
+ }
4194
+ function readCachedLicenseToken() {
4195
+ try {
4196
+ if (!existsSync10(CACHE_PATH)) return null;
4197
+ const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
4198
+ return typeof raw.token === "string" ? raw.token : null;
4199
+ } catch {
4200
+ return null;
4201
+ }
4202
+ }
4203
+ function getRawCachedPlan() {
4204
+ try {
4205
+ const token = readCachedLicenseToken();
4206
+ if (!token) return null;
4207
+ const parts = token.split(".");
4208
+ if (parts.length !== 3) return null;
4209
+ const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
4210
+ const plan = payload.plan ?? "free";
4211
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
4212
+ process.stderr.write(
4213
+ `[license] WARN: using unverified cached plan (API unreachable, JWT expired). Plan: ${plan}
4214
+ `
4215
+ );
4216
+ return {
4217
+ valid: true,
4218
+ plan,
4219
+ email: payload.sub ?? "",
4220
+ expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
4221
+ deviceLimit: limits.devices,
4222
+ employeeLimit: limits.employees,
4223
+ memoryLimit: limits.memories
4224
+ };
4225
+ } catch {
4226
+ return null;
4227
+ }
4228
+ }
4229
+ function cacheResponse(token) {
4230
+ try {
4231
+ writeFileSync6(CACHE_PATH, JSON.stringify({ token }), "utf8");
4232
+ } catch {
4233
+ }
4234
+ }
4235
+ function loadPrismaForLicense() {
4236
+ if (_prismaFailed) return null;
4237
+ const dbUrl = process.env.DATABASE_URL;
4238
+ if (!dbUrl) {
4239
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path9.join(os7.homedir(), "exe-db");
4240
+ if (!existsSync10(path9.join(exeDbRoot, "package.json"))) {
4241
+ _prismaFailed = true;
4242
+ return null;
4243
+ }
4244
+ }
4245
+ if (!_prismaPromise) {
4246
+ _prismaPromise = (async () => {
4247
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
4248
+ if (explicitPath) {
4249
+ const mod2 = await import(pathToFileURL2(explicitPath).href);
4250
+ const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
4251
+ if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
4252
+ return new Ctor2();
4253
+ }
4254
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path9.join(os7.homedir(), "exe-db");
4255
+ const req = createRequire2(path9.join(exeDbRoot, "package.json"));
4256
+ const entry = req.resolve("@prisma/client");
4257
+ const mod = await import(pathToFileURL2(entry).href);
4258
+ const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
4259
+ if (!Ctor) throw new Error(`No PrismaClient in ${entry}`);
4260
+ return new Ctor();
4261
+ })().catch((err) => {
4262
+ _prismaFailed = true;
4263
+ _prismaPromise = null;
4264
+ throw err;
4265
+ });
4266
+ }
4267
+ return _prismaPromise;
4268
+ }
4269
+ async function validateViaPostgres(apiKey) {
4270
+ const loader = loadPrismaForLicense();
4271
+ if (!loader) return null;
4272
+ try {
4273
+ const prisma = await loader;
4274
+ const rows = await prisma.$queryRawUnsafe(
4275
+ `SELECT plan, email, status, device_limit, employee_limit, memory_limit, expires_at
4276
+ FROM billing.licenses WHERE key = $1 LIMIT 1`,
4277
+ apiKey
4278
+ );
4279
+ if (!rows || rows.length === 0) return null;
4280
+ const row = rows[0];
4281
+ if (row.status !== "active") return null;
4282
+ if (row.expires_at && new Date(row.expires_at) < /* @__PURE__ */ new Date()) return null;
4283
+ const plan = row.plan;
4284
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
4285
+ return {
4286
+ valid: true,
4287
+ plan,
4288
+ email: row.email,
4289
+ expiresAt: row.expires_at ? new Date(row.expires_at).toISOString() : null,
4290
+ deviceLimit: row.device_limit ?? limits.devices,
4291
+ employeeLimit: row.employee_limit ?? limits.employees,
4292
+ memoryLimit: row.memory_limit ?? limits.memories
4293
+ };
4294
+ } catch {
4295
+ return null;
4296
+ }
4297
+ }
4298
+ async function validateViaCFWorker(apiKey, deviceId) {
4299
+ try {
4300
+ const res = await fetchRetry(`${API_BASE}/auth/activate`, {
4301
+ method: "POST",
4302
+ headers: { "Content-Type": "application/json" },
4303
+ body: JSON.stringify({ apiKey, deviceId }),
4304
+ signal: AbortSignal.timeout(1e4)
4305
+ });
4306
+ if (!res.ok) return null;
4307
+ const data = await res.json();
4308
+ if (data.error === "device_limit_exceeded") return null;
4309
+ if (!data.valid) return null;
4310
+ if (data.token) {
4311
+ cacheResponse(data.token);
4312
+ const verified = await verifyLicenseJwt(data.token);
4313
+ if (verified) return verified;
4314
+ }
4315
+ const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
4316
+ return {
4317
+ valid: data.valid,
4318
+ plan: data.plan,
4319
+ email: data.email,
4320
+ expiresAt: data.expiresAt,
4321
+ deviceLimit: limits.devices,
4322
+ employeeLimit: limits.employees,
4323
+ memoryLimit: limits.memories
4324
+ };
4325
+ } catch {
4326
+ return null;
4327
+ }
4328
+ }
4329
+ async function validateLicense(apiKey, deviceId) {
4330
+ const did = deviceId ?? loadDeviceId();
4331
+ const pgResult = await validateViaPostgres(apiKey);
4332
+ if (pgResult) {
4333
+ try {
4334
+ writeFileSync6(CACHE_PATH, JSON.stringify({ pgLicense: pgResult, ts: Date.now() }), "utf8");
4335
+ } catch {
4336
+ }
4337
+ return pgResult;
4338
+ }
4339
+ const cfResult = await validateViaCFWorker(apiKey, did);
4340
+ if (cfResult) return cfResult;
4341
+ const cached = await getCachedLicense();
4342
+ if (cached) return cached;
4343
+ try {
4344
+ if (existsSync10(CACHE_PATH)) {
4345
+ const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
4346
+ if (raw.pgLicense && raw.ts && Date.now() - raw.ts < 7 * 24 * 60 * 60 * 1e3) {
4347
+ return raw.pgLicense;
4348
+ }
4349
+ }
4350
+ } catch {
4351
+ }
4352
+ const rawFallback = getRawCachedPlan();
4353
+ if (rawFallback) return rawFallback;
4354
+ return { ...FREE_LICENSE, valid: false };
4355
+ }
4356
+ function getCacheAgeMs() {
4357
+ try {
4358
+ const { statSync: statSync5 } = __require("fs");
4359
+ const s = statSync5(CACHE_PATH);
4360
+ return Date.now() - s.mtimeMs;
4361
+ } catch {
4362
+ return Infinity;
4363
+ }
4364
+ }
4365
+ async function checkLicense() {
4366
+ let key = loadLicense();
4367
+ if (!key) {
4368
+ try {
4369
+ const configPath = path9.join(EXE_AI_DIR, "config.json");
4370
+ if (existsSync10(configPath)) {
4371
+ const raw = JSON.parse(readFileSync8(configPath, "utf8"));
4372
+ const cloud = raw.cloud;
4373
+ if (cloud?.apiKey) {
4374
+ key = cloud.apiKey;
4375
+ saveLicense(key);
4376
+ }
4377
+ }
4378
+ } catch {
4379
+ }
4380
+ }
4381
+ if (!key) return FREE_LICENSE;
4382
+ const cached = await getCachedLicense();
4383
+ if (cached && getCacheAgeMs() < CACHE_MAX_AGE_MS) return cached;
4384
+ const deviceId = loadDeviceId();
4385
+ return validateLicense(key, deviceId);
4386
+ }
4387
+ function isFeatureAllowed(license, feature) {
4388
+ switch (feature) {
4389
+ case "cloud_sync":
4390
+ case "external_agents":
4391
+ case "wiki":
4392
+ return license.plan !== "free";
4393
+ case "unlimited_employees":
4394
+ return license.plan === "team" || license.plan === "agency" || license.plan === "enterprise";
4395
+ }
4396
+ }
4397
+ function mirrorLicenseKey(apiKey) {
4398
+ const trimmed = apiKey.trim();
4399
+ if (!trimmed) return;
4400
+ saveLicense(trimmed);
4401
+ }
4402
+ async function assertVpsLicense(opts) {
4403
+ const env = opts?.env ?? process.env;
4404
+ const inProduction = env.NODE_ENV === "production";
4405
+ if (!opts?.force && !inProduction) {
4406
+ return { ...FREE_LICENSE, plan: "free" };
4407
+ }
4408
+ const envKey = env.EXE_LICENSE_KEY?.trim();
4409
+ if (envKey) {
4410
+ saveLicense(envKey);
4411
+ }
4412
+ const apiKey = envKey ?? loadLicense();
4413
+ if (!apiKey) {
4414
+ throw new Error(
4415
+ "License required: set EXE_LICENSE_KEY env var with your exe_sk_* key. Purchase at https://askexe.com. This VPS image refuses to boot without a valid license."
4416
+ );
4417
+ }
4418
+ const deviceId = loadDeviceId();
4419
+ let backendResponse = null;
4420
+ let explicitRejection = false;
4421
+ let transientFailure = false;
4422
+ try {
4423
+ const res = await fetchRetry(`${API_BASE}/auth/activate`, {
4424
+ method: "POST",
4425
+ headers: { "Content-Type": "application/json" },
4426
+ body: JSON.stringify({ apiKey, deviceId }),
4427
+ signal: AbortSignal.timeout(1e4)
4428
+ });
4429
+ if (res.ok) {
4430
+ backendResponse = await res.json();
4431
+ if (!backendResponse.valid) explicitRejection = true;
4432
+ } else if (res.status === 401 || res.status === 403) {
4433
+ explicitRejection = true;
4434
+ } else {
4435
+ transientFailure = true;
4436
+ }
4437
+ } catch {
4438
+ transientFailure = true;
4439
+ }
4440
+ if (backendResponse?.valid) {
4441
+ if (backendResponse.token) {
4442
+ cacheResponse(backendResponse.token);
4443
+ const verified = await verifyLicenseJwt(backendResponse.token);
4444
+ if (verified) return verified;
4445
+ }
4446
+ const limits = PLAN_LIMITS[backendResponse.plan] ?? PLAN_LIMITS.free;
4447
+ return {
4448
+ valid: true,
4449
+ plan: backendResponse.plan,
4450
+ email: backendResponse.email,
4451
+ expiresAt: backendResponse.expiresAt,
4452
+ deviceLimit: limits.devices,
4453
+ employeeLimit: limits.employees,
4454
+ memoryLimit: limits.memories
4455
+ };
4456
+ }
4457
+ if (explicitRejection) {
4458
+ throw new Error(
4459
+ `License invalid or expired. Renew at https://askexe.com, then restart. Backend rejected key ending in ...${apiKey.slice(-4)}.`
4460
+ );
4461
+ }
4462
+ if (!transientFailure) {
4463
+ throw new Error(
4464
+ "License validation failed: unknown backend state. Restore network connectivity to https://cloud.askexe.com and retry."
4465
+ );
4466
+ }
4467
+ const fresh = await getCachedLicense();
4468
+ if (fresh && fresh.valid) return fresh;
4469
+ const graceDays = opts?.offlineGraceDays ?? 7;
4470
+ const graceMs = graceDays * 24 * 60 * 60 * 1e3;
4471
+ try {
4472
+ const token = readCachedLicenseToken();
4473
+ if (token) {
4474
+ const payloadB64 = token.split(".")[1];
4475
+ if (payloadB64) {
4476
+ const payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString());
4477
+ const expMs = (payload.exp ?? 0) * 1e3;
4478
+ if (Date.now() < expMs + graceMs) {
4479
+ const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
4480
+ const { payload: verified } = await jwtVerify(token, key, {
4481
+ algorithms: [LICENSE_JWT_ALG],
4482
+ clockTolerance: graceDays * 24 * 60 * 60
4483
+ });
4484
+ const plan = verified.plan ?? "free";
4485
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
4486
+ return {
4487
+ valid: true,
4488
+ plan,
4489
+ email: verified.sub ?? "",
4490
+ expiresAt: verified.exp ? new Date(verified.exp * 1e3).toISOString() : null,
4491
+ deviceLimit: limits.devices,
4492
+ employeeLimit: limits.employees,
4493
+ memoryLimit: limits.memories
4494
+ };
4495
+ }
4496
+ }
4497
+ }
4498
+ } catch {
4499
+ }
4500
+ throw new Error(
4501
+ `License validation unreachable for more than ${graceDays} days. Restore network connectivity to https://cloud.askexe.com and retry. This VPS image refuses to boot after the offline grace window.`
4502
+ );
4503
+ }
4504
+ function startLicenseRevalidation(intervalMs = 36e5) {
4505
+ if (_revalTimer) return;
4506
+ _revalTimer = setInterval(async () => {
4507
+ try {
4508
+ const license = await checkLicense();
4509
+ if (!license.valid) {
4510
+ process.stderr.write("[exe-os] License expired or invalid \u2014 features may be restricted\n");
4511
+ }
4512
+ } catch {
4513
+ }
4514
+ }, intervalMs);
4515
+ if (_revalTimer && typeof _revalTimer === "object" && "unref" in _revalTimer) {
4516
+ _revalTimer.unref();
4517
+ }
4518
+ }
4519
+ function stopLicenseRevalidation() {
4520
+ if (_revalTimer) {
4521
+ clearInterval(_revalTimer);
4522
+ _revalTimer = null;
4523
+ }
4524
+ }
4525
+ 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;
3827
4526
  var init_license = __esm({
3828
4527
  "src/lib/license.ts"() {
3829
4528
  "use strict";
@@ -3831,7 +4530,13 @@ var init_license = __esm({
3831
4530
  LICENSE_PATH = path9.join(EXE_AI_DIR, "license.key");
3832
4531
  CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
3833
4532
  DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
3834
- API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com/cloud";
4533
+ API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://cloud.askexe.com";
4534
+ RETRY_DELAY_MS = 500;
4535
+ LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
4536
+ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
4537
+ 4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
4538
+ -----END PUBLIC KEY-----`;
4539
+ LICENSE_JWT_ALG = "ES256";
3835
4540
  PLAN_LIMITS = {
3836
4541
  free: { devices: 1, employees: 1, memories: 5e3 },
3837
4542
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -3839,6 +4544,19 @@ var init_license = __esm({
3839
4544
  agency: { devices: 50, employees: 100, memories: 1e7 },
3840
4545
  enterprise: { devices: -1, employees: -1, memories: -1 }
3841
4546
  };
4547
+ FREE_LICENSE = {
4548
+ valid: true,
4549
+ plan: "free",
4550
+ email: "",
4551
+ expiresAt: null,
4552
+ deviceLimit: 1,
4553
+ employeeLimit: 1,
4554
+ memoryLimit: 5e3
4555
+ };
4556
+ _prismaPromise = null;
4557
+ _prismaFailed = false;
4558
+ CACHE_MAX_AGE_MS = 36e5;
4559
+ _revalTimer = null;
3842
4560
  }
3843
4561
  });
3844
4562
 
@@ -4333,6 +5051,19 @@ async function resolveTask(client, identifier, scopeSession) {
4333
5051
  args: [identifier, ...scope.args]
4334
5052
  });
4335
5053
  if (result.rows.length === 1) return result.rows[0];
5054
+ if (/^[a-f0-9]{7,12}$/i.test(identifier)) {
5055
+ result = await client.execute({
5056
+ sql: `SELECT * FROM tasks WHERE id LIKE ?`,
5057
+ args: [`${identifier}%`]
5058
+ });
5059
+ if (result.rows.length === 1) return result.rows[0];
5060
+ if (result.rows.length > 1) {
5061
+ const matches = result.rows.map((r) => `${String(r.id)} "${String(r.title)}" (${String(r.status)})`).join(", ");
5062
+ throw new Error(
5063
+ `Multiple tasks match short-ID "${identifier}": ${matches}. Use a longer prefix to disambiguate.`
5064
+ );
5065
+ }
5066
+ }
4336
5067
  result = await client.execute({
4337
5068
  sql: `SELECT * FROM tasks WHERE task_file LIKE ?${scope.sql}`,
4338
5069
  args: [`%${identifier}%`, ...scope.args]
@@ -5187,12 +5918,13 @@ async function cascadeUnblock(taskId, baseDir, now) {
5187
5918
  WHERE blocked_by = ? AND status = 'blocked'`,
5188
5919
  args: [now, taskId]
5189
5920
  });
5190
- if (baseDir && unblocked.rowsAffected > 0) {
5191
- const ubScope = sessionScopeFilter();
5192
- const unblockedRows = await client.execute({
5193
- sql: `SELECT task_file FROM tasks WHERE blocked_by IS NULL AND updated_at = ?${ubScope.sql}`,
5194
- args: [now, ...ubScope.args]
5195
- });
5921
+ if (unblocked.rowsAffected === 0) return;
5922
+ const ubScope = sessionScopeFilter();
5923
+ const unblockedRows = await client.execute({
5924
+ sql: `SELECT id, title, assigned_to, priority, task_file FROM tasks WHERE blocked_by IS NULL AND updated_at = ?${ubScope.sql}`,
5925
+ args: [now, ...ubScope.args]
5926
+ });
5927
+ if (baseDir) {
5196
5928
  for (const ur of unblockedRows.rows) {
5197
5929
  try {
5198
5930
  const ubFile = path16.join(baseDir, String(ur.task_file));
@@ -5204,6 +5936,19 @@ async function cascadeUnblock(taskId, baseDir, now) {
5204
5936
  }
5205
5937
  }
5206
5938
  }
5939
+ if (unblockedRows.rows.length > 0 && !process.env.VITEST) {
5940
+ try {
5941
+ const { queueIntercom: queueIntercom2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
5942
+ const dispatched = /* @__PURE__ */ new Set();
5943
+ for (const ur of unblockedRows.rows) {
5944
+ const assignee = String(ur.assigned_to);
5945
+ if (dispatched.has(assignee)) continue;
5946
+ dispatched.add(assignee);
5947
+ queueIntercom2(`${assignee}`, `unblocked: "${String(ur.title)}" is now ready`);
5948
+ }
5949
+ } catch {
5950
+ }
5951
+ }
5207
5952
  }
5208
5953
  async function findNextTask(assignedTo) {
5209
5954
  const client = getClient();
@@ -5413,6 +6158,15 @@ var init_embedder = __esm({
5413
6158
  // src/lib/behaviors.ts
5414
6159
  import crypto5 from "crypto";
5415
6160
  async function storeBehavior(opts) {
6161
+ try {
6162
+ const { loadEmployeesSync: loadEmployeesSync2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
6163
+ const roster = loadEmployeesSync2();
6164
+ if (roster.length > 0 && !roster.some((e) => e.name === opts.agentId)) {
6165
+ throw new Error(`Agent "${opts.agentId}" not found in roster. Cannot store behavior for unregistered agent.`);
6166
+ }
6167
+ } catch (e) {
6168
+ if (e instanceof Error && e.message.includes("not found in roster")) throw e;
6169
+ }
5416
6170
  const client = getClient();
5417
6171
  const id = crypto5.randomUUID();
5418
6172
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -5423,10 +6177,18 @@ async function storeBehavior(opts) {
5423
6177
  vector = new Float32Array(vec);
5424
6178
  } catch {
5425
6179
  }
6180
+ let createdByDevice = null;
6181
+ try {
6182
+ const { loadDeviceId: loadDeviceId2 } = await Promise.resolve().then(() => (init_license(), license_exports));
6183
+ createdByDevice = loadDeviceId2() ?? null;
6184
+ } catch {
6185
+ }
6186
+ const createdByAgent = process.env.AGENT_ID ?? null;
6187
+ const sourceSessionId = process.env.CLAUDE_SESSION_ID ?? process.env.SESSION_ID ?? null;
5426
6188
  await client.execute({
5427
- sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at, vector)
5428
- VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?)`,
5429
- args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now, vector ? vector.buffer : null]
6189
+ sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at, vector, created_by_agent, created_by_device, source_session_id)
6190
+ VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?)`,
6191
+ args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now, vector ? vector.buffer : null, createdByAgent, createdByDevice, sourceSessionId]
5430
6192
  });
5431
6193
  return id;
5432
6194
  }
@@ -5858,6 +6620,12 @@ async function updateTask(input) {
5858
6620
  }
5859
6621
  }
5860
6622
  }
6623
+ if (input.status === "cancelled") {
6624
+ try {
6625
+ await cascadeUnblock(taskId, input.baseDir, now);
6626
+ } catch {
6627
+ }
6628
+ }
5861
6629
  if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
5862
6630
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
5863
6631
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
@@ -6389,11 +7157,12 @@ function getDispatchedBy(sessionKey) {
6389
7157
  }
6390
7158
  }
6391
7159
  function resolveExeSession() {
7160
+ if (process.env.EXE_SESSION_NAME) {
7161
+ const fromEnv = extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
7162
+ if (fromEnv) return fromEnv;
7163
+ }
6392
7164
  const mySession = getMySession();
6393
7165
  if (!mySession) {
6394
- if (process.env.EXE_SESSION_NAME) {
6395
- return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
6396
- }
6397
7166
  return null;
6398
7167
  }
6399
7168
  const fromSessionName = extractRootExe(mySession);
@@ -6408,6 +7177,10 @@ function resolveExeSession() {
6408
7177
  `[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
6409
7178
  `
6410
7179
  );
7180
+ try {
7181
+ registerParentExe(key, fromSessionName);
7182
+ } catch {
7183
+ }
6411
7184
  candidate = fromSessionName;
6412
7185
  } else {
6413
7186
  candidate = fromCache;
@@ -8135,11 +8908,17 @@ var init_platform_procedures = __esm({
8135
8908
  content: "Founder -> coordinator (the executive agent, internally routed as 'COO') -> CTO/CMO. CTO -> engineers. CMO -> content production. Never skip levels: the coordinator does not bypass managers for specialist work. Specialists report to their manager. If you need cross-team info, use ask_team_memory \u2014 don't read other agents' task folders. Each level owns dispatch downward and review upward."
8136
8909
  },
8137
8910
  {
8138
- title: "Customer orchestration maturity \u2014 recommend, never trap",
8911
+ title: "Orchestration phase guidance \u2014 recommend, never trap",
8139
8912
  domain: "workflow",
8140
8913
  priority: "p1",
8141
8914
  content: "New customers start best in Phase 1: founder \u2194 coordinator/Chief of Staff, building company context. Suggest Phase 2 executives when domain work repeats; suggest Phase 3 parallel execution only when review/permission gates are ready. This is guidance, not a blocker: users may jump phases anytime. Never overwrite their phase, role titles, identities, or custom org design."
8142
8915
  },
8916
+ {
8917
+ title: "Routing slot vs display title \u2014 internal 'coo' is plumbing, not your name",
8918
+ domain: "identity",
8919
+ priority: "p0",
8920
+ content: "These procedures reference 'COO' as a shorthand for the coordinator role. This is an INTERNAL routing slot used by exe-os code (chain-of-command checks, dispatch logic, session detection). It is NOT your display title. Your actual title comes from your identity file's `title:` field \u2014 that is what you use externally: introductions, sign-offs, team comms, and any user-facing text. If your identity says `title: AI Chief of Staff`, you are the AI Chief of Staff. The routing slot stays `role: coo` for code compatibility \u2014 never rename it, but also never introduce yourself as 'COO' unless your identity file explicitly says so. The founder chose your title; respect it."
8921
+ },
8143
8922
  {
8144
8923
  title: "Single dispatch path \u2014 create_task only",
8145
8924
  domain: "workflow",
@@ -8173,6 +8952,12 @@ var init_platform_procedures = __esm({
8173
8952
  priority: "p0",
8174
8953
  content: "NEVER: (1) Access the database directly \u2014 it's SQLCipher encrypted, always fails. Use MCP tools only. (2) Manually spawn tmux sessions \u2014 create_task handles it. (3) Run git checkout main \u2014 agents work in worktrees. (4) Modify another agent's in-progress task. (5) Push to remote \u2014 the COO reviews and pushes. (6) Skip update_task(done) \u2014 it's the ONLY way your work gets reviewed. (7) Run git init."
8175
8954
  },
8955
+ {
8956
+ title: "Destructive operations \u2014 mandatory reviewer gate",
8957
+ domain: "security",
8958
+ priority: "p0",
8959
+ content: "Before ANY destructive operation (delete, remove, overwrite, drop, reset, force-push, truncate), you MUST: (1) Have your full task spec accessible \u2014 if you cannot read it, STOP and report to your reviewer. Never improvise destructive actions. (2) Confirm with your reviewer (assigned_by or COO) before executing. (3) If the task spec explicitly authorizes the operation, proceed \u2014 but log it. Violation = immediate task failure. This applies to ALL agents regardless of role."
8960
+ },
8176
8961
  {
8177
8962
  title: "Customer patch triage \u2014 upstream bug vs customization",
8178
8963
  domain: "support",
@@ -8324,7 +9109,7 @@ var init_platform_procedures = __esm({
8324
9109
  title: "MCP tool dispatch \u2014 all tools use action parameter",
8325
9110
  domain: "tool-use",
8326
9111
  priority: "p0",
8327
- content: 'exe-os MCP tools come in two surfaces depending on EXE_MCP_TOOL_SURFACE config. Consolidated (19 tools): action-based dispatch \u2014 memory(action="recall"), task(action="create"), etc. Legacy (108 tools): one tool per operation \u2014 recall_my_memory, create_task, etc. Both surfaces have identical functionality. Use whichever tool names are available in your session. If you see domain tools (memory, task, config, etc.), use the action parameter. If you see specific tools (recall_my_memory, create_task, etc.), call them directly.'
9112
+ content: 'exe-os MCP tools use consolidated action-based dispatch by default (19 tools). Call domain tools with an action parameter: memory(action="recall"), task(action="create"), config(action="list_employees"), etc. Legacy mode (108 separate tools like recall_my_memory, create_task) is still available via EXE_MCP_TOOL_SURFACE=legacy but will be removed in a future version. If you see specific tool names, call them directly \u2014 both surfaces are identical. Consolidated is the default and recommended surface.'
8328
9113
  },
8329
9114
  {
8330
9115
  title: "MCP tools \u2014 memory, decision, and search",
@@ -8458,10 +9243,24 @@ function stableId(memoryId, type, content) {
8458
9243
  return createHash2("sha256").update(`${memoryId}:${type}:${content}`).digest("hex").slice(0, 32);
8459
9244
  }
8460
9245
  function cleanText(text) {
8461
- return text.replace(/```[\s\S]*?```/g, " ").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
9246
+ let cleaned = text.replace(
9247
+ /```(\w*)\n(.*?)(?:\n[\s\S]*?)```/g,
9248
+ (_m, lang, firstLine) => `[code${lang ? `:${lang}` : ""}] ${firstLine.trim()}`
9249
+ );
9250
+ cleaned = cleaned.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
9251
+ return cleaned;
8462
9252
  }
8463
- function splitSentences(text) {
8464
- return cleanText(text).split(/(?<=[.!?])\s+|\n+/).map((s) => s.trim()).filter((s) => s.length >= 24 && s.length <= MAX_SENTENCE_CHARS);
9253
+ function splitSegments(text) {
9254
+ const cleaned = cleanText(text);
9255
+ const segments = cleaned.split(/(?<=[.!?:;])\s+|\n{2,}|(?<=\))\s+(?=[A-Z])|\s*[|│]\s*/).map((s) => s.trim()).filter((s) => s.length >= MIN_SEGMENT_CHARS && s.length <= MAX_SEGMENT_CHARS);
9256
+ if (segments.length === 0 && cleaned.length >= MIN_SEGMENT_CHARS) {
9257
+ const lines = cleaned.split(/\n+/).map((l) => l.trim()).filter((l) => l.length >= MIN_SEGMENT_CHARS && l.length <= MAX_SEGMENT_CHARS);
9258
+ if (lines.length > 0) return lines;
9259
+ if (cleaned.length >= MIN_SEGMENT_CHARS) {
9260
+ return [cleaned.slice(0, MAX_SEGMENT_CHARS)];
9261
+ }
9262
+ }
9263
+ return segments;
8465
9264
  }
8466
9265
  function inferCardType(sentence, toolName) {
8467
9266
  const lower = sentence.toLowerCase();
@@ -8493,12 +9292,12 @@ function predicateFor(type) {
8493
9292
  }
8494
9293
  }
8495
9294
  function extractMemoryCards(row) {
8496
- const sentences = splitSentences(row.raw_text);
9295
+ const segments = splitSegments(row.raw_text);
8497
9296
  const cards = [];
8498
- for (const sentence of sentences) {
9297
+ for (const sentence of segments) {
8499
9298
  const type = inferCardType(sentence, row.tool_name);
8500
9299
  const subject = extractSubject(sentence, row.agent_id);
8501
- const content = sentence.length > MAX_SENTENCE_CHARS ? `${sentence.slice(0, MAX_SENTENCE_CHARS - 1)}\u2026` : sentence;
9300
+ const content = sentence.length > MAX_SEGMENT_CHARS ? `${sentence.slice(0, MAX_SEGMENT_CHARS - 1)}\u2026` : sentence;
8502
9301
  cards.push({
8503
9302
  id: stableId(row.id, type, content),
8504
9303
  memory_id: row.id,
@@ -8594,13 +9393,14 @@ Source memory: ${String(row.source_ref ?? row.memory_id)}`,
8594
9393
  last_accessed: String(row.timestamp)
8595
9394
  }));
8596
9395
  }
8597
- var MAX_CARDS_PER_MEMORY, MAX_SENTENCE_CHARS;
9396
+ var MAX_CARDS_PER_MEMORY, MAX_SEGMENT_CHARS, MIN_SEGMENT_CHARS;
8598
9397
  var init_memory_cards = __esm({
8599
9398
  "src/lib/memory-cards.ts"() {
8600
9399
  "use strict";
8601
9400
  init_database();
8602
- MAX_CARDS_PER_MEMORY = 6;
8603
- MAX_SENTENCE_CHARS = 360;
9401
+ MAX_CARDS_PER_MEMORY = 8;
9402
+ MAX_SEGMENT_CHARS = 500;
9403
+ MIN_SEGMENT_CHARS = 20;
8604
9404
  }
8605
9405
  });
8606
9406