@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
@@ -134,11 +134,17 @@ var PLATFORM_PROCEDURES = [
134
134
  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."
135
135
  },
136
136
  {
137
- title: "Customer orchestration maturity \u2014 recommend, never trap",
137
+ title: "Orchestration phase guidance \u2014 recommend, never trap",
138
138
  domain: "workflow",
139
139
  priority: "p1",
140
140
  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."
141
141
  },
142
+ {
143
+ title: "Routing slot vs display title \u2014 internal 'coo' is plumbing, not your name",
144
+ domain: "identity",
145
+ priority: "p0",
146
+ 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."
147
+ },
142
148
  {
143
149
  title: "Single dispatch path \u2014 create_task only",
144
150
  domain: "workflow",
@@ -172,6 +178,12 @@ var PLATFORM_PROCEDURES = [
172
178
  priority: "p0",
173
179
  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."
174
180
  },
181
+ {
182
+ title: "Destructive operations \u2014 mandatory reviewer gate",
183
+ domain: "security",
184
+ priority: "p0",
185
+ 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."
186
+ },
175
187
  {
176
188
  title: "Customer patch triage \u2014 upstream bug vs customization",
177
189
  domain: "support",
@@ -663,6 +663,19 @@ var init_runtime_table = __esm({
663
663
  });
664
664
 
665
665
  // src/lib/agent-config.ts
666
+ var agent_config_exports = {};
667
+ __export(agent_config_exports, {
668
+ AGENT_CONFIG_PATH: () => AGENT_CONFIG_PATH,
669
+ DEFAULT_MODELS: () => DEFAULT_MODELS,
670
+ KNOWN_RUNTIMES: () => KNOWN_RUNTIMES,
671
+ RUNTIME_LABELS: () => RUNTIME_LABELS,
672
+ clearAgentRuntime: () => clearAgentRuntime,
673
+ getAgentRuntime: () => getAgentRuntime,
674
+ loadAgentConfig: () => loadAgentConfig,
675
+ saveAgentConfig: () => saveAgentConfig,
676
+ setAgentMcps: () => setAgentMcps,
677
+ setAgentRuntime: () => setAgentRuntime
678
+ });
666
679
  import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync4 } from "fs";
667
680
  import path3 from "path";
668
681
  function loadAgentConfig() {
@@ -673,6 +686,12 @@ function loadAgentConfig() {
673
686
  return {};
674
687
  }
675
688
  }
689
+ function saveAgentConfig(config) {
690
+ const dir = path3.dirname(AGENT_CONFIG_PATH);
691
+ ensurePrivateDirSync(dir);
692
+ writeFileSync2(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
693
+ enforcePrivateFileSync(AGENT_CONFIG_PATH);
694
+ }
676
695
  function getAgentRuntime(agentId) {
677
696
  const config = loadAgentConfig();
678
697
  const entry = config[agentId];
@@ -681,7 +700,47 @@ function getAgentRuntime(agentId) {
681
700
  if (orgDefault) return orgDefault;
682
701
  return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
683
702
  }
684
- var AGENT_CONFIG_PATH, DEFAULT_MODELS;
703
+ function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
704
+ const knownModels = KNOWN_RUNTIMES[runtime];
705
+ if (!knownModels) {
706
+ return {
707
+ ok: false,
708
+ error: `Unknown runtime "${runtime}". Valid: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`
709
+ };
710
+ }
711
+ if (!knownModels.includes(model)) {
712
+ return {
713
+ ok: false,
714
+ error: `Unknown model "${model}" for runtime "${runtime}". Valid: ${knownModels.join(", ")}`
715
+ };
716
+ }
717
+ const config = loadAgentConfig();
718
+ const existing = config[agentId];
719
+ const entry = { runtime, model };
720
+ if (reasoning_effort) entry.reasoning_effort = reasoning_effort;
721
+ if (mcps !== void 0) {
722
+ entry.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
723
+ } else if (existing?.mcps) {
724
+ entry.mcps = existing.mcps;
725
+ }
726
+ config[agentId] = entry;
727
+ saveAgentConfig(config);
728
+ return { ok: true };
729
+ }
730
+ function setAgentMcps(agentId, mcps) {
731
+ const config = loadAgentConfig();
732
+ const existing = config[agentId] ?? getAgentRuntime(agentId);
733
+ existing.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
734
+ config[agentId] = existing;
735
+ saveAgentConfig(config);
736
+ return { ok: true };
737
+ }
738
+ function clearAgentRuntime(agentId) {
739
+ const config = loadAgentConfig();
740
+ delete config[agentId];
741
+ saveAgentConfig(config);
742
+ }
743
+ var AGENT_CONFIG_PATH, KNOWN_RUNTIMES, RUNTIME_LABELS, DEFAULT_MODELS;
685
744
  var init_agent_config = __esm({
686
745
  "src/lib/agent-config.ts"() {
687
746
  "use strict";
@@ -689,6 +748,16 @@ var init_agent_config = __esm({
689
748
  init_runtime_table();
690
749
  init_secure_files();
691
750
  AGENT_CONFIG_PATH = path3.join(EXE_AI_DIR, "agent-config.json");
751
+ KNOWN_RUNTIMES = {
752
+ claude: ["claude-opus-4.6", "claude-opus-4", "claude-sonnet-4.6", "claude-sonnet-4", "claude-haiku-4.5"],
753
+ codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
754
+ opencode: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4", "google/gemini-2.5-pro", "deepseek/deepseek-r3", "minimax/minimax-m2.5"]
755
+ };
756
+ RUNTIME_LABELS = {
757
+ claude: "Claude Code (Anthropic)",
758
+ codex: "Codex (OpenAI)",
759
+ opencode: "OpenCode (open source)"
760
+ };
692
761
  DEFAULT_MODELS = {
693
762
  claude: "claude-opus-4.6",
694
763
  codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
@@ -879,6 +948,32 @@ var init_db_retry = __esm({
879
948
  });
880
949
 
881
950
  // src/lib/employees.ts
951
+ var employees_exports = {};
952
+ __export(employees_exports, {
953
+ COORDINATOR_ROLE: () => COORDINATOR_ROLE,
954
+ DEFAULT_COORDINATOR_TEMPLATE_NAME: () => DEFAULT_COORDINATOR_TEMPLATE_NAME,
955
+ EMPLOYEES_PATH: () => EMPLOYEES_PATH,
956
+ addEmployee: () => addEmployee,
957
+ baseAgentName: () => baseAgentName,
958
+ canCoordinate: () => canCoordinate,
959
+ getCoordinatorEmployee: () => getCoordinatorEmployee,
960
+ getCoordinatorName: () => getCoordinatorName,
961
+ getEmployee: () => getEmployee,
962
+ getEmployeeByRole: () => getEmployeeByRole,
963
+ getEmployeeNamesByRole: () => getEmployeeNamesByRole,
964
+ hasRole: () => hasRole,
965
+ hireEmployee: () => hireEmployee,
966
+ isCoordinatorName: () => isCoordinatorName,
967
+ isCoordinatorRole: () => isCoordinatorRole,
968
+ isMultiInstance: () => isMultiInstance,
969
+ loadEmployees: () => loadEmployees,
970
+ loadEmployeesSync: () => loadEmployeesSync,
971
+ normalizeRole: () => normalizeRole,
972
+ normalizeRosterCase: () => normalizeRosterCase,
973
+ registerBinSymlinks: () => registerBinSymlinks,
974
+ saveEmployees: () => saveEmployees,
975
+ validateEmployeeName: () => validateEmployeeName
976
+ });
882
977
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
883
978
  import { existsSync as existsSync6, symlinkSync, readlinkSync, readFileSync as readFileSync5, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
884
979
  import { execSync as execSync3 } from "child_process";
@@ -900,6 +995,24 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
900
995
  if (!agentName) return false;
901
996
  return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
902
997
  }
998
+ function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
999
+ return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
1000
+ }
1001
+ function validateEmployeeName(name) {
1002
+ if (!name) {
1003
+ return { valid: false, error: "Name is required" };
1004
+ }
1005
+ if (name.length > 32) {
1006
+ return { valid: false, error: "Name must be 32 characters or fewer" };
1007
+ }
1008
+ if (!/^[a-z][a-z0-9]*$/.test(name)) {
1009
+ return {
1010
+ valid: false,
1011
+ error: "Name must start with a letter and contain only lowercase alphanumeric characters"
1012
+ };
1013
+ }
1014
+ return { valid: true };
1015
+ }
903
1016
  async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
904
1017
  if (!existsSync6(employeesPath)) {
905
1018
  return [];
@@ -911,6 +1024,10 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
911
1024
  return [];
912
1025
  }
913
1026
  }
1027
+ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
1028
+ await mkdir2(path5.dirname(employeesPath), { recursive: true });
1029
+ await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
1030
+ }
914
1031
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
915
1032
  if (!existsSync6(employeesPath)) return [];
916
1033
  try {
@@ -922,6 +1039,19 @@ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
922
1039
  function getEmployee(employees, name) {
923
1040
  return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
924
1041
  }
1042
+ function getEmployeeByRole(employees, role) {
1043
+ const lower = role.toLowerCase();
1044
+ return employees.find((e) => e.role.toLowerCase() === lower);
1045
+ }
1046
+ function getEmployeeNamesByRole(employees, role) {
1047
+ const lower = role.toLowerCase();
1048
+ return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
1049
+ }
1050
+ function hasRole(agentName, role) {
1051
+ const employees = loadEmployeesSync();
1052
+ const emp = getEmployee(employees, agentName);
1053
+ return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
1054
+ }
925
1055
  function baseAgentName(name, employees) {
926
1056
  const match = name.match(/^([a-zA-Z]+)\d+$/);
927
1057
  if (!match) return name;
@@ -936,7 +1066,131 @@ function isMultiInstance(agentName, employees) {
936
1066
  if (!emp) return false;
937
1067
  return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
938
1068
  }
939
- var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR;
1069
+ function addEmployee(employees, employee) {
1070
+ const { systemPrompt: _legacyPrompt, ...rest } = employee;
1071
+ const normalized = { ...rest, name: employee.name.toLowerCase() };
1072
+ if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
1073
+ throw new Error(`Employee '${normalized.name}' already exists`);
1074
+ }
1075
+ return [...employees, normalized];
1076
+ }
1077
+ function appendToCoordinatorTeam(employee) {
1078
+ const coordinator = getCoordinatorEmployee(loadEmployeesSync());
1079
+ if (!coordinator) return;
1080
+ const idPath = path5.join(IDENTITY_DIR, `${coordinator.name}.md`);
1081
+ if (!existsSync6(idPath)) return;
1082
+ const content = readFileSync5(idPath, "utf-8");
1083
+ if (content.includes(`**${capitalize(employee.name)}`)) return;
1084
+ const teamMatch = content.match(TEAM_SECTION_RE);
1085
+ if (!teamMatch || teamMatch.index === void 0) return;
1086
+ const afterTeam = content.slice(teamMatch.index + teamMatch[0].length);
1087
+ const nextHeading = afterTeam.match(/\n## /);
1088
+ const entry = `
1089
+ **${capitalize(employee.name)} (${employee.role}):** Newly hired. Update this description as the role develops.
1090
+ `;
1091
+ let updated;
1092
+ if (nextHeading && nextHeading.index !== void 0) {
1093
+ const insertAt = teamMatch.index + teamMatch[0].length + nextHeading.index;
1094
+ updated = content.slice(0, insertAt) + entry + content.slice(insertAt);
1095
+ } else {
1096
+ updated = content.trimEnd() + "\n" + entry;
1097
+ }
1098
+ writeFileSync4(idPath, updated, "utf-8");
1099
+ }
1100
+ function capitalize(s) {
1101
+ return s.charAt(0).toUpperCase() + s.slice(1);
1102
+ }
1103
+ async function hireEmployee(employee) {
1104
+ const employees = await loadEmployees();
1105
+ const updated = addEmployee(employees, employee);
1106
+ await saveEmployees(updated);
1107
+ try {
1108
+ appendToCoordinatorTeam(employee);
1109
+ } catch {
1110
+ }
1111
+ try {
1112
+ const { loadAgentConfig: loadAgentConfig2, saveAgentConfig: saveAgentConfig2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
1113
+ const config = loadAgentConfig2();
1114
+ const name = employee.name.toLowerCase();
1115
+ if (!config[name] && config["default"]) {
1116
+ config[name] = { ...config["default"] };
1117
+ saveAgentConfig2(config);
1118
+ }
1119
+ } catch {
1120
+ }
1121
+ return updated;
1122
+ }
1123
+ async function normalizeRosterCase(rosterPath) {
1124
+ const employees = await loadEmployees(rosterPath);
1125
+ let changed = false;
1126
+ for (const emp of employees) {
1127
+ if (emp.name !== emp.name.toLowerCase()) {
1128
+ const oldName = emp.name;
1129
+ emp.name = emp.name.toLowerCase();
1130
+ changed = true;
1131
+ try {
1132
+ const identityDir = path5.join(os4.homedir(), ".exe-os", "identity");
1133
+ const oldPath = path5.join(identityDir, `${oldName}.md`);
1134
+ const newPath = path5.join(identityDir, `${emp.name}.md`);
1135
+ if (existsSync6(oldPath) && !existsSync6(newPath)) {
1136
+ renameSync3(oldPath, newPath);
1137
+ } else if (existsSync6(oldPath) && oldPath !== newPath) {
1138
+ const content = readFileSync5(oldPath, "utf-8");
1139
+ writeFileSync4(newPath, content, "utf-8");
1140
+ if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
1141
+ unlinkSync(oldPath);
1142
+ }
1143
+ }
1144
+ } catch {
1145
+ }
1146
+ }
1147
+ }
1148
+ if (changed) {
1149
+ await saveEmployees(employees, rosterPath);
1150
+ }
1151
+ return changed;
1152
+ }
1153
+ function findExeBin() {
1154
+ try {
1155
+ return execSync3(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
1156
+ } catch {
1157
+ return null;
1158
+ }
1159
+ }
1160
+ function registerBinSymlinks(name) {
1161
+ const created = [];
1162
+ const skipped = [];
1163
+ const errors = [];
1164
+ const exeBinPath = findExeBin();
1165
+ if (!exeBinPath) {
1166
+ errors.push("Could not find 'exe-os' in PATH");
1167
+ return { created, skipped, errors };
1168
+ }
1169
+ const binDir = path5.dirname(exeBinPath);
1170
+ let target;
1171
+ try {
1172
+ target = readlinkSync(exeBinPath);
1173
+ } catch {
1174
+ errors.push("Could not read 'exe' symlink");
1175
+ return { created, skipped, errors };
1176
+ }
1177
+ for (const suffix of ["", "-opencode"]) {
1178
+ const linkName = `${name}${suffix}`;
1179
+ const linkPath = path5.join(binDir, linkName);
1180
+ if (existsSync6(linkPath)) {
1181
+ skipped.push(linkName);
1182
+ continue;
1183
+ }
1184
+ try {
1185
+ symlinkSync(target, linkPath);
1186
+ created.push(linkName);
1187
+ } catch (err) {
1188
+ errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
1189
+ }
1190
+ }
1191
+ return { created, skipped, errors };
1192
+ }
1193
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR, TEAM_SECTION_RE;
940
1194
  var init_employees = __esm({
941
1195
  "src/lib/employees.ts"() {
942
1196
  "use strict";
@@ -946,6 +1200,7 @@ var init_employees = __esm({
946
1200
  COORDINATOR_ROLE = "COO";
947
1201
  MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
948
1202
  IDENTITY_DIR = path5.join(EXE_AI_DIR, "identity");
1203
+ TEAM_SECTION_RE = /^## Team\b.*$/m;
949
1204
  }
950
1205
  });
951
1206
 
@@ -3850,6 +4105,22 @@ async function ensureSchema() {
3850
4105
  } catch (e) {
3851
4106
  logCatchDebug("migration", e);
3852
4107
  }
4108
+ try {
4109
+ await client.execute({
4110
+ sql: `ALTER TABLE memories ADD COLUMN visibility TEXT DEFAULT 'private'`,
4111
+ args: []
4112
+ });
4113
+ } catch (e) {
4114
+ logCatchDebug("migration", e);
4115
+ }
4116
+ try {
4117
+ await client.execute({
4118
+ sql: `ALTER TABLE memories ADD COLUMN strength REAL DEFAULT 1.0`,
4119
+ args: []
4120
+ });
4121
+ } catch (e) {
4122
+ logCatchDebug("migration", e);
4123
+ }
3853
4124
  }
3854
4125
  async function disposeDatabase() {
3855
4126
  if (_walCheckpointTimer) {
@@ -4268,7 +4539,7 @@ async function assertVpsLicense(opts) {
4268
4539
  }
4269
4540
  if (!transientFailure) {
4270
4541
  throw new Error(
4271
- "License validation failed: unknown backend state. Restore network connectivity to https://askexe.com/cloud and retry."
4542
+ "License validation failed: unknown backend state. Restore network connectivity to https://cloud.askexe.com and retry."
4272
4543
  );
4273
4544
  }
4274
4545
  const fresh = await getCachedLicense();
@@ -4305,7 +4576,7 @@ async function assertVpsLicense(opts) {
4305
4576
  } catch {
4306
4577
  }
4307
4578
  throw new Error(
4308
- `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.`
4579
+ `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.`
4309
4580
  );
4310
4581
  }
4311
4582
  function startLicenseRevalidation(intervalMs = 36e5) {
@@ -4337,7 +4608,7 @@ var init_license = __esm({
4337
4608
  LICENSE_PATH = path9.join(EXE_AI_DIR, "license.key");
4338
4609
  CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
4339
4610
  DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
4340
- API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com/cloud";
4611
+ API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://cloud.askexe.com";
4341
4612
  RETRY_DELAY_MS = 500;
4342
4613
  LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
4343
4614
  MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
@@ -4851,6 +5122,19 @@ async function resolveTask(client, identifier, scopeSession) {
4851
5122
  args: [identifier, ...scope.args]
4852
5123
  });
4853
5124
  if (result.rows.length === 1) return result.rows[0];
5125
+ if (/^[a-f0-9]{7,12}$/i.test(identifier)) {
5126
+ result = await client.execute({
5127
+ sql: `SELECT * FROM tasks WHERE id LIKE ?`,
5128
+ args: [`${identifier}%`]
5129
+ });
5130
+ if (result.rows.length === 1) return result.rows[0];
5131
+ if (result.rows.length > 1) {
5132
+ const matches = result.rows.map((r) => `${String(r.id)} "${String(r.title)}" (${String(r.status)})`).join(", ");
5133
+ throw new Error(
5134
+ `Multiple tasks match short-ID "${identifier}": ${matches}. Use a longer prefix to disambiguate.`
5135
+ );
5136
+ }
5137
+ }
4854
5138
  result = await client.execute({
4855
5139
  sql: `SELECT * FROM tasks WHERE task_file LIKE ?${scope.sql}`,
4856
5140
  args: [`%${identifier}%`, ...scope.args]
@@ -5705,12 +5989,13 @@ async function cascadeUnblock(taskId, baseDir, now) {
5705
5989
  WHERE blocked_by = ? AND status = 'blocked'`,
5706
5990
  args: [now, taskId]
5707
5991
  });
5708
- if (baseDir && unblocked.rowsAffected > 0) {
5709
- const ubScope = sessionScopeFilter();
5710
- const unblockedRows = await client.execute({
5711
- sql: `SELECT task_file FROM tasks WHERE blocked_by IS NULL AND updated_at = ?${ubScope.sql}`,
5712
- args: [now, ...ubScope.args]
5713
- });
5992
+ if (unblocked.rowsAffected === 0) return;
5993
+ const ubScope = sessionScopeFilter();
5994
+ const unblockedRows = await client.execute({
5995
+ sql: `SELECT id, title, assigned_to, priority, task_file FROM tasks WHERE blocked_by IS NULL AND updated_at = ?${ubScope.sql}`,
5996
+ args: [now, ...ubScope.args]
5997
+ });
5998
+ if (baseDir) {
5714
5999
  for (const ur of unblockedRows.rows) {
5715
6000
  try {
5716
6001
  const ubFile = path16.join(baseDir, String(ur.task_file));
@@ -5722,6 +6007,19 @@ async function cascadeUnblock(taskId, baseDir, now) {
5722
6007
  }
5723
6008
  }
5724
6009
  }
6010
+ if (unblockedRows.rows.length > 0 && !process.env.VITEST) {
6011
+ try {
6012
+ const { queueIntercom: queueIntercom2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
6013
+ const dispatched = /* @__PURE__ */ new Set();
6014
+ for (const ur of unblockedRows.rows) {
6015
+ const assignee = String(ur.assigned_to);
6016
+ if (dispatched.has(assignee)) continue;
6017
+ dispatched.add(assignee);
6018
+ queueIntercom2(`${assignee}`, `unblocked: "${String(ur.title)}" is now ready`);
6019
+ }
6020
+ } catch {
6021
+ }
6022
+ }
5725
6023
  }
5726
6024
  async function findNextTask(assignedTo) {
5727
6025
  const client = getClient();
@@ -5931,6 +6229,15 @@ var init_embedder = __esm({
5931
6229
  // src/lib/behaviors.ts
5932
6230
  import crypto5 from "crypto";
5933
6231
  async function storeBehavior(opts) {
6232
+ try {
6233
+ const { loadEmployeesSync: loadEmployeesSync2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
6234
+ const roster = loadEmployeesSync2();
6235
+ if (roster.length > 0 && !roster.some((e) => e.name === opts.agentId)) {
6236
+ throw new Error(`Agent "${opts.agentId}" not found in roster. Cannot store behavior for unregistered agent.`);
6237
+ }
6238
+ } catch (e) {
6239
+ if (e instanceof Error && e.message.includes("not found in roster")) throw e;
6240
+ }
5934
6241
  const client = getClient();
5935
6242
  const id = crypto5.randomUUID();
5936
6243
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -6384,6 +6691,12 @@ async function updateTask(input) {
6384
6691
  }
6385
6692
  }
6386
6693
  }
6694
+ if (input.status === "cancelled") {
6695
+ try {
6696
+ await cascadeUnblock(taskId, input.baseDir, now);
6697
+ } catch {
6698
+ }
6699
+ }
6387
6700
  if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
6388
6701
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
6389
6702
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
@@ -6915,11 +7228,12 @@ function getDispatchedBy(sessionKey) {
6915
7228
  }
6916
7229
  }
6917
7230
  function resolveExeSession() {
7231
+ if (process.env.EXE_SESSION_NAME) {
7232
+ const fromEnv = extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
7233
+ if (fromEnv) return fromEnv;
7234
+ }
6918
7235
  const mySession = getMySession();
6919
7236
  if (!mySession) {
6920
- if (process.env.EXE_SESSION_NAME) {
6921
- return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
6922
- }
6923
7237
  return null;
6924
7238
  }
6925
7239
  const fromSessionName = extractRootExe(mySession);
@@ -6934,6 +7248,10 @@ function resolveExeSession() {
6934
7248
  `[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
6935
7249
  `
6936
7250
  );
7251
+ try {
7252
+ registerParentExe(key, fromSessionName);
7253
+ } catch {
7254
+ }
6937
7255
  candidate = fromSessionName;
6938
7256
  } else {
6939
7257
  candidate = fromCache;
@@ -8661,11 +8979,17 @@ var init_platform_procedures = __esm({
8661
8979
  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."
8662
8980
  },
8663
8981
  {
8664
- title: "Customer orchestration maturity \u2014 recommend, never trap",
8982
+ title: "Orchestration phase guidance \u2014 recommend, never trap",
8665
8983
  domain: "workflow",
8666
8984
  priority: "p1",
8667
8985
  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."
8668
8986
  },
8987
+ {
8988
+ title: "Routing slot vs display title \u2014 internal 'coo' is plumbing, not your name",
8989
+ domain: "identity",
8990
+ priority: "p0",
8991
+ 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."
8992
+ },
8669
8993
  {
8670
8994
  title: "Single dispatch path \u2014 create_task only",
8671
8995
  domain: "workflow",
@@ -8699,6 +9023,12 @@ var init_platform_procedures = __esm({
8699
9023
  priority: "p0",
8700
9024
  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."
8701
9025
  },
9026
+ {
9027
+ title: "Destructive operations \u2014 mandatory reviewer gate",
9028
+ domain: "security",
9029
+ priority: "p0",
9030
+ 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."
9031
+ },
8702
9032
  {
8703
9033
  title: "Customer patch triage \u2014 upstream bug vs customization",
8704
9034
  domain: "support",
@@ -8984,10 +9314,24 @@ function stableId(memoryId, type, content) {
8984
9314
  return createHash2("sha256").update(`${memoryId}:${type}:${content}`).digest("hex").slice(0, 32);
8985
9315
  }
8986
9316
  function cleanText(text) {
8987
- return text.replace(/```[\s\S]*?```/g, " ").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
9317
+ let cleaned = text.replace(
9318
+ /```(\w*)\n(.*?)(?:\n[\s\S]*?)```/g,
9319
+ (_m, lang, firstLine) => `[code${lang ? `:${lang}` : ""}] ${firstLine.trim()}`
9320
+ );
9321
+ cleaned = cleaned.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
9322
+ return cleaned;
8988
9323
  }
8989
- function splitSentences(text) {
8990
- return cleanText(text).split(/(?<=[.!?])\s+|\n+/).map((s) => s.trim()).filter((s) => s.length >= 24 && s.length <= MAX_SENTENCE_CHARS);
9324
+ function splitSegments(text) {
9325
+ const cleaned = cleanText(text);
9326
+ 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);
9327
+ if (segments.length === 0 && cleaned.length >= MIN_SEGMENT_CHARS) {
9328
+ const lines = cleaned.split(/\n+/).map((l) => l.trim()).filter((l) => l.length >= MIN_SEGMENT_CHARS && l.length <= MAX_SEGMENT_CHARS);
9329
+ if (lines.length > 0) return lines;
9330
+ if (cleaned.length >= MIN_SEGMENT_CHARS) {
9331
+ return [cleaned.slice(0, MAX_SEGMENT_CHARS)];
9332
+ }
9333
+ }
9334
+ return segments;
8991
9335
  }
8992
9336
  function inferCardType(sentence, toolName) {
8993
9337
  const lower = sentence.toLowerCase();
@@ -9019,12 +9363,12 @@ function predicateFor(type) {
9019
9363
  }
9020
9364
  }
9021
9365
  function extractMemoryCards(row) {
9022
- const sentences = splitSentences(row.raw_text);
9366
+ const segments = splitSegments(row.raw_text);
9023
9367
  const cards = [];
9024
- for (const sentence of sentences) {
9368
+ for (const sentence of segments) {
9025
9369
  const type = inferCardType(sentence, row.tool_name);
9026
9370
  const subject = extractSubject(sentence, row.agent_id);
9027
- const content = sentence.length > MAX_SENTENCE_CHARS ? `${sentence.slice(0, MAX_SENTENCE_CHARS - 1)}\u2026` : sentence;
9371
+ const content = sentence.length > MAX_SEGMENT_CHARS ? `${sentence.slice(0, MAX_SEGMENT_CHARS - 1)}\u2026` : sentence;
9028
9372
  cards.push({
9029
9373
  id: stableId(row.id, type, content),
9030
9374
  memory_id: row.id,
@@ -9120,13 +9464,14 @@ Source memory: ${String(row.source_ref ?? row.memory_id)}`,
9120
9464
  last_accessed: String(row.timestamp)
9121
9465
  }));
9122
9466
  }
9123
- var MAX_CARDS_PER_MEMORY, MAX_SENTENCE_CHARS;
9467
+ var MAX_CARDS_PER_MEMORY, MAX_SEGMENT_CHARS, MIN_SEGMENT_CHARS;
9124
9468
  var init_memory_cards = __esm({
9125
9469
  "src/lib/memory-cards.ts"() {
9126
9470
  "use strict";
9127
9471
  init_database();
9128
- MAX_CARDS_PER_MEMORY = 6;
9129
- MAX_SENTENCE_CHARS = 360;
9472
+ MAX_CARDS_PER_MEMORY = 8;
9473
+ MAX_SEGMENT_CHARS = 500;
9474
+ MIN_SEGMENT_CHARS = 20;
9130
9475
  }
9131
9476
  });
9132
9477