@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.
- package/README.md +9 -7
- package/dist/bin/agentic-ontology-backfill.js +54 -11
- package/dist/bin/agentic-reflection-backfill.js +29 -1
- package/dist/bin/agentic-semantic-label.js +29 -1
- package/dist/bin/backfill-conversations.js +53 -10
- package/dist/bin/backfill-responses.js +54 -11
- package/dist/bin/backfill-vectors.js +29 -1
- package/dist/bin/bulk-sync-postgres.js +55 -12
- package/dist/bin/cleanup-stale-review-tasks.js +75 -15
- package/dist/bin/cli.js +293 -76
- package/dist/bin/exe-agent-config.js +7 -1
- package/dist/bin/exe-agent.js +28 -2
- package/dist/bin/exe-assign.js +54 -11
- package/dist/bin/exe-boot.js +481 -147
- package/dist/bin/exe-call.js +45 -4
- package/dist/bin/exe-cloud.js +93 -15
- package/dist/bin/exe-dispatch.js +369 -24
- package/dist/bin/exe-doctor.js +53 -10
- package/dist/bin/exe-export-behaviors.js +54 -11
- package/dist/bin/exe-forget.js +54 -11
- package/dist/bin/exe-gateway.js +128 -23
- package/dist/bin/exe-heartbeat.js +75 -15
- package/dist/bin/exe-kill.js +54 -11
- package/dist/bin/exe-launch-agent.js +70 -12
- package/dist/bin/exe-new-employee.js +175 -7
- package/dist/bin/exe-pending-messages.js +75 -15
- package/dist/bin/exe-pending-notifications.js +75 -15
- package/dist/bin/exe-pending-reviews.js +75 -15
- package/dist/bin/exe-rename.js +54 -11
- package/dist/bin/exe-review.js +54 -11
- package/dist/bin/exe-search.js +54 -11
- package/dist/bin/exe-session-cleanup.js +491 -146
- package/dist/bin/exe-settings.js +10 -4
- package/dist/bin/exe-start-codex.js +524 -245
- package/dist/bin/exe-start-opencode.js +534 -165
- package/dist/bin/exe-status.js +75 -15
- package/dist/bin/exe-support.js +1 -1
- package/dist/bin/exe-team.js +54 -11
- package/dist/bin/git-sweep.js +369 -24
- package/dist/bin/graph-backfill.js +54 -11
- package/dist/bin/graph-export.js +54 -11
- package/dist/bin/install.js +62 -4
- package/dist/bin/intercom-check.js +491 -146
- package/dist/bin/pre-publish.js +13 -1
- package/dist/bin/scan-tasks.js +369 -24
- package/dist/bin/setup.js +91 -13
- package/dist/bin/shard-migrate.js +54 -11
- package/dist/bin/stack-update.js +1 -1
- package/dist/bin/update.js +3 -3
- package/dist/gateway/index.js +128 -23
- package/dist/hooks/bug-report-worker.js +128 -23
- package/dist/hooks/codex-stop-task-finalizer.js +512 -140
- package/dist/hooks/commit-complete.js +369 -24
- package/dist/hooks/error-recall.js +54 -11
- package/dist/hooks/ingest.js +4575 -252
- package/dist/hooks/instructions-loaded.js +54 -11
- package/dist/hooks/notification.js +54 -11
- package/dist/hooks/post-compact.js +75 -15
- package/dist/hooks/post-tool-combined.js +75 -15
- package/dist/hooks/pre-compact.js +449 -104
- package/dist/hooks/pre-tool-use.js +90 -15
- package/dist/hooks/prompt-submit.js +129 -24
- package/dist/hooks/session-end.js +451 -109
- package/dist/hooks/session-start.js +104 -16
- package/dist/hooks/stop.js +74 -14
- package/dist/hooks/subagent-stop.js +75 -15
- package/dist/hooks/summary-worker.js +73 -7
- package/dist/index.js +128 -23
- package/dist/lib/agent-config.js +16 -1
- package/dist/lib/cloud-sync.js +38 -1
- package/dist/lib/consolidation.js +16 -1
- package/dist/lib/database.js +16 -0
- package/dist/lib/db.js +16 -0
- package/dist/lib/device-registry.js +16 -0
- package/dist/lib/employee-templates.js +29 -3
- package/dist/lib/employees.js +16 -1
- package/dist/lib/exe-daemon.js +268 -42
- package/dist/lib/hybrid-search.js +54 -11
- package/dist/lib/license.js +3 -3
- package/dist/lib/messaging.js +21 -4
- package/dist/lib/schedules.js +29 -1
- package/dist/lib/skill-learning.js +458 -70
- package/dist/lib/status-brief.js +14 -1
- package/dist/lib/store.js +54 -11
- package/dist/lib/tasks.js +393 -91
- package/dist/lib/tmux-routing.js +316 -14
- package/dist/mcp/server.js +169 -30
- package/dist/mcp/tools/create-task.js +75 -13
- package/dist/mcp/tools/deactivate-behavior.js +33 -24
- package/dist/mcp/tools/list-tasks.js +21 -4
- package/dist/mcp/tools/send-message.js +21 -4
- package/dist/mcp/tools/update-task.js +390 -91
- package/dist/runtime/index.js +446 -101
- package/dist/tui/App.js +208 -54
- package/package.json +1 -1
package/dist/bin/exe-dispatch.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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 (
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
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: "
|
|
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
|
-
|
|
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
|
|
8969
|
-
|
|
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
|
|
9345
|
+
const segments = splitSegments(row.raw_text);
|
|
9002
9346
|
const cards = [];
|
|
9003
|
-
for (const sentence of
|
|
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 >
|
|
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,
|
|
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 =
|
|
9108
|
-
|
|
9451
|
+
MAX_CARDS_PER_MEMORY = 8;
|
|
9452
|
+
MAX_SEGMENT_CHARS = 500;
|
|
9453
|
+
MIN_SEGMENT_CHARS = 20;
|
|
9109
9454
|
}
|
|
9110
9455
|
});
|
|
9111
9456
|
|