@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
 
@@ -2615,6 +2870,13 @@ async function ensureSchema() {
2615
2870
  } catch (e) {
2616
2871
  logCatchDebug("migration", e);
2617
2872
  }
2873
+ for (const col of ["created_by_agent TEXT", "created_by_device TEXT", "source_session_id TEXT"]) {
2874
+ try {
2875
+ await client.execute({ sql: `ALTER TABLE behaviors ADD COLUMN ${col}`, args: [] });
2876
+ } catch (e) {
2877
+ logCatchDebug("migration", e);
2878
+ }
2879
+ }
2618
2880
  try {
2619
2881
  await client.execute({
2620
2882
  sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
@@ -3831,6 +4093,22 @@ async function ensureSchema() {
3831
4093
  } catch (e) {
3832
4094
  logCatchDebug("migration", e);
3833
4095
  }
4096
+ try {
4097
+ await client.execute({
4098
+ sql: `ALTER TABLE memories ADD COLUMN visibility TEXT DEFAULT 'private'`,
4099
+ args: []
4100
+ });
4101
+ } catch (e) {
4102
+ logCatchDebug("migration", e);
4103
+ }
4104
+ try {
4105
+ await client.execute({
4106
+ sql: `ALTER TABLE memories ADD COLUMN strength REAL DEFAULT 1.0`,
4107
+ args: []
4108
+ });
4109
+ } catch (e) {
4110
+ logCatchDebug("migration", e);
4111
+ }
3834
4112
  }
3835
4113
  async function disposeDatabase() {
3836
4114
  if (_walCheckpointTimer) {
@@ -3882,6 +4160,23 @@ var init_database = __esm({
3882
4160
  });
3883
4161
 
3884
4162
  // src/lib/license.ts
4163
+ var license_exports = {};
4164
+ __export(license_exports, {
4165
+ LICENSE_PUBLIC_KEY_PEM: () => LICENSE_PUBLIC_KEY_PEM,
4166
+ PLAN_LIMITS: () => PLAN_LIMITS,
4167
+ assertVpsLicense: () => assertVpsLicense,
4168
+ checkLicense: () => checkLicense,
4169
+ getCachedLicense: () => getCachedLicense,
4170
+ isFeatureAllowed: () => isFeatureAllowed,
4171
+ loadDeviceId: () => loadDeviceId,
4172
+ loadLicense: () => loadLicense,
4173
+ mirrorLicenseKey: () => mirrorLicenseKey,
4174
+ readCachedLicenseToken: () => readCachedLicenseToken,
4175
+ saveLicense: () => saveLicense,
4176
+ startLicenseRevalidation: () => startLicenseRevalidation,
4177
+ stopLicenseRevalidation: () => stopLicenseRevalidation,
4178
+ validateLicense: () => validateLicense
4179
+ });
3885
4180
  import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync10, mkdirSync as mkdirSync5 } from "fs";
3886
4181
  import { randomUUID as randomUUID2 } from "crypto";
3887
4182
  import { createRequire as createRequire2 } from "module";
@@ -3889,7 +4184,411 @@ import { pathToFileURL as pathToFileURL2 } from "url";
3889
4184
  import os7 from "os";
3890
4185
  import path9 from "path";
3891
4186
  import { jwtVerify, importSPKI } from "jose";
3892
- var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, PLAN_LIMITS;
4187
+ async function fetchRetry(url, init) {
4188
+ try {
4189
+ return await fetch(url, init);
4190
+ } catch {
4191
+ await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
4192
+ return fetch(url, { ...init, signal: AbortSignal.timeout(1e4) });
4193
+ }
4194
+ }
4195
+ function loadDeviceId() {
4196
+ const deviceJsonPath = path9.join(EXE_AI_DIR, "device.json");
4197
+ try {
4198
+ if (existsSync10(deviceJsonPath)) {
4199
+ const data = JSON.parse(readFileSync8(deviceJsonPath, "utf8"));
4200
+ if (data.deviceId) return data.deviceId;
4201
+ }
4202
+ } catch {
4203
+ }
4204
+ try {
4205
+ if (existsSync10(DEVICE_ID_PATH)) {
4206
+ const id2 = readFileSync8(DEVICE_ID_PATH, "utf8").trim();
4207
+ if (id2) return id2;
4208
+ }
4209
+ } catch {
4210
+ }
4211
+ const id = randomUUID2();
4212
+ mkdirSync5(EXE_AI_DIR, { recursive: true });
4213
+ writeFileSync6(DEVICE_ID_PATH, id, "utf8");
4214
+ return id;
4215
+ }
4216
+ function loadLicense() {
4217
+ try {
4218
+ if (!existsSync10(LICENSE_PATH)) return null;
4219
+ return readFileSync8(LICENSE_PATH, "utf8").trim();
4220
+ } catch {
4221
+ return null;
4222
+ }
4223
+ }
4224
+ function saveLicense(apiKey) {
4225
+ mkdirSync5(EXE_AI_DIR, { recursive: true });
4226
+ writeFileSync6(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
4227
+ }
4228
+ async function verifyLicenseJwt(token) {
4229
+ try {
4230
+ const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
4231
+ const { payload } = await jwtVerify(token, key, {
4232
+ algorithms: [LICENSE_JWT_ALG]
4233
+ });
4234
+ const plan = payload.plan ?? "free";
4235
+ const email = payload.sub ?? "";
4236
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
4237
+ return {
4238
+ valid: true,
4239
+ plan,
4240
+ email,
4241
+ expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
4242
+ deviceLimit: limits.devices,
4243
+ employeeLimit: limits.employees,
4244
+ memoryLimit: limits.memories
4245
+ };
4246
+ } catch {
4247
+ return null;
4248
+ }
4249
+ }
4250
+ async function getCachedLicense() {
4251
+ try {
4252
+ if (!existsSync10(CACHE_PATH)) return null;
4253
+ const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
4254
+ if (!raw.token || typeof raw.token !== "string") return null;
4255
+ return await verifyLicenseJwt(raw.token);
4256
+ } catch {
4257
+ return null;
4258
+ }
4259
+ }
4260
+ function readCachedLicenseToken() {
4261
+ try {
4262
+ if (!existsSync10(CACHE_PATH)) return null;
4263
+ const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
4264
+ return typeof raw.token === "string" ? raw.token : null;
4265
+ } catch {
4266
+ return null;
4267
+ }
4268
+ }
4269
+ function getRawCachedPlan() {
4270
+ try {
4271
+ const token = readCachedLicenseToken();
4272
+ if (!token) return null;
4273
+ const parts = token.split(".");
4274
+ if (parts.length !== 3) return null;
4275
+ const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
4276
+ const plan = payload.plan ?? "free";
4277
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
4278
+ process.stderr.write(
4279
+ `[license] WARN: using unverified cached plan (API unreachable, JWT expired). Plan: ${plan}
4280
+ `
4281
+ );
4282
+ return {
4283
+ valid: true,
4284
+ plan,
4285
+ email: payload.sub ?? "",
4286
+ expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
4287
+ deviceLimit: limits.devices,
4288
+ employeeLimit: limits.employees,
4289
+ memoryLimit: limits.memories
4290
+ };
4291
+ } catch {
4292
+ return null;
4293
+ }
4294
+ }
4295
+ function cacheResponse(token) {
4296
+ try {
4297
+ writeFileSync6(CACHE_PATH, JSON.stringify({ token }), "utf8");
4298
+ } catch {
4299
+ }
4300
+ }
4301
+ function loadPrismaForLicense() {
4302
+ if (_prismaFailed) return null;
4303
+ const dbUrl = process.env.DATABASE_URL;
4304
+ if (!dbUrl) {
4305
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path9.join(os7.homedir(), "exe-db");
4306
+ if (!existsSync10(path9.join(exeDbRoot, "package.json"))) {
4307
+ _prismaFailed = true;
4308
+ return null;
4309
+ }
4310
+ }
4311
+ if (!_prismaPromise) {
4312
+ _prismaPromise = (async () => {
4313
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
4314
+ if (explicitPath) {
4315
+ const mod2 = await import(pathToFileURL2(explicitPath).href);
4316
+ const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
4317
+ if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
4318
+ return new Ctor2();
4319
+ }
4320
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path9.join(os7.homedir(), "exe-db");
4321
+ const req = createRequire2(path9.join(exeDbRoot, "package.json"));
4322
+ const entry = req.resolve("@prisma/client");
4323
+ const mod = await import(pathToFileURL2(entry).href);
4324
+ const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
4325
+ if (!Ctor) throw new Error(`No PrismaClient in ${entry}`);
4326
+ return new Ctor();
4327
+ })().catch((err) => {
4328
+ _prismaFailed = true;
4329
+ _prismaPromise = null;
4330
+ throw err;
4331
+ });
4332
+ }
4333
+ return _prismaPromise;
4334
+ }
4335
+ async function validateViaPostgres(apiKey) {
4336
+ const loader = loadPrismaForLicense();
4337
+ if (!loader) return null;
4338
+ try {
4339
+ const prisma = await loader;
4340
+ const rows = await prisma.$queryRawUnsafe(
4341
+ `SELECT plan, email, status, device_limit, employee_limit, memory_limit, expires_at
4342
+ FROM billing.licenses WHERE key = $1 LIMIT 1`,
4343
+ apiKey
4344
+ );
4345
+ if (!rows || rows.length === 0) return null;
4346
+ const row = rows[0];
4347
+ if (row.status !== "active") return null;
4348
+ if (row.expires_at && new Date(row.expires_at) < /* @__PURE__ */ new Date()) return null;
4349
+ const plan = row.plan;
4350
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
4351
+ return {
4352
+ valid: true,
4353
+ plan,
4354
+ email: row.email,
4355
+ expiresAt: row.expires_at ? new Date(row.expires_at).toISOString() : null,
4356
+ deviceLimit: row.device_limit ?? limits.devices,
4357
+ employeeLimit: row.employee_limit ?? limits.employees,
4358
+ memoryLimit: row.memory_limit ?? limits.memories
4359
+ };
4360
+ } catch {
4361
+ return null;
4362
+ }
4363
+ }
4364
+ async function validateViaCFWorker(apiKey, deviceId) {
4365
+ try {
4366
+ const res = await fetchRetry(`${API_BASE}/auth/activate`, {
4367
+ method: "POST",
4368
+ headers: { "Content-Type": "application/json" },
4369
+ body: JSON.stringify({ apiKey, deviceId }),
4370
+ signal: AbortSignal.timeout(1e4)
4371
+ });
4372
+ if (!res.ok) return null;
4373
+ const data = await res.json();
4374
+ if (data.error === "device_limit_exceeded") return null;
4375
+ if (!data.valid) return null;
4376
+ if (data.token) {
4377
+ cacheResponse(data.token);
4378
+ const verified = await verifyLicenseJwt(data.token);
4379
+ if (verified) return verified;
4380
+ }
4381
+ const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
4382
+ return {
4383
+ valid: data.valid,
4384
+ plan: data.plan,
4385
+ email: data.email,
4386
+ expiresAt: data.expiresAt,
4387
+ deviceLimit: limits.devices,
4388
+ employeeLimit: limits.employees,
4389
+ memoryLimit: limits.memories
4390
+ };
4391
+ } catch {
4392
+ return null;
4393
+ }
4394
+ }
4395
+ async function validateLicense(apiKey, deviceId) {
4396
+ const did = deviceId ?? loadDeviceId();
4397
+ const pgResult = await validateViaPostgres(apiKey);
4398
+ if (pgResult) {
4399
+ try {
4400
+ writeFileSync6(CACHE_PATH, JSON.stringify({ pgLicense: pgResult, ts: Date.now() }), "utf8");
4401
+ } catch {
4402
+ }
4403
+ return pgResult;
4404
+ }
4405
+ const cfResult = await validateViaCFWorker(apiKey, did);
4406
+ if (cfResult) return cfResult;
4407
+ const cached = await getCachedLicense();
4408
+ if (cached) return cached;
4409
+ try {
4410
+ if (existsSync10(CACHE_PATH)) {
4411
+ const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
4412
+ if (raw.pgLicense && raw.ts && Date.now() - raw.ts < 7 * 24 * 60 * 60 * 1e3) {
4413
+ return raw.pgLicense;
4414
+ }
4415
+ }
4416
+ } catch {
4417
+ }
4418
+ const rawFallback = getRawCachedPlan();
4419
+ if (rawFallback) return rawFallback;
4420
+ return { ...FREE_LICENSE, valid: false };
4421
+ }
4422
+ function getCacheAgeMs() {
4423
+ try {
4424
+ const { statSync: statSync5 } = __require("fs");
4425
+ const s = statSync5(CACHE_PATH);
4426
+ return Date.now() - s.mtimeMs;
4427
+ } catch {
4428
+ return Infinity;
4429
+ }
4430
+ }
4431
+ async function checkLicense() {
4432
+ let key = loadLicense();
4433
+ if (!key) {
4434
+ try {
4435
+ const configPath = path9.join(EXE_AI_DIR, "config.json");
4436
+ if (existsSync10(configPath)) {
4437
+ const raw = JSON.parse(readFileSync8(configPath, "utf8"));
4438
+ const cloud = raw.cloud;
4439
+ if (cloud?.apiKey) {
4440
+ key = cloud.apiKey;
4441
+ saveLicense(key);
4442
+ }
4443
+ }
4444
+ } catch {
4445
+ }
4446
+ }
4447
+ if (!key) return FREE_LICENSE;
4448
+ const cached = await getCachedLicense();
4449
+ if (cached && getCacheAgeMs() < CACHE_MAX_AGE_MS) return cached;
4450
+ const deviceId = loadDeviceId();
4451
+ return validateLicense(key, deviceId);
4452
+ }
4453
+ function isFeatureAllowed(license, feature) {
4454
+ switch (feature) {
4455
+ case "cloud_sync":
4456
+ case "external_agents":
4457
+ case "wiki":
4458
+ return license.plan !== "free";
4459
+ case "unlimited_employees":
4460
+ return license.plan === "team" || license.plan === "agency" || license.plan === "enterprise";
4461
+ }
4462
+ }
4463
+ function mirrorLicenseKey(apiKey) {
4464
+ const trimmed = apiKey.trim();
4465
+ if (!trimmed) return;
4466
+ saveLicense(trimmed);
4467
+ }
4468
+ async function assertVpsLicense(opts) {
4469
+ const env = opts?.env ?? process.env;
4470
+ const inProduction = env.NODE_ENV === "production";
4471
+ if (!opts?.force && !inProduction) {
4472
+ return { ...FREE_LICENSE, plan: "free" };
4473
+ }
4474
+ const envKey = env.EXE_LICENSE_KEY?.trim();
4475
+ if (envKey) {
4476
+ saveLicense(envKey);
4477
+ }
4478
+ const apiKey = envKey ?? loadLicense();
4479
+ if (!apiKey) {
4480
+ throw new Error(
4481
+ "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."
4482
+ );
4483
+ }
4484
+ const deviceId = loadDeviceId();
4485
+ let backendResponse = null;
4486
+ let explicitRejection = false;
4487
+ let transientFailure = false;
4488
+ try {
4489
+ const res = await fetchRetry(`${API_BASE}/auth/activate`, {
4490
+ method: "POST",
4491
+ headers: { "Content-Type": "application/json" },
4492
+ body: JSON.stringify({ apiKey, deviceId }),
4493
+ signal: AbortSignal.timeout(1e4)
4494
+ });
4495
+ if (res.ok) {
4496
+ backendResponse = await res.json();
4497
+ if (!backendResponse.valid) explicitRejection = true;
4498
+ } else if (res.status === 401 || res.status === 403) {
4499
+ explicitRejection = true;
4500
+ } else {
4501
+ transientFailure = true;
4502
+ }
4503
+ } catch {
4504
+ transientFailure = true;
4505
+ }
4506
+ if (backendResponse?.valid) {
4507
+ if (backendResponse.token) {
4508
+ cacheResponse(backendResponse.token);
4509
+ const verified = await verifyLicenseJwt(backendResponse.token);
4510
+ if (verified) return verified;
4511
+ }
4512
+ const limits = PLAN_LIMITS[backendResponse.plan] ?? PLAN_LIMITS.free;
4513
+ return {
4514
+ valid: true,
4515
+ plan: backendResponse.plan,
4516
+ email: backendResponse.email,
4517
+ expiresAt: backendResponse.expiresAt,
4518
+ deviceLimit: limits.devices,
4519
+ employeeLimit: limits.employees,
4520
+ memoryLimit: limits.memories
4521
+ };
4522
+ }
4523
+ if (explicitRejection) {
4524
+ throw new Error(
4525
+ `License invalid or expired. Renew at https://askexe.com, then restart. Backend rejected key ending in ...${apiKey.slice(-4)}.`
4526
+ );
4527
+ }
4528
+ if (!transientFailure) {
4529
+ throw new Error(
4530
+ "License validation failed: unknown backend state. Restore network connectivity to https://cloud.askexe.com and retry."
4531
+ );
4532
+ }
4533
+ const fresh = await getCachedLicense();
4534
+ if (fresh && fresh.valid) return fresh;
4535
+ const graceDays = opts?.offlineGraceDays ?? 7;
4536
+ const graceMs = graceDays * 24 * 60 * 60 * 1e3;
4537
+ try {
4538
+ const token = readCachedLicenseToken();
4539
+ if (token) {
4540
+ const payloadB64 = token.split(".")[1];
4541
+ if (payloadB64) {
4542
+ const payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString());
4543
+ const expMs = (payload.exp ?? 0) * 1e3;
4544
+ if (Date.now() < expMs + graceMs) {
4545
+ const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
4546
+ const { payload: verified } = await jwtVerify(token, key, {
4547
+ algorithms: [LICENSE_JWT_ALG],
4548
+ clockTolerance: graceDays * 24 * 60 * 60
4549
+ });
4550
+ const plan = verified.plan ?? "free";
4551
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
4552
+ return {
4553
+ valid: true,
4554
+ plan,
4555
+ email: verified.sub ?? "",
4556
+ expiresAt: verified.exp ? new Date(verified.exp * 1e3).toISOString() : null,
4557
+ deviceLimit: limits.devices,
4558
+ employeeLimit: limits.employees,
4559
+ memoryLimit: limits.memories
4560
+ };
4561
+ }
4562
+ }
4563
+ }
4564
+ } catch {
4565
+ }
4566
+ throw new Error(
4567
+ `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.`
4568
+ );
4569
+ }
4570
+ function startLicenseRevalidation(intervalMs = 36e5) {
4571
+ if (_revalTimer) return;
4572
+ _revalTimer = setInterval(async () => {
4573
+ try {
4574
+ const license = await checkLicense();
4575
+ if (!license.valid) {
4576
+ process.stderr.write("[exe-os] License expired or invalid \u2014 features may be restricted\n");
4577
+ }
4578
+ } catch {
4579
+ }
4580
+ }, intervalMs);
4581
+ if (_revalTimer && typeof _revalTimer === "object" && "unref" in _revalTimer) {
4582
+ _revalTimer.unref();
4583
+ }
4584
+ }
4585
+ function stopLicenseRevalidation() {
4586
+ if (_revalTimer) {
4587
+ clearInterval(_revalTimer);
4588
+ _revalTimer = null;
4589
+ }
4590
+ }
4591
+ 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;
3893
4592
  var init_license = __esm({
3894
4593
  "src/lib/license.ts"() {
3895
4594
  "use strict";
@@ -3897,7 +4596,13 @@ var init_license = __esm({
3897
4596
  LICENSE_PATH = path9.join(EXE_AI_DIR, "license.key");
3898
4597
  CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
3899
4598
  DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
3900
- API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com/cloud";
4599
+ API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://cloud.askexe.com";
4600
+ RETRY_DELAY_MS = 500;
4601
+ LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
4602
+ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
4603
+ 4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
4604
+ -----END PUBLIC KEY-----`;
4605
+ LICENSE_JWT_ALG = "ES256";
3901
4606
  PLAN_LIMITS = {
3902
4607
  free: { devices: 1, employees: 1, memories: 5e3 },
3903
4608
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -3905,6 +4610,19 @@ var init_license = __esm({
3905
4610
  agency: { devices: 50, employees: 100, memories: 1e7 },
3906
4611
  enterprise: { devices: -1, employees: -1, memories: -1 }
3907
4612
  };
4613
+ FREE_LICENSE = {
4614
+ valid: true,
4615
+ plan: "free",
4616
+ email: "",
4617
+ expiresAt: null,
4618
+ deviceLimit: 1,
4619
+ employeeLimit: 1,
4620
+ memoryLimit: 5e3
4621
+ };
4622
+ _prismaPromise = null;
4623
+ _prismaFailed = false;
4624
+ CACHE_MAX_AGE_MS = 36e5;
4625
+ _revalTimer = null;
3908
4626
  }
3909
4627
  });
3910
4628
 
@@ -4416,6 +5134,19 @@ async function resolveTask(client, identifier, scopeSession) {
4416
5134
  args: [identifier, ...scope.args]
4417
5135
  });
4418
5136
  if (result2.rows.length === 1) return result2.rows[0];
5137
+ if (/^[a-f0-9]{7,12}$/i.test(identifier)) {
5138
+ result2 = await client.execute({
5139
+ sql: `SELECT * FROM tasks WHERE id LIKE ?`,
5140
+ args: [`${identifier}%`]
5141
+ });
5142
+ if (result2.rows.length === 1) return result2.rows[0];
5143
+ if (result2.rows.length > 1) {
5144
+ const matches = result2.rows.map((r) => `${String(r.id)} "${String(r.title)}" (${String(r.status)})`).join(", ");
5145
+ throw new Error(
5146
+ `Multiple tasks match short-ID "${identifier}": ${matches}. Use a longer prefix to disambiguate.`
5147
+ );
5148
+ }
5149
+ }
4419
5150
  result2 = await client.execute({
4420
5151
  sql: `SELECT * FROM tasks WHERE task_file LIKE ?${scope.sql}`,
4421
5152
  args: [`%${identifier}%`, ...scope.args]
@@ -5270,12 +6001,13 @@ async function cascadeUnblock(taskId, baseDir, now) {
5270
6001
  WHERE blocked_by = ? AND status = 'blocked'`,
5271
6002
  args: [now, taskId]
5272
6003
  });
5273
- if (baseDir && unblocked.rowsAffected > 0) {
5274
- const ubScope = sessionScopeFilter();
5275
- const unblockedRows = await client.execute({
5276
- sql: `SELECT task_file FROM tasks WHERE blocked_by IS NULL AND updated_at = ?${ubScope.sql}`,
5277
- args: [now, ...ubScope.args]
5278
- });
6004
+ if (unblocked.rowsAffected === 0) return;
6005
+ const ubScope = sessionScopeFilter();
6006
+ const unblockedRows = await client.execute({
6007
+ sql: `SELECT id, title, assigned_to, priority, task_file FROM tasks WHERE blocked_by IS NULL AND updated_at = ?${ubScope.sql}`,
6008
+ args: [now, ...ubScope.args]
6009
+ });
6010
+ if (baseDir) {
5279
6011
  for (const ur of unblockedRows.rows) {
5280
6012
  try {
5281
6013
  const ubFile = path16.join(baseDir, String(ur.task_file));
@@ -5287,6 +6019,19 @@ async function cascadeUnblock(taskId, baseDir, now) {
5287
6019
  }
5288
6020
  }
5289
6021
  }
6022
+ if (unblockedRows.rows.length > 0 && !process.env.VITEST) {
6023
+ try {
6024
+ const { queueIntercom: queueIntercom2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
6025
+ const dispatched = /* @__PURE__ */ new Set();
6026
+ for (const ur of unblockedRows.rows) {
6027
+ const assignee = String(ur.assigned_to);
6028
+ if (dispatched.has(assignee)) continue;
6029
+ dispatched.add(assignee);
6030
+ queueIntercom2(`${assignee}`, `unblocked: "${String(ur.title)}" is now ready`);
6031
+ }
6032
+ } catch {
6033
+ }
6034
+ }
5290
6035
  }
5291
6036
  async function findNextTask(assignedTo) {
5292
6037
  const client = getClient();
@@ -5496,6 +6241,15 @@ var init_embedder = __esm({
5496
6241
  // src/lib/behaviors.ts
5497
6242
  import crypto5 from "crypto";
5498
6243
  async function storeBehavior(opts) {
6244
+ try {
6245
+ const { loadEmployeesSync: loadEmployeesSync2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
6246
+ const roster = loadEmployeesSync2();
6247
+ if (roster.length > 0 && !roster.some((e) => e.name === opts.agentId)) {
6248
+ throw new Error(`Agent "${opts.agentId}" not found in roster. Cannot store behavior for unregistered agent.`);
6249
+ }
6250
+ } catch (e) {
6251
+ if (e instanceof Error && e.message.includes("not found in roster")) throw e;
6252
+ }
5499
6253
  const client = getClient();
5500
6254
  const id = crypto5.randomUUID();
5501
6255
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -5506,10 +6260,18 @@ async function storeBehavior(opts) {
5506
6260
  vector = new Float32Array(vec);
5507
6261
  } catch {
5508
6262
  }
6263
+ let createdByDevice = null;
6264
+ try {
6265
+ const { loadDeviceId: loadDeviceId2 } = await Promise.resolve().then(() => (init_license(), license_exports));
6266
+ createdByDevice = loadDeviceId2() ?? null;
6267
+ } catch {
6268
+ }
6269
+ const createdByAgent = process.env.AGENT_ID ?? null;
6270
+ const sourceSessionId = process.env.CLAUDE_SESSION_ID ?? process.env.SESSION_ID ?? null;
5509
6271
  await client.execute({
5510
- sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at, vector)
5511
- VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?)`,
5512
- args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now, vector ? vector.buffer : null]
6272
+ 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)
6273
+ VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?)`,
6274
+ args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now, vector ? vector.buffer : null, createdByAgent, createdByDevice, sourceSessionId]
5513
6275
  });
5514
6276
  return id;
5515
6277
  }
@@ -5941,6 +6703,12 @@ async function updateTask(input) {
5941
6703
  }
5942
6704
  }
5943
6705
  }
6706
+ if (input.status === "cancelled") {
6707
+ try {
6708
+ await cascadeUnblock(taskId, input.baseDir, now);
6709
+ } catch {
6710
+ }
6711
+ }
5944
6712
  if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
5945
6713
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
5946
6714
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
@@ -6472,11 +7240,12 @@ function getDispatchedBy(sessionKey) {
6472
7240
  }
6473
7241
  }
6474
7242
  function resolveExeSession() {
7243
+ if (process.env.EXE_SESSION_NAME) {
7244
+ const fromEnv = extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
7245
+ if (fromEnv) return fromEnv;
7246
+ }
6475
7247
  const mySession = getMySession();
6476
7248
  if (!mySession) {
6477
- if (process.env.EXE_SESSION_NAME) {
6478
- return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
6479
- }
6480
7249
  return null;
6481
7250
  }
6482
7251
  const fromSessionName = extractRootExe(mySession);
@@ -6491,6 +7260,10 @@ function resolveExeSession() {
6491
7260
  `[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
6492
7261
  `
6493
7262
  );
7263
+ try {
7264
+ registerParentExe(key, fromSessionName);
7265
+ } catch {
7266
+ }
6494
7267
  candidate = fromSessionName;
6495
7268
  } else {
6496
7269
  candidate = fromCache;
@@ -8185,11 +8958,17 @@ var init_platform_procedures = __esm({
8185
8958
  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."
8186
8959
  },
8187
8960
  {
8188
- title: "Customer orchestration maturity \u2014 recommend, never trap",
8961
+ title: "Orchestration phase guidance \u2014 recommend, never trap",
8189
8962
  domain: "workflow",
8190
8963
  priority: "p1",
8191
8964
  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."
8192
8965
  },
8966
+ {
8967
+ title: "Routing slot vs display title \u2014 internal 'coo' is plumbing, not your name",
8968
+ domain: "identity",
8969
+ priority: "p0",
8970
+ 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."
8971
+ },
8193
8972
  {
8194
8973
  title: "Single dispatch path \u2014 create_task only",
8195
8974
  domain: "workflow",
@@ -8223,6 +9002,12 @@ var init_platform_procedures = __esm({
8223
9002
  priority: "p0",
8224
9003
  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."
8225
9004
  },
9005
+ {
9006
+ title: "Destructive operations \u2014 mandatory reviewer gate",
9007
+ domain: "security",
9008
+ priority: "p0",
9009
+ 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."
9010
+ },
8226
9011
  {
8227
9012
  title: "Customer patch triage \u2014 upstream bug vs customization",
8228
9013
  domain: "support",
@@ -8374,7 +9159,7 @@ var init_platform_procedures = __esm({
8374
9159
  title: "MCP tool dispatch \u2014 all tools use action parameter",
8375
9160
  domain: "tool-use",
8376
9161
  priority: "p0",
8377
- 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.'
9162
+ 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.'
8378
9163
  },
8379
9164
  {
8380
9165
  title: "MCP tools \u2014 memory, decision, and search",
@@ -8508,10 +9293,24 @@ function stableId(memoryId, type, content) {
8508
9293
  return createHash2("sha256").update(`${memoryId}:${type}:${content}`).digest("hex").slice(0, 32);
8509
9294
  }
8510
9295
  function cleanText(text) {
8511
- return text.replace(/```[\s\S]*?```/g, " ").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
9296
+ let cleaned = text.replace(
9297
+ /```(\w*)\n(.*?)(?:\n[\s\S]*?)```/g,
9298
+ (_m, lang, firstLine) => `[code${lang ? `:${lang}` : ""}] ${firstLine.trim()}`
9299
+ );
9300
+ cleaned = cleaned.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
9301
+ return cleaned;
8512
9302
  }
8513
- function splitSentences(text) {
8514
- return cleanText(text).split(/(?<=[.!?])\s+|\n+/).map((s) => s.trim()).filter((s) => s.length >= 24 && s.length <= MAX_SENTENCE_CHARS);
9303
+ function splitSegments(text) {
9304
+ const cleaned = cleanText(text);
9305
+ 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);
9306
+ if (segments.length === 0 && cleaned.length >= MIN_SEGMENT_CHARS) {
9307
+ const lines = cleaned.split(/\n+/).map((l) => l.trim()).filter((l) => l.length >= MIN_SEGMENT_CHARS && l.length <= MAX_SEGMENT_CHARS);
9308
+ if (lines.length > 0) return lines;
9309
+ if (cleaned.length >= MIN_SEGMENT_CHARS) {
9310
+ return [cleaned.slice(0, MAX_SEGMENT_CHARS)];
9311
+ }
9312
+ }
9313
+ return segments;
8515
9314
  }
8516
9315
  function inferCardType(sentence, toolName) {
8517
9316
  const lower = sentence.toLowerCase();
@@ -8543,12 +9342,12 @@ function predicateFor(type) {
8543
9342
  }
8544
9343
  }
8545
9344
  function extractMemoryCards(row) {
8546
- const sentences = splitSentences(row.raw_text);
9345
+ const segments = splitSegments(row.raw_text);
8547
9346
  const cards = [];
8548
- for (const sentence of sentences) {
9347
+ for (const sentence of segments) {
8549
9348
  const type = inferCardType(sentence, row.tool_name);
8550
9349
  const subject = extractSubject(sentence, row.agent_id);
8551
- const content = sentence.length > MAX_SENTENCE_CHARS ? `${sentence.slice(0, MAX_SENTENCE_CHARS - 1)}\u2026` : sentence;
9350
+ const content = sentence.length > MAX_SEGMENT_CHARS ? `${sentence.slice(0, MAX_SEGMENT_CHARS - 1)}\u2026` : sentence;
8552
9351
  cards.push({
8553
9352
  id: stableId(row.id, type, content),
8554
9353
  memory_id: row.id,
@@ -8644,13 +9443,14 @@ Source memory: ${String(row.source_ref ?? row.memory_id)}`,
8644
9443
  last_accessed: String(row.timestamp)
8645
9444
  }));
8646
9445
  }
8647
- var MAX_CARDS_PER_MEMORY, MAX_SENTENCE_CHARS;
9446
+ var MAX_CARDS_PER_MEMORY, MAX_SEGMENT_CHARS, MIN_SEGMENT_CHARS;
8648
9447
  var init_memory_cards = __esm({
8649
9448
  "src/lib/memory-cards.ts"() {
8650
9449
  "use strict";
8651
9450
  init_database();
8652
- MAX_CARDS_PER_MEMORY = 6;
8653
- MAX_SENTENCE_CHARS = 360;
9451
+ MAX_CARDS_PER_MEMORY = 8;
9452
+ MAX_SEGMENT_CHARS = 500;
9453
+ MIN_SEGMENT_CHARS = 20;
8654
9454
  }
8655
9455
  });
8656
9456