@askexenow/exe-os 0.9.112 → 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 +54 -11
  3. package/dist/bin/agentic-reflection-backfill.js +29 -1
  4. package/dist/bin/agentic-semantic-label.js +29 -1
  5. package/dist/bin/backfill-conversations.js +53 -10
  6. package/dist/bin/backfill-responses.js +54 -11
  7. package/dist/bin/backfill-vectors.js +29 -1
  8. package/dist/bin/bulk-sync-postgres.js +55 -12
  9. package/dist/bin/cleanup-stale-review-tasks.js +75 -15
  10. package/dist/bin/cli.js +293 -76
  11. package/dist/bin/exe-agent-config.js +7 -1
  12. package/dist/bin/exe-agent.js +28 -2
  13. package/dist/bin/exe-assign.js +54 -11
  14. package/dist/bin/exe-boot.js +481 -147
  15. package/dist/bin/exe-call.js +45 -4
  16. package/dist/bin/exe-cloud.js +93 -15
  17. package/dist/bin/exe-dispatch.js +369 -24
  18. package/dist/bin/exe-doctor.js +53 -10
  19. package/dist/bin/exe-export-behaviors.js +54 -11
  20. package/dist/bin/exe-forget.js +54 -11
  21. package/dist/bin/exe-gateway.js +128 -23
  22. package/dist/bin/exe-heartbeat.js +75 -15
  23. package/dist/bin/exe-kill.js +54 -11
  24. package/dist/bin/exe-launch-agent.js +70 -12
  25. package/dist/bin/exe-new-employee.js +175 -7
  26. package/dist/bin/exe-pending-messages.js +75 -15
  27. package/dist/bin/exe-pending-notifications.js +75 -15
  28. package/dist/bin/exe-pending-reviews.js +75 -15
  29. package/dist/bin/exe-rename.js +54 -11
  30. package/dist/bin/exe-review.js +54 -11
  31. package/dist/bin/exe-search.js +54 -11
  32. package/dist/bin/exe-session-cleanup.js +491 -146
  33. package/dist/bin/exe-settings.js +10 -4
  34. package/dist/bin/exe-start-codex.js +524 -245
  35. package/dist/bin/exe-start-opencode.js +534 -165
  36. package/dist/bin/exe-status.js +75 -15
  37. package/dist/bin/exe-support.js +1 -1
  38. package/dist/bin/exe-team.js +54 -11
  39. package/dist/bin/git-sweep.js +369 -24
  40. package/dist/bin/graph-backfill.js +54 -11
  41. package/dist/bin/graph-export.js +54 -11
  42. package/dist/bin/install.js +62 -4
  43. package/dist/bin/intercom-check.js +491 -146
  44. package/dist/bin/pre-publish.js +13 -1
  45. package/dist/bin/scan-tasks.js +369 -24
  46. package/dist/bin/setup.js +91 -13
  47. package/dist/bin/shard-migrate.js +54 -11
  48. package/dist/bin/stack-update.js +1 -1
  49. package/dist/bin/update.js +3 -3
  50. package/dist/gateway/index.js +128 -23
  51. package/dist/hooks/bug-report-worker.js +128 -23
  52. package/dist/hooks/codex-stop-task-finalizer.js +512 -140
  53. package/dist/hooks/commit-complete.js +369 -24
  54. package/dist/hooks/error-recall.js +54 -11
  55. package/dist/hooks/ingest.js +4575 -252
  56. package/dist/hooks/instructions-loaded.js +54 -11
  57. package/dist/hooks/notification.js +54 -11
  58. package/dist/hooks/post-compact.js +75 -15
  59. package/dist/hooks/post-tool-combined.js +75 -15
  60. package/dist/hooks/pre-compact.js +449 -104
  61. package/dist/hooks/pre-tool-use.js +90 -15
  62. package/dist/hooks/prompt-submit.js +129 -24
  63. package/dist/hooks/session-end.js +451 -109
  64. package/dist/hooks/session-start.js +104 -16
  65. package/dist/hooks/stop.js +74 -14
  66. package/dist/hooks/subagent-stop.js +75 -15
  67. package/dist/hooks/summary-worker.js +73 -7
  68. package/dist/index.js +128 -23
  69. package/dist/lib/agent-config.js +16 -1
  70. package/dist/lib/cloud-sync.js +38 -1
  71. package/dist/lib/consolidation.js +16 -1
  72. package/dist/lib/database.js +16 -0
  73. package/dist/lib/db.js +16 -0
  74. package/dist/lib/device-registry.js +16 -0
  75. package/dist/lib/employee-templates.js +29 -3
  76. package/dist/lib/employees.js +16 -1
  77. package/dist/lib/exe-daemon.js +268 -42
  78. package/dist/lib/hybrid-search.js +54 -11
  79. package/dist/lib/license.js +3 -3
  80. package/dist/lib/messaging.js +21 -4
  81. package/dist/lib/schedules.js +29 -1
  82. package/dist/lib/skill-learning.js +458 -70
  83. package/dist/lib/status-brief.js +14 -1
  84. package/dist/lib/store.js +54 -11
  85. package/dist/lib/tasks.js +393 -91
  86. package/dist/lib/tmux-routing.js +316 -14
  87. package/dist/mcp/server.js +169 -30
  88. package/dist/mcp/tools/create-task.js +75 -13
  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 +390 -91
  93. package/dist/runtime/index.js +446 -101
  94. package/dist/tui/App.js +208 -54
  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
 
@@ -3838,6 +4093,22 @@ async function ensureSchema() {
3838
4093
  } catch (e) {
3839
4094
  logCatchDebug("migration", e);
3840
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
+ }
3841
4112
  }
3842
4113
  async function disposeDatabase() {
3843
4114
  if (_walCheckpointTimer) {
@@ -4256,7 +4527,7 @@ async function assertVpsLicense(opts) {
4256
4527
  }
4257
4528
  if (!transientFailure) {
4258
4529
  throw new Error(
4259
- "License validation failed: unknown backend state. Restore network connectivity to https://askexe.com/cloud and retry."
4530
+ "License validation failed: unknown backend state. Restore network connectivity to https://cloud.askexe.com and retry."
4260
4531
  );
4261
4532
  }
4262
4533
  const fresh = await getCachedLicense();
@@ -4293,7 +4564,7 @@ async function assertVpsLicense(opts) {
4293
4564
  } catch {
4294
4565
  }
4295
4566
  throw new Error(
4296
- `License validation unreachable for more than ${graceDays} days. Restore network connectivity to https://askexe.com/cloud and retry. This VPS image refuses to boot after the offline grace window.`
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.`
4297
4568
  );
4298
4569
  }
4299
4570
  function startLicenseRevalidation(intervalMs = 36e5) {
@@ -4325,7 +4596,7 @@ var init_license = __esm({
4325
4596
  LICENSE_PATH = path9.join(EXE_AI_DIR, "license.key");
4326
4597
  CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
4327
4598
  DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
4328
- API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com/cloud";
4599
+ API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://cloud.askexe.com";
4329
4600
  RETRY_DELAY_MS = 500;
4330
4601
  LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
4331
4602
  MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
@@ -4863,6 +5134,19 @@ async function resolveTask(client, identifier, scopeSession) {
4863
5134
  args: [identifier, ...scope.args]
4864
5135
  });
4865
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
+ }
4866
5150
  result2 = await client.execute({
4867
5151
  sql: `SELECT * FROM tasks WHERE task_file LIKE ?${scope.sql}`,
4868
5152
  args: [`%${identifier}%`, ...scope.args]
@@ -5717,12 +6001,13 @@ async function cascadeUnblock(taskId, baseDir, now) {
5717
6001
  WHERE blocked_by = ? AND status = 'blocked'`,
5718
6002
  args: [now, taskId]
5719
6003
  });
5720
- if (baseDir && unblocked.rowsAffected > 0) {
5721
- const ubScope = sessionScopeFilter();
5722
- const unblockedRows = await client.execute({
5723
- sql: `SELECT task_file FROM tasks WHERE blocked_by IS NULL AND updated_at = ?${ubScope.sql}`,
5724
- args: [now, ...ubScope.args]
5725
- });
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) {
5726
6011
  for (const ur of unblockedRows.rows) {
5727
6012
  try {
5728
6013
  const ubFile = path16.join(baseDir, String(ur.task_file));
@@ -5734,6 +6019,19 @@ async function cascadeUnblock(taskId, baseDir, now) {
5734
6019
  }
5735
6020
  }
5736
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
+ }
5737
6035
  }
5738
6036
  async function findNextTask(assignedTo) {
5739
6037
  const client = getClient();
@@ -5943,6 +6241,15 @@ var init_embedder = __esm({
5943
6241
  // src/lib/behaviors.ts
5944
6242
  import crypto5 from "crypto";
5945
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
+ }
5946
6253
  const client = getClient();
5947
6254
  const id = crypto5.randomUUID();
5948
6255
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -6396,6 +6703,12 @@ async function updateTask(input) {
6396
6703
  }
6397
6704
  }
6398
6705
  }
6706
+ if (input.status === "cancelled") {
6707
+ try {
6708
+ await cascadeUnblock(taskId, input.baseDir, now);
6709
+ } catch {
6710
+ }
6711
+ }
6399
6712
  if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
6400
6713
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
6401
6714
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
@@ -6927,11 +7240,12 @@ function getDispatchedBy(sessionKey) {
6927
7240
  }
6928
7241
  }
6929
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
+ }
6930
7247
  const mySession = getMySession();
6931
7248
  if (!mySession) {
6932
- if (process.env.EXE_SESSION_NAME) {
6933
- return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
6934
- }
6935
7249
  return null;
6936
7250
  }
6937
7251
  const fromSessionName = extractRootExe(mySession);
@@ -6946,6 +7260,10 @@ function resolveExeSession() {
6946
7260
  `[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
6947
7261
  `
6948
7262
  );
7263
+ try {
7264
+ registerParentExe(key, fromSessionName);
7265
+ } catch {
7266
+ }
6949
7267
  candidate = fromSessionName;
6950
7268
  } else {
6951
7269
  candidate = fromCache;
@@ -8640,11 +8958,17 @@ var init_platform_procedures = __esm({
8640
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."
8641
8959
  },
8642
8960
  {
8643
- title: "Customer orchestration maturity \u2014 recommend, never trap",
8961
+ title: "Orchestration phase guidance \u2014 recommend, never trap",
8644
8962
  domain: "workflow",
8645
8963
  priority: "p1",
8646
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."
8647
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
+ },
8648
8972
  {
8649
8973
  title: "Single dispatch path \u2014 create_task only",
8650
8974
  domain: "workflow",
@@ -8678,6 +9002,12 @@ var init_platform_procedures = __esm({
8678
9002
  priority: "p0",
8679
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."
8680
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
+ },
8681
9011
  {
8682
9012
  title: "Customer patch triage \u2014 upstream bug vs customization",
8683
9013
  domain: "support",
@@ -8963,10 +9293,24 @@ function stableId(memoryId, type, content) {
8963
9293
  return createHash2("sha256").update(`${memoryId}:${type}:${content}`).digest("hex").slice(0, 32);
8964
9294
  }
8965
9295
  function cleanText(text) {
8966
- 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;
8967
9302
  }
8968
- function splitSentences(text) {
8969
- 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;
8970
9314
  }
8971
9315
  function inferCardType(sentence, toolName) {
8972
9316
  const lower = sentence.toLowerCase();
@@ -8998,12 +9342,12 @@ function predicateFor(type) {
8998
9342
  }
8999
9343
  }
9000
9344
  function extractMemoryCards(row) {
9001
- const sentences = splitSentences(row.raw_text);
9345
+ const segments = splitSegments(row.raw_text);
9002
9346
  const cards = [];
9003
- for (const sentence of sentences) {
9347
+ for (const sentence of segments) {
9004
9348
  const type = inferCardType(sentence, row.tool_name);
9005
9349
  const subject = extractSubject(sentence, row.agent_id);
9006
- 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;
9007
9351
  cards.push({
9008
9352
  id: stableId(row.id, type, content),
9009
9353
  memory_id: row.id,
@@ -9099,13 +9443,14 @@ Source memory: ${String(row.source_ref ?? row.memory_id)}`,
9099
9443
  last_accessed: String(row.timestamp)
9100
9444
  }));
9101
9445
  }
9102
- var MAX_CARDS_PER_MEMORY, MAX_SENTENCE_CHARS;
9446
+ var MAX_CARDS_PER_MEMORY, MAX_SEGMENT_CHARS, MIN_SEGMENT_CHARS;
9103
9447
  var init_memory_cards = __esm({
9104
9448
  "src/lib/memory-cards.ts"() {
9105
9449
  "use strict";
9106
9450
  init_database();
9107
- MAX_CARDS_PER_MEMORY = 6;
9108
- MAX_SENTENCE_CHARS = 360;
9451
+ MAX_CARDS_PER_MEMORY = 8;
9452
+ MAX_SEGMENT_CHARS = 500;
9453
+ MIN_SEGMENT_CHARS = 20;
9109
9454
  }
9110
9455
  });
9111
9456