@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
@@ -650,6 +650,19 @@ var init_runtime_table = __esm({
650
650
  });
651
651
 
652
652
  // src/lib/agent-config.ts
653
+ var agent_config_exports = {};
654
+ __export(agent_config_exports, {
655
+ AGENT_CONFIG_PATH: () => AGENT_CONFIG_PATH,
656
+ DEFAULT_MODELS: () => DEFAULT_MODELS,
657
+ KNOWN_RUNTIMES: () => KNOWN_RUNTIMES,
658
+ RUNTIME_LABELS: () => RUNTIME_LABELS,
659
+ clearAgentRuntime: () => clearAgentRuntime,
660
+ getAgentRuntime: () => getAgentRuntime,
661
+ loadAgentConfig: () => loadAgentConfig,
662
+ saveAgentConfig: () => saveAgentConfig,
663
+ setAgentMcps: () => setAgentMcps,
664
+ setAgentRuntime: () => setAgentRuntime
665
+ });
653
666
  import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync4 } from "fs";
654
667
  import path3 from "path";
655
668
  function loadAgentConfig() {
@@ -660,6 +673,12 @@ function loadAgentConfig() {
660
673
  return {};
661
674
  }
662
675
  }
676
+ function saveAgentConfig(config) {
677
+ const dir = path3.dirname(AGENT_CONFIG_PATH);
678
+ ensurePrivateDirSync(dir);
679
+ writeFileSync2(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
680
+ enforcePrivateFileSync(AGENT_CONFIG_PATH);
681
+ }
663
682
  function getAgentRuntime(agentId) {
664
683
  const config = loadAgentConfig();
665
684
  const entry = config[agentId];
@@ -668,7 +687,47 @@ function getAgentRuntime(agentId) {
668
687
  if (orgDefault) return orgDefault;
669
688
  return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
670
689
  }
671
- var AGENT_CONFIG_PATH, DEFAULT_MODELS;
690
+ function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
691
+ const knownModels = KNOWN_RUNTIMES[runtime];
692
+ if (!knownModels) {
693
+ return {
694
+ ok: false,
695
+ error: `Unknown runtime "${runtime}". Valid: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`
696
+ };
697
+ }
698
+ if (!knownModels.includes(model)) {
699
+ return {
700
+ ok: false,
701
+ error: `Unknown model "${model}" for runtime "${runtime}". Valid: ${knownModels.join(", ")}`
702
+ };
703
+ }
704
+ const config = loadAgentConfig();
705
+ const existing = config[agentId];
706
+ const entry = { runtime, model };
707
+ if (reasoning_effort) entry.reasoning_effort = reasoning_effort;
708
+ if (mcps !== void 0) {
709
+ entry.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
710
+ } else if (existing?.mcps) {
711
+ entry.mcps = existing.mcps;
712
+ }
713
+ config[agentId] = entry;
714
+ saveAgentConfig(config);
715
+ return { ok: true };
716
+ }
717
+ function setAgentMcps(agentId, mcps) {
718
+ const config = loadAgentConfig();
719
+ const existing = config[agentId] ?? getAgentRuntime(agentId);
720
+ existing.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
721
+ config[agentId] = existing;
722
+ saveAgentConfig(config);
723
+ return { ok: true };
724
+ }
725
+ function clearAgentRuntime(agentId) {
726
+ const config = loadAgentConfig();
727
+ delete config[agentId];
728
+ saveAgentConfig(config);
729
+ }
730
+ var AGENT_CONFIG_PATH, KNOWN_RUNTIMES, RUNTIME_LABELS, DEFAULT_MODELS;
672
731
  var init_agent_config = __esm({
673
732
  "src/lib/agent-config.ts"() {
674
733
  "use strict";
@@ -676,6 +735,16 @@ var init_agent_config = __esm({
676
735
  init_runtime_table();
677
736
  init_secure_files();
678
737
  AGENT_CONFIG_PATH = path3.join(EXE_AI_DIR, "agent-config.json");
738
+ KNOWN_RUNTIMES = {
739
+ claude: ["claude-opus-4.6", "claude-opus-4", "claude-sonnet-4.6", "claude-sonnet-4", "claude-haiku-4.5"],
740
+ codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
741
+ opencode: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4", "google/gemini-2.5-pro", "deepseek/deepseek-r3", "minimax/minimax-m2.5"]
742
+ };
743
+ RUNTIME_LABELS = {
744
+ claude: "Claude Code (Anthropic)",
745
+ codex: "Codex (OpenAI)",
746
+ opencode: "OpenCode (open source)"
747
+ };
679
748
  DEFAULT_MODELS = {
680
749
  claude: "claude-opus-4.6",
681
750
  codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
@@ -818,6 +887,32 @@ var init_db_retry = __esm({
818
887
  });
819
888
 
820
889
  // src/lib/employees.ts
890
+ var employees_exports = {};
891
+ __export(employees_exports, {
892
+ COORDINATOR_ROLE: () => COORDINATOR_ROLE,
893
+ DEFAULT_COORDINATOR_TEMPLATE_NAME: () => DEFAULT_COORDINATOR_TEMPLATE_NAME,
894
+ EMPLOYEES_PATH: () => EMPLOYEES_PATH,
895
+ addEmployee: () => addEmployee,
896
+ baseAgentName: () => baseAgentName,
897
+ canCoordinate: () => canCoordinate,
898
+ getCoordinatorEmployee: () => getCoordinatorEmployee,
899
+ getCoordinatorName: () => getCoordinatorName,
900
+ getEmployee: () => getEmployee,
901
+ getEmployeeByRole: () => getEmployeeByRole,
902
+ getEmployeeNamesByRole: () => getEmployeeNamesByRole,
903
+ hasRole: () => hasRole,
904
+ hireEmployee: () => hireEmployee,
905
+ isCoordinatorName: () => isCoordinatorName,
906
+ isCoordinatorRole: () => isCoordinatorRole,
907
+ isMultiInstance: () => isMultiInstance,
908
+ loadEmployees: () => loadEmployees,
909
+ loadEmployeesSync: () => loadEmployeesSync,
910
+ normalizeRole: () => normalizeRole,
911
+ normalizeRosterCase: () => normalizeRosterCase,
912
+ registerBinSymlinks: () => registerBinSymlinks,
913
+ saveEmployees: () => saveEmployees,
914
+ validateEmployeeName: () => validateEmployeeName
915
+ });
821
916
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
822
917
  import { existsSync as existsSync6, symlinkSync, readlinkSync, readFileSync as readFileSync5, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
823
918
  import { execSync as execSync3 } from "child_process";
@@ -839,6 +934,24 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
839
934
  if (!agentName) return false;
840
935
  return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
841
936
  }
937
+ function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
938
+ return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
939
+ }
940
+ function validateEmployeeName(name) {
941
+ if (!name) {
942
+ return { valid: false, error: "Name is required" };
943
+ }
944
+ if (name.length > 32) {
945
+ return { valid: false, error: "Name must be 32 characters or fewer" };
946
+ }
947
+ if (!/^[a-z][a-z0-9]*$/.test(name)) {
948
+ return {
949
+ valid: false,
950
+ error: "Name must start with a letter and contain only lowercase alphanumeric characters"
951
+ };
952
+ }
953
+ return { valid: true };
954
+ }
842
955
  async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
843
956
  if (!existsSync6(employeesPath)) {
844
957
  return [];
@@ -850,6 +963,10 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
850
963
  return [];
851
964
  }
852
965
  }
966
+ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
967
+ await mkdir2(path5.dirname(employeesPath), { recursive: true });
968
+ await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
969
+ }
853
970
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
854
971
  if (!existsSync6(employeesPath)) return [];
855
972
  try {
@@ -861,6 +978,19 @@ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
861
978
  function getEmployee(employees, name) {
862
979
  return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
863
980
  }
981
+ function getEmployeeByRole(employees, role) {
982
+ const lower = role.toLowerCase();
983
+ return employees.find((e) => e.role.toLowerCase() === lower);
984
+ }
985
+ function getEmployeeNamesByRole(employees, role) {
986
+ const lower = role.toLowerCase();
987
+ return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
988
+ }
989
+ function hasRole(agentName, role) {
990
+ const employees = loadEmployeesSync();
991
+ const emp = getEmployee(employees, agentName);
992
+ return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
993
+ }
864
994
  function baseAgentName(name, employees) {
865
995
  const match = name.match(/^([a-zA-Z]+)\d+$/);
866
996
  if (!match) return name;
@@ -875,7 +1005,131 @@ function isMultiInstance(agentName, employees) {
875
1005
  if (!emp) return false;
876
1006
  return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
877
1007
  }
878
- var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR;
1008
+ function addEmployee(employees, employee) {
1009
+ const { systemPrompt: _legacyPrompt, ...rest } = employee;
1010
+ const normalized = { ...rest, name: employee.name.toLowerCase() };
1011
+ if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
1012
+ throw new Error(`Employee '${normalized.name}' already exists`);
1013
+ }
1014
+ return [...employees, normalized];
1015
+ }
1016
+ function appendToCoordinatorTeam(employee) {
1017
+ const coordinator = getCoordinatorEmployee(loadEmployeesSync());
1018
+ if (!coordinator) return;
1019
+ const idPath = path5.join(IDENTITY_DIR, `${coordinator.name}.md`);
1020
+ if (!existsSync6(idPath)) return;
1021
+ const content = readFileSync5(idPath, "utf-8");
1022
+ if (content.includes(`**${capitalize(employee.name)}`)) return;
1023
+ const teamMatch = content.match(TEAM_SECTION_RE);
1024
+ if (!teamMatch || teamMatch.index === void 0) return;
1025
+ const afterTeam = content.slice(teamMatch.index + teamMatch[0].length);
1026
+ const nextHeading = afterTeam.match(/\n## /);
1027
+ const entry = `
1028
+ **${capitalize(employee.name)} (${employee.role}):** Newly hired. Update this description as the role develops.
1029
+ `;
1030
+ let updated;
1031
+ if (nextHeading && nextHeading.index !== void 0) {
1032
+ const insertAt = teamMatch.index + teamMatch[0].length + nextHeading.index;
1033
+ updated = content.slice(0, insertAt) + entry + content.slice(insertAt);
1034
+ } else {
1035
+ updated = content.trimEnd() + "\n" + entry;
1036
+ }
1037
+ writeFileSync4(idPath, updated, "utf-8");
1038
+ }
1039
+ function capitalize(s) {
1040
+ return s.charAt(0).toUpperCase() + s.slice(1);
1041
+ }
1042
+ async function hireEmployee(employee) {
1043
+ const employees = await loadEmployees();
1044
+ const updated = addEmployee(employees, employee);
1045
+ await saveEmployees(updated);
1046
+ try {
1047
+ appendToCoordinatorTeam(employee);
1048
+ } catch {
1049
+ }
1050
+ try {
1051
+ const { loadAgentConfig: loadAgentConfig2, saveAgentConfig: saveAgentConfig2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
1052
+ const config = loadAgentConfig2();
1053
+ const name = employee.name.toLowerCase();
1054
+ if (!config[name] && config["default"]) {
1055
+ config[name] = { ...config["default"] };
1056
+ saveAgentConfig2(config);
1057
+ }
1058
+ } catch {
1059
+ }
1060
+ return updated;
1061
+ }
1062
+ async function normalizeRosterCase(rosterPath) {
1063
+ const employees = await loadEmployees(rosterPath);
1064
+ let changed = false;
1065
+ for (const emp of employees) {
1066
+ if (emp.name !== emp.name.toLowerCase()) {
1067
+ const oldName = emp.name;
1068
+ emp.name = emp.name.toLowerCase();
1069
+ changed = true;
1070
+ try {
1071
+ const identityDir = path5.join(os4.homedir(), ".exe-os", "identity");
1072
+ const oldPath = path5.join(identityDir, `${oldName}.md`);
1073
+ const newPath = path5.join(identityDir, `${emp.name}.md`);
1074
+ if (existsSync6(oldPath) && !existsSync6(newPath)) {
1075
+ renameSync3(oldPath, newPath);
1076
+ } else if (existsSync6(oldPath) && oldPath !== newPath) {
1077
+ const content = readFileSync5(oldPath, "utf-8");
1078
+ writeFileSync4(newPath, content, "utf-8");
1079
+ if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
1080
+ unlinkSync(oldPath);
1081
+ }
1082
+ }
1083
+ } catch {
1084
+ }
1085
+ }
1086
+ }
1087
+ if (changed) {
1088
+ await saveEmployees(employees, rosterPath);
1089
+ }
1090
+ return changed;
1091
+ }
1092
+ function findExeBin() {
1093
+ try {
1094
+ return execSync3(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
1095
+ } catch {
1096
+ return null;
1097
+ }
1098
+ }
1099
+ function registerBinSymlinks(name) {
1100
+ const created = [];
1101
+ const skipped = [];
1102
+ const errors = [];
1103
+ const exeBinPath = findExeBin();
1104
+ if (!exeBinPath) {
1105
+ errors.push("Could not find 'exe-os' in PATH");
1106
+ return { created, skipped, errors };
1107
+ }
1108
+ const binDir = path5.dirname(exeBinPath);
1109
+ let target;
1110
+ try {
1111
+ target = readlinkSync(exeBinPath);
1112
+ } catch {
1113
+ errors.push("Could not read 'exe' symlink");
1114
+ return { created, skipped, errors };
1115
+ }
1116
+ for (const suffix of ["", "-opencode"]) {
1117
+ const linkName = `${name}${suffix}`;
1118
+ const linkPath = path5.join(binDir, linkName);
1119
+ if (existsSync6(linkPath)) {
1120
+ skipped.push(linkName);
1121
+ continue;
1122
+ }
1123
+ try {
1124
+ symlinkSync(target, linkPath);
1125
+ created.push(linkName);
1126
+ } catch (err) {
1127
+ errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
1128
+ }
1129
+ }
1130
+ return { created, skipped, errors };
1131
+ }
1132
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR, TEAM_SECTION_RE;
879
1133
  var init_employees = __esm({
880
1134
  "src/lib/employees.ts"() {
881
1135
  "use strict";
@@ -885,6 +1139,7 @@ var init_employees = __esm({
885
1139
  COORDINATOR_ROLE = "COO";
886
1140
  MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
887
1141
  IDENTITY_DIR = path5.join(EXE_AI_DIR, "identity");
1142
+ TEAM_SECTION_RE = /^## Team\b.*$/m;
888
1143
  }
889
1144
  });
890
1145
 
@@ -972,6 +1227,23 @@ var init_database = __esm({
972
1227
  });
973
1228
 
974
1229
  // src/lib/license.ts
1230
+ var license_exports = {};
1231
+ __export(license_exports, {
1232
+ LICENSE_PUBLIC_KEY_PEM: () => LICENSE_PUBLIC_KEY_PEM,
1233
+ PLAN_LIMITS: () => PLAN_LIMITS,
1234
+ assertVpsLicense: () => assertVpsLicense,
1235
+ checkLicense: () => checkLicense,
1236
+ getCachedLicense: () => getCachedLicense,
1237
+ isFeatureAllowed: () => isFeatureAllowed,
1238
+ loadDeviceId: () => loadDeviceId,
1239
+ loadLicense: () => loadLicense,
1240
+ mirrorLicenseKey: () => mirrorLicenseKey,
1241
+ readCachedLicenseToken: () => readCachedLicenseToken,
1242
+ saveLicense: () => saveLicense,
1243
+ startLicenseRevalidation: () => startLicenseRevalidation,
1244
+ stopLicenseRevalidation: () => stopLicenseRevalidation,
1245
+ validateLicense: () => validateLicense
1246
+ });
975
1247
  import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync8, mkdirSync as mkdirSync5 } from "fs";
976
1248
  import { randomUUID } from "crypto";
977
1249
  import { createRequire as createRequire2 } from "module";
@@ -979,7 +1251,411 @@ import { pathToFileURL as pathToFileURL2 } from "url";
979
1251
  import os6 from "os";
980
1252
  import path7 from "path";
981
1253
  import { jwtVerify, importSPKI } from "jose";
982
- var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, PLAN_LIMITS;
1254
+ async function fetchRetry(url, init) {
1255
+ try {
1256
+ return await fetch(url, init);
1257
+ } catch {
1258
+ await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
1259
+ return fetch(url, { ...init, signal: AbortSignal.timeout(1e4) });
1260
+ }
1261
+ }
1262
+ function loadDeviceId() {
1263
+ const deviceJsonPath = path7.join(EXE_AI_DIR, "device.json");
1264
+ try {
1265
+ if (existsSync8(deviceJsonPath)) {
1266
+ const data = JSON.parse(readFileSync6(deviceJsonPath, "utf8"));
1267
+ if (data.deviceId) return data.deviceId;
1268
+ }
1269
+ } catch {
1270
+ }
1271
+ try {
1272
+ if (existsSync8(DEVICE_ID_PATH)) {
1273
+ const id2 = readFileSync6(DEVICE_ID_PATH, "utf8").trim();
1274
+ if (id2) return id2;
1275
+ }
1276
+ } catch {
1277
+ }
1278
+ const id = randomUUID();
1279
+ mkdirSync5(EXE_AI_DIR, { recursive: true });
1280
+ writeFileSync5(DEVICE_ID_PATH, id, "utf8");
1281
+ return id;
1282
+ }
1283
+ function loadLicense() {
1284
+ try {
1285
+ if (!existsSync8(LICENSE_PATH)) return null;
1286
+ return readFileSync6(LICENSE_PATH, "utf8").trim();
1287
+ } catch {
1288
+ return null;
1289
+ }
1290
+ }
1291
+ function saveLicense(apiKey) {
1292
+ mkdirSync5(EXE_AI_DIR, { recursive: true });
1293
+ writeFileSync5(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
1294
+ }
1295
+ async function verifyLicenseJwt(token) {
1296
+ try {
1297
+ const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
1298
+ const { payload } = await jwtVerify(token, key, {
1299
+ algorithms: [LICENSE_JWT_ALG]
1300
+ });
1301
+ const plan = payload.plan ?? "free";
1302
+ const email = payload.sub ?? "";
1303
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
1304
+ return {
1305
+ valid: true,
1306
+ plan,
1307
+ email,
1308
+ expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
1309
+ deviceLimit: limits.devices,
1310
+ employeeLimit: limits.employees,
1311
+ memoryLimit: limits.memories
1312
+ };
1313
+ } catch {
1314
+ return null;
1315
+ }
1316
+ }
1317
+ async function getCachedLicense() {
1318
+ try {
1319
+ if (!existsSync8(CACHE_PATH)) return null;
1320
+ const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
1321
+ if (!raw.token || typeof raw.token !== "string") return null;
1322
+ return await verifyLicenseJwt(raw.token);
1323
+ } catch {
1324
+ return null;
1325
+ }
1326
+ }
1327
+ function readCachedLicenseToken() {
1328
+ try {
1329
+ if (!existsSync8(CACHE_PATH)) return null;
1330
+ const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
1331
+ return typeof raw.token === "string" ? raw.token : null;
1332
+ } catch {
1333
+ return null;
1334
+ }
1335
+ }
1336
+ function getRawCachedPlan() {
1337
+ try {
1338
+ const token = readCachedLicenseToken();
1339
+ if (!token) return null;
1340
+ const parts = token.split(".");
1341
+ if (parts.length !== 3) return null;
1342
+ const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
1343
+ const plan = payload.plan ?? "free";
1344
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
1345
+ process.stderr.write(
1346
+ `[license] WARN: using unverified cached plan (API unreachable, JWT expired). Plan: ${plan}
1347
+ `
1348
+ );
1349
+ return {
1350
+ valid: true,
1351
+ plan,
1352
+ email: payload.sub ?? "",
1353
+ expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
1354
+ deviceLimit: limits.devices,
1355
+ employeeLimit: limits.employees,
1356
+ memoryLimit: limits.memories
1357
+ };
1358
+ } catch {
1359
+ return null;
1360
+ }
1361
+ }
1362
+ function cacheResponse(token) {
1363
+ try {
1364
+ writeFileSync5(CACHE_PATH, JSON.stringify({ token }), "utf8");
1365
+ } catch {
1366
+ }
1367
+ }
1368
+ function loadPrismaForLicense() {
1369
+ if (_prismaFailed) return null;
1370
+ const dbUrl = process.env.DATABASE_URL;
1371
+ if (!dbUrl) {
1372
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path7.join(os6.homedir(), "exe-db");
1373
+ if (!existsSync8(path7.join(exeDbRoot, "package.json"))) {
1374
+ _prismaFailed = true;
1375
+ return null;
1376
+ }
1377
+ }
1378
+ if (!_prismaPromise) {
1379
+ _prismaPromise = (async () => {
1380
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
1381
+ if (explicitPath) {
1382
+ const mod2 = await import(pathToFileURL2(explicitPath).href);
1383
+ const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
1384
+ if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
1385
+ return new Ctor2();
1386
+ }
1387
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path7.join(os6.homedir(), "exe-db");
1388
+ const req = createRequire2(path7.join(exeDbRoot, "package.json"));
1389
+ const entry = req.resolve("@prisma/client");
1390
+ const mod = await import(pathToFileURL2(entry).href);
1391
+ const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
1392
+ if (!Ctor) throw new Error(`No PrismaClient in ${entry}`);
1393
+ return new Ctor();
1394
+ })().catch((err) => {
1395
+ _prismaFailed = true;
1396
+ _prismaPromise = null;
1397
+ throw err;
1398
+ });
1399
+ }
1400
+ return _prismaPromise;
1401
+ }
1402
+ async function validateViaPostgres(apiKey) {
1403
+ const loader = loadPrismaForLicense();
1404
+ if (!loader) return null;
1405
+ try {
1406
+ const prisma = await loader;
1407
+ const rows = await prisma.$queryRawUnsafe(
1408
+ `SELECT plan, email, status, device_limit, employee_limit, memory_limit, expires_at
1409
+ FROM billing.licenses WHERE key = $1 LIMIT 1`,
1410
+ apiKey
1411
+ );
1412
+ if (!rows || rows.length === 0) return null;
1413
+ const row = rows[0];
1414
+ if (row.status !== "active") return null;
1415
+ if (row.expires_at && new Date(row.expires_at) < /* @__PURE__ */ new Date()) return null;
1416
+ const plan = row.plan;
1417
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
1418
+ return {
1419
+ valid: true,
1420
+ plan,
1421
+ email: row.email,
1422
+ expiresAt: row.expires_at ? new Date(row.expires_at).toISOString() : null,
1423
+ deviceLimit: row.device_limit ?? limits.devices,
1424
+ employeeLimit: row.employee_limit ?? limits.employees,
1425
+ memoryLimit: row.memory_limit ?? limits.memories
1426
+ };
1427
+ } catch {
1428
+ return null;
1429
+ }
1430
+ }
1431
+ async function validateViaCFWorker(apiKey, deviceId) {
1432
+ try {
1433
+ const res = await fetchRetry(`${API_BASE}/auth/activate`, {
1434
+ method: "POST",
1435
+ headers: { "Content-Type": "application/json" },
1436
+ body: JSON.stringify({ apiKey, deviceId }),
1437
+ signal: AbortSignal.timeout(1e4)
1438
+ });
1439
+ if (!res.ok) return null;
1440
+ const data = await res.json();
1441
+ if (data.error === "device_limit_exceeded") return null;
1442
+ if (!data.valid) return null;
1443
+ if (data.token) {
1444
+ cacheResponse(data.token);
1445
+ const verified = await verifyLicenseJwt(data.token);
1446
+ if (verified) return verified;
1447
+ }
1448
+ const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
1449
+ return {
1450
+ valid: data.valid,
1451
+ plan: data.plan,
1452
+ email: data.email,
1453
+ expiresAt: data.expiresAt,
1454
+ deviceLimit: limits.devices,
1455
+ employeeLimit: limits.employees,
1456
+ memoryLimit: limits.memories
1457
+ };
1458
+ } catch {
1459
+ return null;
1460
+ }
1461
+ }
1462
+ async function validateLicense(apiKey, deviceId) {
1463
+ const did = deviceId ?? loadDeviceId();
1464
+ const pgResult = await validateViaPostgres(apiKey);
1465
+ if (pgResult) {
1466
+ try {
1467
+ writeFileSync5(CACHE_PATH, JSON.stringify({ pgLicense: pgResult, ts: Date.now() }), "utf8");
1468
+ } catch {
1469
+ }
1470
+ return pgResult;
1471
+ }
1472
+ const cfResult = await validateViaCFWorker(apiKey, did);
1473
+ if (cfResult) return cfResult;
1474
+ const cached = await getCachedLicense();
1475
+ if (cached) return cached;
1476
+ try {
1477
+ if (existsSync8(CACHE_PATH)) {
1478
+ const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
1479
+ if (raw.pgLicense && raw.ts && Date.now() - raw.ts < 7 * 24 * 60 * 60 * 1e3) {
1480
+ return raw.pgLicense;
1481
+ }
1482
+ }
1483
+ } catch {
1484
+ }
1485
+ const rawFallback = getRawCachedPlan();
1486
+ if (rawFallback) return rawFallback;
1487
+ return { ...FREE_LICENSE, valid: false };
1488
+ }
1489
+ function getCacheAgeMs() {
1490
+ try {
1491
+ const { statSync: statSync3 } = __require("fs");
1492
+ const s = statSync3(CACHE_PATH);
1493
+ return Date.now() - s.mtimeMs;
1494
+ } catch {
1495
+ return Infinity;
1496
+ }
1497
+ }
1498
+ async function checkLicense() {
1499
+ let key = loadLicense();
1500
+ if (!key) {
1501
+ try {
1502
+ const configPath = path7.join(EXE_AI_DIR, "config.json");
1503
+ if (existsSync8(configPath)) {
1504
+ const raw = JSON.parse(readFileSync6(configPath, "utf8"));
1505
+ const cloud = raw.cloud;
1506
+ if (cloud?.apiKey) {
1507
+ key = cloud.apiKey;
1508
+ saveLicense(key);
1509
+ }
1510
+ }
1511
+ } catch {
1512
+ }
1513
+ }
1514
+ if (!key) return FREE_LICENSE;
1515
+ const cached = await getCachedLicense();
1516
+ if (cached && getCacheAgeMs() < CACHE_MAX_AGE_MS) return cached;
1517
+ const deviceId = loadDeviceId();
1518
+ return validateLicense(key, deviceId);
1519
+ }
1520
+ function isFeatureAllowed(license, feature) {
1521
+ switch (feature) {
1522
+ case "cloud_sync":
1523
+ case "external_agents":
1524
+ case "wiki":
1525
+ return license.plan !== "free";
1526
+ case "unlimited_employees":
1527
+ return license.plan === "team" || license.plan === "agency" || license.plan === "enterprise";
1528
+ }
1529
+ }
1530
+ function mirrorLicenseKey(apiKey) {
1531
+ const trimmed = apiKey.trim();
1532
+ if (!trimmed) return;
1533
+ saveLicense(trimmed);
1534
+ }
1535
+ async function assertVpsLicense(opts) {
1536
+ const env = opts?.env ?? process.env;
1537
+ const inProduction = env.NODE_ENV === "production";
1538
+ if (!opts?.force && !inProduction) {
1539
+ return { ...FREE_LICENSE, plan: "free" };
1540
+ }
1541
+ const envKey = env.EXE_LICENSE_KEY?.trim();
1542
+ if (envKey) {
1543
+ saveLicense(envKey);
1544
+ }
1545
+ const apiKey = envKey ?? loadLicense();
1546
+ if (!apiKey) {
1547
+ throw new Error(
1548
+ "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."
1549
+ );
1550
+ }
1551
+ const deviceId = loadDeviceId();
1552
+ let backendResponse = null;
1553
+ let explicitRejection = false;
1554
+ let transientFailure = false;
1555
+ try {
1556
+ const res = await fetchRetry(`${API_BASE}/auth/activate`, {
1557
+ method: "POST",
1558
+ headers: { "Content-Type": "application/json" },
1559
+ body: JSON.stringify({ apiKey, deviceId }),
1560
+ signal: AbortSignal.timeout(1e4)
1561
+ });
1562
+ if (res.ok) {
1563
+ backendResponse = await res.json();
1564
+ if (!backendResponse.valid) explicitRejection = true;
1565
+ } else if (res.status === 401 || res.status === 403) {
1566
+ explicitRejection = true;
1567
+ } else {
1568
+ transientFailure = true;
1569
+ }
1570
+ } catch {
1571
+ transientFailure = true;
1572
+ }
1573
+ if (backendResponse?.valid) {
1574
+ if (backendResponse.token) {
1575
+ cacheResponse(backendResponse.token);
1576
+ const verified = await verifyLicenseJwt(backendResponse.token);
1577
+ if (verified) return verified;
1578
+ }
1579
+ const limits = PLAN_LIMITS[backendResponse.plan] ?? PLAN_LIMITS.free;
1580
+ return {
1581
+ valid: true,
1582
+ plan: backendResponse.plan,
1583
+ email: backendResponse.email,
1584
+ expiresAt: backendResponse.expiresAt,
1585
+ deviceLimit: limits.devices,
1586
+ employeeLimit: limits.employees,
1587
+ memoryLimit: limits.memories
1588
+ };
1589
+ }
1590
+ if (explicitRejection) {
1591
+ throw new Error(
1592
+ `License invalid or expired. Renew at https://askexe.com, then restart. Backend rejected key ending in ...${apiKey.slice(-4)}.`
1593
+ );
1594
+ }
1595
+ if (!transientFailure) {
1596
+ throw new Error(
1597
+ "License validation failed: unknown backend state. Restore network connectivity to https://cloud.askexe.com and retry."
1598
+ );
1599
+ }
1600
+ const fresh = await getCachedLicense();
1601
+ if (fresh && fresh.valid) return fresh;
1602
+ const graceDays = opts?.offlineGraceDays ?? 7;
1603
+ const graceMs = graceDays * 24 * 60 * 60 * 1e3;
1604
+ try {
1605
+ const token = readCachedLicenseToken();
1606
+ if (token) {
1607
+ const payloadB64 = token.split(".")[1];
1608
+ if (payloadB64) {
1609
+ const payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString());
1610
+ const expMs = (payload.exp ?? 0) * 1e3;
1611
+ if (Date.now() < expMs + graceMs) {
1612
+ const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
1613
+ const { payload: verified } = await jwtVerify(token, key, {
1614
+ algorithms: [LICENSE_JWT_ALG],
1615
+ clockTolerance: graceDays * 24 * 60 * 60
1616
+ });
1617
+ const plan = verified.plan ?? "free";
1618
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
1619
+ return {
1620
+ valid: true,
1621
+ plan,
1622
+ email: verified.sub ?? "",
1623
+ expiresAt: verified.exp ? new Date(verified.exp * 1e3).toISOString() : null,
1624
+ deviceLimit: limits.devices,
1625
+ employeeLimit: limits.employees,
1626
+ memoryLimit: limits.memories
1627
+ };
1628
+ }
1629
+ }
1630
+ }
1631
+ } catch {
1632
+ }
1633
+ throw new Error(
1634
+ `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.`
1635
+ );
1636
+ }
1637
+ function startLicenseRevalidation(intervalMs = 36e5) {
1638
+ if (_revalTimer) return;
1639
+ _revalTimer = setInterval(async () => {
1640
+ try {
1641
+ const license = await checkLicense();
1642
+ if (!license.valid) {
1643
+ process.stderr.write("[exe-os] License expired or invalid \u2014 features may be restricted\n");
1644
+ }
1645
+ } catch {
1646
+ }
1647
+ }, intervalMs);
1648
+ if (_revalTimer && typeof _revalTimer === "object" && "unref" in _revalTimer) {
1649
+ _revalTimer.unref();
1650
+ }
1651
+ }
1652
+ function stopLicenseRevalidation() {
1653
+ if (_revalTimer) {
1654
+ clearInterval(_revalTimer);
1655
+ _revalTimer = null;
1656
+ }
1657
+ }
1658
+ 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;
983
1659
  var init_license = __esm({
984
1660
  "src/lib/license.ts"() {
985
1661
  "use strict";
@@ -987,7 +1663,13 @@ var init_license = __esm({
987
1663
  LICENSE_PATH = path7.join(EXE_AI_DIR, "license.key");
988
1664
  CACHE_PATH = path7.join(EXE_AI_DIR, "license-cache.json");
989
1665
  DEVICE_ID_PATH = path7.join(EXE_AI_DIR, "device-id");
990
- API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com/cloud";
1666
+ API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://cloud.askexe.com";
1667
+ RETRY_DELAY_MS = 500;
1668
+ LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
1669
+ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
1670
+ 4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
1671
+ -----END PUBLIC KEY-----`;
1672
+ LICENSE_JWT_ALG = "ES256";
991
1673
  PLAN_LIMITS = {
992
1674
  free: { devices: 1, employees: 1, memories: 5e3 },
993
1675
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -995,6 +1677,19 @@ var init_license = __esm({
995
1677
  agency: { devices: 50, employees: 100, memories: 1e7 },
996
1678
  enterprise: { devices: -1, employees: -1, memories: -1 }
997
1679
  };
1680
+ FREE_LICENSE = {
1681
+ valid: true,
1682
+ plan: "free",
1683
+ email: "",
1684
+ expiresAt: null,
1685
+ deviceLimit: 1,
1686
+ employeeLimit: 1,
1687
+ memoryLimit: 5e3
1688
+ };
1689
+ _prismaPromise = null;
1690
+ _prismaFailed = false;
1691
+ CACHE_MAX_AGE_MS = 36e5;
1692
+ _revalTimer = null;
998
1693
  }
999
1694
  });
1000
1695
 
@@ -1506,6 +2201,19 @@ async function resolveTask(client, identifier, scopeSession) {
1506
2201
  args: [identifier, ...scope.args]
1507
2202
  });
1508
2203
  if (result.rows.length === 1) return result.rows[0];
2204
+ if (/^[a-f0-9]{7,12}$/i.test(identifier)) {
2205
+ result = await client.execute({
2206
+ sql: `SELECT * FROM tasks WHERE id LIKE ?`,
2207
+ args: [`${identifier}%`]
2208
+ });
2209
+ if (result.rows.length === 1) return result.rows[0];
2210
+ if (result.rows.length > 1) {
2211
+ const matches = result.rows.map((r) => `${String(r.id)} "${String(r.title)}" (${String(r.status)})`).join(", ");
2212
+ throw new Error(
2213
+ `Multiple tasks match short-ID "${identifier}": ${matches}. Use a longer prefix to disambiguate.`
2214
+ );
2215
+ }
2216
+ }
1509
2217
  result = await client.execute({
1510
2218
  sql: `SELECT * FROM tasks WHERE task_file LIKE ?${scope.sql}`,
1511
2219
  args: [`%${identifier}%`, ...scope.args]
@@ -2360,12 +3068,13 @@ async function cascadeUnblock(taskId, baseDir, now) {
2360
3068
  WHERE blocked_by = ? AND status = 'blocked'`,
2361
3069
  args: [now, taskId]
2362
3070
  });
2363
- if (baseDir && unblocked.rowsAffected > 0) {
2364
- const ubScope = sessionScopeFilter();
2365
- const unblockedRows = await client.execute({
2366
- sql: `SELECT task_file FROM tasks WHERE blocked_by IS NULL AND updated_at = ?${ubScope.sql}`,
2367
- args: [now, ...ubScope.args]
2368
- });
3071
+ if (unblocked.rowsAffected === 0) return;
3072
+ const ubScope = sessionScopeFilter();
3073
+ const unblockedRows = await client.execute({
3074
+ sql: `SELECT id, title, assigned_to, priority, task_file FROM tasks WHERE blocked_by IS NULL AND updated_at = ?${ubScope.sql}`,
3075
+ args: [now, ...ubScope.args]
3076
+ });
3077
+ if (baseDir) {
2369
3078
  for (const ur of unblockedRows.rows) {
2370
3079
  try {
2371
3080
  const ubFile = path14.join(baseDir, String(ur.task_file));
@@ -2377,6 +3086,19 @@ async function cascadeUnblock(taskId, baseDir, now) {
2377
3086
  }
2378
3087
  }
2379
3088
  }
3089
+ if (unblockedRows.rows.length > 0 && !process.env.VITEST) {
3090
+ try {
3091
+ const { queueIntercom: queueIntercom2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
3092
+ const dispatched = /* @__PURE__ */ new Set();
3093
+ for (const ur of unblockedRows.rows) {
3094
+ const assignee = String(ur.assigned_to);
3095
+ if (dispatched.has(assignee)) continue;
3096
+ dispatched.add(assignee);
3097
+ queueIntercom2(`${assignee}`, `unblocked: "${String(ur.title)}" is now ready`);
3098
+ }
3099
+ } catch {
3100
+ }
3101
+ }
2380
3102
  }
2381
3103
  async function findNextTask(assignedTo) {
2382
3104
  const client = getClient();
@@ -3035,6 +3757,15 @@ var init_embedder = __esm({
3035
3757
  // src/lib/behaviors.ts
3036
3758
  import crypto5 from "crypto";
3037
3759
  async function storeBehavior(opts) {
3760
+ try {
3761
+ const { loadEmployeesSync: loadEmployeesSync2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
3762
+ const roster = loadEmployeesSync2();
3763
+ if (roster.length > 0 && !roster.some((e) => e.name === opts.agentId)) {
3764
+ throw new Error(`Agent "${opts.agentId}" not found in roster. Cannot store behavior for unregistered agent.`);
3765
+ }
3766
+ } catch (e) {
3767
+ if (e instanceof Error && e.message.includes("not found in roster")) throw e;
3768
+ }
3038
3769
  const client = getClient();
3039
3770
  const id = crypto5.randomUUID();
3040
3771
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -3045,10 +3776,18 @@ async function storeBehavior(opts) {
3045
3776
  vector = new Float32Array(vec);
3046
3777
  } catch {
3047
3778
  }
3779
+ let createdByDevice = null;
3780
+ try {
3781
+ const { loadDeviceId: loadDeviceId2 } = await Promise.resolve().then(() => (init_license(), license_exports));
3782
+ createdByDevice = loadDeviceId2() ?? null;
3783
+ } catch {
3784
+ }
3785
+ const createdByAgent = process.env.AGENT_ID ?? null;
3786
+ const sourceSessionId = process.env.CLAUDE_SESSION_ID ?? process.env.SESSION_ID ?? null;
3048
3787
  await client.execute({
3049
- sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at, vector)
3050
- VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?)`,
3051
- args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now, vector ? vector.buffer : null]
3788
+ 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)
3789
+ VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?)`,
3790
+ args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now, vector ? vector.buffer : null, createdByAgent, createdByDevice, sourceSessionId]
3052
3791
  });
3053
3792
  return id;
3054
3793
  }
@@ -3480,6 +4219,12 @@ async function updateTask(input) {
3480
4219
  }
3481
4220
  }
3482
4221
  }
4222
+ if (input.status === "cancelled") {
4223
+ try {
4224
+ await cascadeUnblock(taskId, input.baseDir, now);
4225
+ } catch {
4226
+ }
4227
+ }
3483
4228
  if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
3484
4229
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
3485
4230
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
@@ -4011,11 +4756,12 @@ function getDispatchedBy(sessionKey) {
4011
4756
  }
4012
4757
  }
4013
4758
  function resolveExeSession() {
4759
+ if (process.env.EXE_SESSION_NAME) {
4760
+ const fromEnv = extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
4761
+ if (fromEnv) return fromEnv;
4762
+ }
4014
4763
  const mySession = getMySession();
4015
4764
  if (!mySession) {
4016
- if (process.env.EXE_SESSION_NAME) {
4017
- return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
4018
- }
4019
4765
  return null;
4020
4766
  }
4021
4767
  const fromSessionName = extractRootExe(mySession);
@@ -4030,6 +4776,10 @@ function resolveExeSession() {
4030
4776
  `[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
4031
4777
  `
4032
4778
  );
4779
+ try {
4780
+ registerParentExe(key, fromSessionName);
4781
+ } catch {
4782
+ }
4033
4783
  candidate = fromSessionName;
4034
4784
  } else {
4035
4785
  candidate = fromCache;