@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/git-sweep.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
|
|
|
@@ -3772,6 +4027,22 @@ async function ensureSchema() {
|
|
|
3772
4027
|
} catch (e) {
|
|
3773
4028
|
logCatchDebug("migration", e);
|
|
3774
4029
|
}
|
|
4030
|
+
try {
|
|
4031
|
+
await client.execute({
|
|
4032
|
+
sql: `ALTER TABLE memories ADD COLUMN visibility TEXT DEFAULT 'private'`,
|
|
4033
|
+
args: []
|
|
4034
|
+
});
|
|
4035
|
+
} catch (e) {
|
|
4036
|
+
logCatchDebug("migration", e);
|
|
4037
|
+
}
|
|
4038
|
+
try {
|
|
4039
|
+
await client.execute({
|
|
4040
|
+
sql: `ALTER TABLE memories ADD COLUMN strength REAL DEFAULT 1.0`,
|
|
4041
|
+
args: []
|
|
4042
|
+
});
|
|
4043
|
+
} catch (e) {
|
|
4044
|
+
logCatchDebug("migration", e);
|
|
4045
|
+
}
|
|
3775
4046
|
}
|
|
3776
4047
|
async function disposeDatabase() {
|
|
3777
4048
|
if (_walCheckpointTimer) {
|
|
@@ -4190,7 +4461,7 @@ async function assertVpsLicense(opts) {
|
|
|
4190
4461
|
}
|
|
4191
4462
|
if (!transientFailure) {
|
|
4192
4463
|
throw new Error(
|
|
4193
|
-
"License validation failed: unknown backend state. Restore network connectivity to https://askexe.com
|
|
4464
|
+
"License validation failed: unknown backend state. Restore network connectivity to https://cloud.askexe.com and retry."
|
|
4194
4465
|
);
|
|
4195
4466
|
}
|
|
4196
4467
|
const fresh = await getCachedLicense();
|
|
@@ -4227,7 +4498,7 @@ async function assertVpsLicense(opts) {
|
|
|
4227
4498
|
} catch {
|
|
4228
4499
|
}
|
|
4229
4500
|
throw new Error(
|
|
4230
|
-
`License validation unreachable for more than ${graceDays} days. Restore network connectivity to https://askexe.com
|
|
4501
|
+
`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.`
|
|
4231
4502
|
);
|
|
4232
4503
|
}
|
|
4233
4504
|
function startLicenseRevalidation(intervalMs = 36e5) {
|
|
@@ -4259,7 +4530,7 @@ var init_license = __esm({
|
|
|
4259
4530
|
LICENSE_PATH = path9.join(EXE_AI_DIR, "license.key");
|
|
4260
4531
|
CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
|
|
4261
4532
|
DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
|
|
4262
|
-
API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com
|
|
4533
|
+
API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://cloud.askexe.com";
|
|
4263
4534
|
RETRY_DELAY_MS = 500;
|
|
4264
4535
|
LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
4265
4536
|
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
|
|
@@ -4780,6 +5051,19 @@ async function resolveTask(client, identifier, scopeSession) {
|
|
|
4780
5051
|
args: [identifier, ...scope.args]
|
|
4781
5052
|
});
|
|
4782
5053
|
if (result.rows.length === 1) return result.rows[0];
|
|
5054
|
+
if (/^[a-f0-9]{7,12}$/i.test(identifier)) {
|
|
5055
|
+
result = await client.execute({
|
|
5056
|
+
sql: `SELECT * FROM tasks WHERE id LIKE ?`,
|
|
5057
|
+
args: [`${identifier}%`]
|
|
5058
|
+
});
|
|
5059
|
+
if (result.rows.length === 1) return result.rows[0];
|
|
5060
|
+
if (result.rows.length > 1) {
|
|
5061
|
+
const matches = result.rows.map((r) => `${String(r.id)} "${String(r.title)}" (${String(r.status)})`).join(", ");
|
|
5062
|
+
throw new Error(
|
|
5063
|
+
`Multiple tasks match short-ID "${identifier}": ${matches}. Use a longer prefix to disambiguate.`
|
|
5064
|
+
);
|
|
5065
|
+
}
|
|
5066
|
+
}
|
|
4783
5067
|
result = await client.execute({
|
|
4784
5068
|
sql: `SELECT * FROM tasks WHERE task_file LIKE ?${scope.sql}`,
|
|
4785
5069
|
args: [`%${identifier}%`, ...scope.args]
|
|
@@ -5634,12 +5918,13 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
5634
5918
|
WHERE blocked_by = ? AND status = 'blocked'`,
|
|
5635
5919
|
args: [now, taskId]
|
|
5636
5920
|
});
|
|
5637
|
-
if (
|
|
5638
|
-
|
|
5639
|
-
|
|
5640
|
-
|
|
5641
|
-
|
|
5642
|
-
|
|
5921
|
+
if (unblocked.rowsAffected === 0) return;
|
|
5922
|
+
const ubScope = sessionScopeFilter();
|
|
5923
|
+
const unblockedRows = await client.execute({
|
|
5924
|
+
sql: `SELECT id, title, assigned_to, priority, task_file FROM tasks WHERE blocked_by IS NULL AND updated_at = ?${ubScope.sql}`,
|
|
5925
|
+
args: [now, ...ubScope.args]
|
|
5926
|
+
});
|
|
5927
|
+
if (baseDir) {
|
|
5643
5928
|
for (const ur of unblockedRows.rows) {
|
|
5644
5929
|
try {
|
|
5645
5930
|
const ubFile = path16.join(baseDir, String(ur.task_file));
|
|
@@ -5651,6 +5936,19 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
5651
5936
|
}
|
|
5652
5937
|
}
|
|
5653
5938
|
}
|
|
5939
|
+
if (unblockedRows.rows.length > 0 && !process.env.VITEST) {
|
|
5940
|
+
try {
|
|
5941
|
+
const { queueIntercom: queueIntercom2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
5942
|
+
const dispatched = /* @__PURE__ */ new Set();
|
|
5943
|
+
for (const ur of unblockedRows.rows) {
|
|
5944
|
+
const assignee = String(ur.assigned_to);
|
|
5945
|
+
if (dispatched.has(assignee)) continue;
|
|
5946
|
+
dispatched.add(assignee);
|
|
5947
|
+
queueIntercom2(`${assignee}`, `unblocked: "${String(ur.title)}" is now ready`);
|
|
5948
|
+
}
|
|
5949
|
+
} catch {
|
|
5950
|
+
}
|
|
5951
|
+
}
|
|
5654
5952
|
}
|
|
5655
5953
|
async function findNextTask(assignedTo) {
|
|
5656
5954
|
const client = getClient();
|
|
@@ -5860,6 +6158,15 @@ var init_embedder = __esm({
|
|
|
5860
6158
|
// src/lib/behaviors.ts
|
|
5861
6159
|
import crypto5 from "crypto";
|
|
5862
6160
|
async function storeBehavior(opts) {
|
|
6161
|
+
try {
|
|
6162
|
+
const { loadEmployeesSync: loadEmployeesSync2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
|
|
6163
|
+
const roster = loadEmployeesSync2();
|
|
6164
|
+
if (roster.length > 0 && !roster.some((e) => e.name === opts.agentId)) {
|
|
6165
|
+
throw new Error(`Agent "${opts.agentId}" not found in roster. Cannot store behavior for unregistered agent.`);
|
|
6166
|
+
}
|
|
6167
|
+
} catch (e) {
|
|
6168
|
+
if (e instanceof Error && e.message.includes("not found in roster")) throw e;
|
|
6169
|
+
}
|
|
5863
6170
|
const client = getClient();
|
|
5864
6171
|
const id = crypto5.randomUUID();
|
|
5865
6172
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -6313,6 +6620,12 @@ async function updateTask(input) {
|
|
|
6313
6620
|
}
|
|
6314
6621
|
}
|
|
6315
6622
|
}
|
|
6623
|
+
if (input.status === "cancelled") {
|
|
6624
|
+
try {
|
|
6625
|
+
await cascadeUnblock(taskId, input.baseDir, now);
|
|
6626
|
+
} catch {
|
|
6627
|
+
}
|
|
6628
|
+
}
|
|
6316
6629
|
if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
6317
6630
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
6318
6631
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
@@ -6844,11 +7157,12 @@ function getDispatchedBy(sessionKey) {
|
|
|
6844
7157
|
}
|
|
6845
7158
|
}
|
|
6846
7159
|
function resolveExeSession() {
|
|
7160
|
+
if (process.env.EXE_SESSION_NAME) {
|
|
7161
|
+
const fromEnv = extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
|
|
7162
|
+
if (fromEnv) return fromEnv;
|
|
7163
|
+
}
|
|
6847
7164
|
const mySession = getMySession();
|
|
6848
7165
|
if (!mySession) {
|
|
6849
|
-
if (process.env.EXE_SESSION_NAME) {
|
|
6850
|
-
return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
|
|
6851
|
-
}
|
|
6852
7166
|
return null;
|
|
6853
7167
|
}
|
|
6854
7168
|
const fromSessionName = extractRootExe(mySession);
|
|
@@ -6863,6 +7177,10 @@ function resolveExeSession() {
|
|
|
6863
7177
|
`[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
|
|
6864
7178
|
`
|
|
6865
7179
|
);
|
|
7180
|
+
try {
|
|
7181
|
+
registerParentExe(key, fromSessionName);
|
|
7182
|
+
} catch {
|
|
7183
|
+
}
|
|
6866
7184
|
candidate = fromSessionName;
|
|
6867
7185
|
} else {
|
|
6868
7186
|
candidate = fromCache;
|
|
@@ -8590,11 +8908,17 @@ var init_platform_procedures = __esm({
|
|
|
8590
8908
|
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."
|
|
8591
8909
|
},
|
|
8592
8910
|
{
|
|
8593
|
-
title: "
|
|
8911
|
+
title: "Orchestration phase guidance \u2014 recommend, never trap",
|
|
8594
8912
|
domain: "workflow",
|
|
8595
8913
|
priority: "p1",
|
|
8596
8914
|
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."
|
|
8597
8915
|
},
|
|
8916
|
+
{
|
|
8917
|
+
title: "Routing slot vs display title \u2014 internal 'coo' is plumbing, not your name",
|
|
8918
|
+
domain: "identity",
|
|
8919
|
+
priority: "p0",
|
|
8920
|
+
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."
|
|
8921
|
+
},
|
|
8598
8922
|
{
|
|
8599
8923
|
title: "Single dispatch path \u2014 create_task only",
|
|
8600
8924
|
domain: "workflow",
|
|
@@ -8628,6 +8952,12 @@ var init_platform_procedures = __esm({
|
|
|
8628
8952
|
priority: "p0",
|
|
8629
8953
|
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."
|
|
8630
8954
|
},
|
|
8955
|
+
{
|
|
8956
|
+
title: "Destructive operations \u2014 mandatory reviewer gate",
|
|
8957
|
+
domain: "security",
|
|
8958
|
+
priority: "p0",
|
|
8959
|
+
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."
|
|
8960
|
+
},
|
|
8631
8961
|
{
|
|
8632
8962
|
title: "Customer patch triage \u2014 upstream bug vs customization",
|
|
8633
8963
|
domain: "support",
|
|
@@ -8913,10 +9243,24 @@ function stableId(memoryId, type, content) {
|
|
|
8913
9243
|
return createHash2("sha256").update(`${memoryId}:${type}:${content}`).digest("hex").slice(0, 32);
|
|
8914
9244
|
}
|
|
8915
9245
|
function cleanText(text) {
|
|
8916
|
-
|
|
9246
|
+
let cleaned = text.replace(
|
|
9247
|
+
/```(\w*)\n(.*?)(?:\n[\s\S]*?)```/g,
|
|
9248
|
+
(_m, lang, firstLine) => `[code${lang ? `:${lang}` : ""}] ${firstLine.trim()}`
|
|
9249
|
+
);
|
|
9250
|
+
cleaned = cleaned.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
|
9251
|
+
return cleaned;
|
|
8917
9252
|
}
|
|
8918
|
-
function
|
|
8919
|
-
|
|
9253
|
+
function splitSegments(text) {
|
|
9254
|
+
const cleaned = cleanText(text);
|
|
9255
|
+
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);
|
|
9256
|
+
if (segments.length === 0 && cleaned.length >= MIN_SEGMENT_CHARS) {
|
|
9257
|
+
const lines = cleaned.split(/\n+/).map((l) => l.trim()).filter((l) => l.length >= MIN_SEGMENT_CHARS && l.length <= MAX_SEGMENT_CHARS);
|
|
9258
|
+
if (lines.length > 0) return lines;
|
|
9259
|
+
if (cleaned.length >= MIN_SEGMENT_CHARS) {
|
|
9260
|
+
return [cleaned.slice(0, MAX_SEGMENT_CHARS)];
|
|
9261
|
+
}
|
|
9262
|
+
}
|
|
9263
|
+
return segments;
|
|
8920
9264
|
}
|
|
8921
9265
|
function inferCardType(sentence, toolName) {
|
|
8922
9266
|
const lower = sentence.toLowerCase();
|
|
@@ -8948,12 +9292,12 @@ function predicateFor(type) {
|
|
|
8948
9292
|
}
|
|
8949
9293
|
}
|
|
8950
9294
|
function extractMemoryCards(row) {
|
|
8951
|
-
const
|
|
9295
|
+
const segments = splitSegments(row.raw_text);
|
|
8952
9296
|
const cards = [];
|
|
8953
|
-
for (const sentence of
|
|
9297
|
+
for (const sentence of segments) {
|
|
8954
9298
|
const type = inferCardType(sentence, row.tool_name);
|
|
8955
9299
|
const subject = extractSubject(sentence, row.agent_id);
|
|
8956
|
-
const content = sentence.length >
|
|
9300
|
+
const content = sentence.length > MAX_SEGMENT_CHARS ? `${sentence.slice(0, MAX_SEGMENT_CHARS - 1)}\u2026` : sentence;
|
|
8957
9301
|
cards.push({
|
|
8958
9302
|
id: stableId(row.id, type, content),
|
|
8959
9303
|
memory_id: row.id,
|
|
@@ -9049,13 +9393,14 @@ Source memory: ${String(row.source_ref ?? row.memory_id)}`,
|
|
|
9049
9393
|
last_accessed: String(row.timestamp)
|
|
9050
9394
|
}));
|
|
9051
9395
|
}
|
|
9052
|
-
var MAX_CARDS_PER_MEMORY,
|
|
9396
|
+
var MAX_CARDS_PER_MEMORY, MAX_SEGMENT_CHARS, MIN_SEGMENT_CHARS;
|
|
9053
9397
|
var init_memory_cards = __esm({
|
|
9054
9398
|
"src/lib/memory-cards.ts"() {
|
|
9055
9399
|
"use strict";
|
|
9056
9400
|
init_database();
|
|
9057
|
-
MAX_CARDS_PER_MEMORY =
|
|
9058
|
-
|
|
9401
|
+
MAX_CARDS_PER_MEMORY = 8;
|
|
9402
|
+
MAX_SEGMENT_CHARS = 500;
|
|
9403
|
+
MIN_SEGMENT_CHARS = 20;
|
|
9059
9404
|
}
|
|
9060
9405
|
});
|
|
9061
9406
|
|