@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
|
@@ -650,6 +650,19 @@ var init_runtime_table = __esm({
|
|
|
650
650
|
});
|
|
651
651
|
|
|
652
652
|
// src/lib/agent-config.ts
|
|
653
|
+
var agent_config_exports = {};
|
|
654
|
+
__export(agent_config_exports, {
|
|
655
|
+
AGENT_CONFIG_PATH: () => AGENT_CONFIG_PATH,
|
|
656
|
+
DEFAULT_MODELS: () => DEFAULT_MODELS,
|
|
657
|
+
KNOWN_RUNTIMES: () => KNOWN_RUNTIMES,
|
|
658
|
+
RUNTIME_LABELS: () => RUNTIME_LABELS,
|
|
659
|
+
clearAgentRuntime: () => clearAgentRuntime,
|
|
660
|
+
getAgentRuntime: () => getAgentRuntime,
|
|
661
|
+
loadAgentConfig: () => loadAgentConfig,
|
|
662
|
+
saveAgentConfig: () => saveAgentConfig,
|
|
663
|
+
setAgentMcps: () => setAgentMcps,
|
|
664
|
+
setAgentRuntime: () => setAgentRuntime
|
|
665
|
+
});
|
|
653
666
|
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync4 } from "fs";
|
|
654
667
|
import path3 from "path";
|
|
655
668
|
function loadAgentConfig() {
|
|
@@ -660,6 +673,12 @@ function loadAgentConfig() {
|
|
|
660
673
|
return {};
|
|
661
674
|
}
|
|
662
675
|
}
|
|
676
|
+
function saveAgentConfig(config) {
|
|
677
|
+
const dir = path3.dirname(AGENT_CONFIG_PATH);
|
|
678
|
+
ensurePrivateDirSync(dir);
|
|
679
|
+
writeFileSync2(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
680
|
+
enforcePrivateFileSync(AGENT_CONFIG_PATH);
|
|
681
|
+
}
|
|
663
682
|
function getAgentRuntime(agentId) {
|
|
664
683
|
const config = loadAgentConfig();
|
|
665
684
|
const entry = config[agentId];
|
|
@@ -668,7 +687,47 @@ function getAgentRuntime(agentId) {
|
|
|
668
687
|
if (orgDefault) return orgDefault;
|
|
669
688
|
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
670
689
|
}
|
|
671
|
-
|
|
690
|
+
function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
|
|
691
|
+
const knownModels = KNOWN_RUNTIMES[runtime];
|
|
692
|
+
if (!knownModels) {
|
|
693
|
+
return {
|
|
694
|
+
ok: false,
|
|
695
|
+
error: `Unknown runtime "${runtime}". Valid: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
if (!knownModels.includes(model)) {
|
|
699
|
+
return {
|
|
700
|
+
ok: false,
|
|
701
|
+
error: `Unknown model "${model}" for runtime "${runtime}". Valid: ${knownModels.join(", ")}`
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
const config = loadAgentConfig();
|
|
705
|
+
const existing = config[agentId];
|
|
706
|
+
const entry = { runtime, model };
|
|
707
|
+
if (reasoning_effort) entry.reasoning_effort = reasoning_effort;
|
|
708
|
+
if (mcps !== void 0) {
|
|
709
|
+
entry.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
|
|
710
|
+
} else if (existing?.mcps) {
|
|
711
|
+
entry.mcps = existing.mcps;
|
|
712
|
+
}
|
|
713
|
+
config[agentId] = entry;
|
|
714
|
+
saveAgentConfig(config);
|
|
715
|
+
return { ok: true };
|
|
716
|
+
}
|
|
717
|
+
function setAgentMcps(agentId, mcps) {
|
|
718
|
+
const config = loadAgentConfig();
|
|
719
|
+
const existing = config[agentId] ?? getAgentRuntime(agentId);
|
|
720
|
+
existing.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
|
|
721
|
+
config[agentId] = existing;
|
|
722
|
+
saveAgentConfig(config);
|
|
723
|
+
return { ok: true };
|
|
724
|
+
}
|
|
725
|
+
function clearAgentRuntime(agentId) {
|
|
726
|
+
const config = loadAgentConfig();
|
|
727
|
+
delete config[agentId];
|
|
728
|
+
saveAgentConfig(config);
|
|
729
|
+
}
|
|
730
|
+
var AGENT_CONFIG_PATH, KNOWN_RUNTIMES, RUNTIME_LABELS, DEFAULT_MODELS;
|
|
672
731
|
var init_agent_config = __esm({
|
|
673
732
|
"src/lib/agent-config.ts"() {
|
|
674
733
|
"use strict";
|
|
@@ -676,6 +735,16 @@ var init_agent_config = __esm({
|
|
|
676
735
|
init_runtime_table();
|
|
677
736
|
init_secure_files();
|
|
678
737
|
AGENT_CONFIG_PATH = path3.join(EXE_AI_DIR, "agent-config.json");
|
|
738
|
+
KNOWN_RUNTIMES = {
|
|
739
|
+
claude: ["claude-opus-4.6", "claude-opus-4", "claude-sonnet-4.6", "claude-sonnet-4", "claude-haiku-4.5"],
|
|
740
|
+
codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
|
|
741
|
+
opencode: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4", "google/gemini-2.5-pro", "deepseek/deepseek-r3", "minimax/minimax-m2.5"]
|
|
742
|
+
};
|
|
743
|
+
RUNTIME_LABELS = {
|
|
744
|
+
claude: "Claude Code (Anthropic)",
|
|
745
|
+
codex: "Codex (OpenAI)",
|
|
746
|
+
opencode: "OpenCode (open source)"
|
|
747
|
+
};
|
|
679
748
|
DEFAULT_MODELS = {
|
|
680
749
|
claude: "claude-opus-4.6",
|
|
681
750
|
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
@@ -866,6 +935,32 @@ var init_db_retry = __esm({
|
|
|
866
935
|
});
|
|
867
936
|
|
|
868
937
|
// src/lib/employees.ts
|
|
938
|
+
var employees_exports = {};
|
|
939
|
+
__export(employees_exports, {
|
|
940
|
+
COORDINATOR_ROLE: () => COORDINATOR_ROLE,
|
|
941
|
+
DEFAULT_COORDINATOR_TEMPLATE_NAME: () => DEFAULT_COORDINATOR_TEMPLATE_NAME,
|
|
942
|
+
EMPLOYEES_PATH: () => EMPLOYEES_PATH,
|
|
943
|
+
addEmployee: () => addEmployee,
|
|
944
|
+
baseAgentName: () => baseAgentName,
|
|
945
|
+
canCoordinate: () => canCoordinate,
|
|
946
|
+
getCoordinatorEmployee: () => getCoordinatorEmployee,
|
|
947
|
+
getCoordinatorName: () => getCoordinatorName,
|
|
948
|
+
getEmployee: () => getEmployee,
|
|
949
|
+
getEmployeeByRole: () => getEmployeeByRole,
|
|
950
|
+
getEmployeeNamesByRole: () => getEmployeeNamesByRole,
|
|
951
|
+
hasRole: () => hasRole,
|
|
952
|
+
hireEmployee: () => hireEmployee,
|
|
953
|
+
isCoordinatorName: () => isCoordinatorName,
|
|
954
|
+
isCoordinatorRole: () => isCoordinatorRole,
|
|
955
|
+
isMultiInstance: () => isMultiInstance,
|
|
956
|
+
loadEmployees: () => loadEmployees,
|
|
957
|
+
loadEmployeesSync: () => loadEmployeesSync,
|
|
958
|
+
normalizeRole: () => normalizeRole,
|
|
959
|
+
normalizeRosterCase: () => normalizeRosterCase,
|
|
960
|
+
registerBinSymlinks: () => registerBinSymlinks,
|
|
961
|
+
saveEmployees: () => saveEmployees,
|
|
962
|
+
validateEmployeeName: () => validateEmployeeName
|
|
963
|
+
});
|
|
869
964
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
870
965
|
import { existsSync as existsSync6, symlinkSync, readlinkSync, readFileSync as readFileSync5, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
|
|
871
966
|
import { execSync as execSync3 } from "child_process";
|
|
@@ -887,6 +982,24 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
|
|
|
887
982
|
if (!agentName) return false;
|
|
888
983
|
return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
|
|
889
984
|
}
|
|
985
|
+
function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
|
|
986
|
+
return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
|
|
987
|
+
}
|
|
988
|
+
function validateEmployeeName(name) {
|
|
989
|
+
if (!name) {
|
|
990
|
+
return { valid: false, error: "Name is required" };
|
|
991
|
+
}
|
|
992
|
+
if (name.length > 32) {
|
|
993
|
+
return { valid: false, error: "Name must be 32 characters or fewer" };
|
|
994
|
+
}
|
|
995
|
+
if (!/^[a-z][a-z0-9]*$/.test(name)) {
|
|
996
|
+
return {
|
|
997
|
+
valid: false,
|
|
998
|
+
error: "Name must start with a letter and contain only lowercase alphanumeric characters"
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
return { valid: true };
|
|
1002
|
+
}
|
|
890
1003
|
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
891
1004
|
if (!existsSync6(employeesPath)) {
|
|
892
1005
|
return [];
|
|
@@ -898,6 +1011,10 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
|
898
1011
|
return [];
|
|
899
1012
|
}
|
|
900
1013
|
}
|
|
1014
|
+
async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
1015
|
+
await mkdir2(path5.dirname(employeesPath), { recursive: true });
|
|
1016
|
+
await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
1017
|
+
}
|
|
901
1018
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
902
1019
|
if (!existsSync6(employeesPath)) return [];
|
|
903
1020
|
try {
|
|
@@ -909,6 +1026,19 @@ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
|
909
1026
|
function getEmployee(employees, name) {
|
|
910
1027
|
return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
911
1028
|
}
|
|
1029
|
+
function getEmployeeByRole(employees, role) {
|
|
1030
|
+
const lower = role.toLowerCase();
|
|
1031
|
+
return employees.find((e) => e.role.toLowerCase() === lower);
|
|
1032
|
+
}
|
|
1033
|
+
function getEmployeeNamesByRole(employees, role) {
|
|
1034
|
+
const lower = role.toLowerCase();
|
|
1035
|
+
return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
|
|
1036
|
+
}
|
|
1037
|
+
function hasRole(agentName, role) {
|
|
1038
|
+
const employees = loadEmployeesSync();
|
|
1039
|
+
const emp = getEmployee(employees, agentName);
|
|
1040
|
+
return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
|
|
1041
|
+
}
|
|
912
1042
|
function baseAgentName(name, employees) {
|
|
913
1043
|
const match = name.match(/^([a-zA-Z]+)\d+$/);
|
|
914
1044
|
if (!match) return name;
|
|
@@ -923,7 +1053,131 @@ function isMultiInstance(agentName, employees) {
|
|
|
923
1053
|
if (!emp) return false;
|
|
924
1054
|
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
925
1055
|
}
|
|
926
|
-
|
|
1056
|
+
function addEmployee(employees, employee) {
|
|
1057
|
+
const { systemPrompt: _legacyPrompt, ...rest } = employee;
|
|
1058
|
+
const normalized = { ...rest, name: employee.name.toLowerCase() };
|
|
1059
|
+
if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
|
|
1060
|
+
throw new Error(`Employee '${normalized.name}' already exists`);
|
|
1061
|
+
}
|
|
1062
|
+
return [...employees, normalized];
|
|
1063
|
+
}
|
|
1064
|
+
function appendToCoordinatorTeam(employee) {
|
|
1065
|
+
const coordinator = getCoordinatorEmployee(loadEmployeesSync());
|
|
1066
|
+
if (!coordinator) return;
|
|
1067
|
+
const idPath = path5.join(IDENTITY_DIR, `${coordinator.name}.md`);
|
|
1068
|
+
if (!existsSync6(idPath)) return;
|
|
1069
|
+
const content = readFileSync5(idPath, "utf-8");
|
|
1070
|
+
if (content.includes(`**${capitalize(employee.name)}`)) return;
|
|
1071
|
+
const teamMatch = content.match(TEAM_SECTION_RE);
|
|
1072
|
+
if (!teamMatch || teamMatch.index === void 0) return;
|
|
1073
|
+
const afterTeam = content.slice(teamMatch.index + teamMatch[0].length);
|
|
1074
|
+
const nextHeading = afterTeam.match(/\n## /);
|
|
1075
|
+
const entry = `
|
|
1076
|
+
**${capitalize(employee.name)} (${employee.role}):** Newly hired. Update this description as the role develops.
|
|
1077
|
+
`;
|
|
1078
|
+
let updated;
|
|
1079
|
+
if (nextHeading && nextHeading.index !== void 0) {
|
|
1080
|
+
const insertAt = teamMatch.index + teamMatch[0].length + nextHeading.index;
|
|
1081
|
+
updated = content.slice(0, insertAt) + entry + content.slice(insertAt);
|
|
1082
|
+
} else {
|
|
1083
|
+
updated = content.trimEnd() + "\n" + entry;
|
|
1084
|
+
}
|
|
1085
|
+
writeFileSync4(idPath, updated, "utf-8");
|
|
1086
|
+
}
|
|
1087
|
+
function capitalize(s) {
|
|
1088
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
1089
|
+
}
|
|
1090
|
+
async function hireEmployee(employee) {
|
|
1091
|
+
const employees = await loadEmployees();
|
|
1092
|
+
const updated = addEmployee(employees, employee);
|
|
1093
|
+
await saveEmployees(updated);
|
|
1094
|
+
try {
|
|
1095
|
+
appendToCoordinatorTeam(employee);
|
|
1096
|
+
} catch {
|
|
1097
|
+
}
|
|
1098
|
+
try {
|
|
1099
|
+
const { loadAgentConfig: loadAgentConfig2, saveAgentConfig: saveAgentConfig2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
|
|
1100
|
+
const config = loadAgentConfig2();
|
|
1101
|
+
const name = employee.name.toLowerCase();
|
|
1102
|
+
if (!config[name] && config["default"]) {
|
|
1103
|
+
config[name] = { ...config["default"] };
|
|
1104
|
+
saveAgentConfig2(config);
|
|
1105
|
+
}
|
|
1106
|
+
} catch {
|
|
1107
|
+
}
|
|
1108
|
+
return updated;
|
|
1109
|
+
}
|
|
1110
|
+
async function normalizeRosterCase(rosterPath) {
|
|
1111
|
+
const employees = await loadEmployees(rosterPath);
|
|
1112
|
+
let changed = false;
|
|
1113
|
+
for (const emp of employees) {
|
|
1114
|
+
if (emp.name !== emp.name.toLowerCase()) {
|
|
1115
|
+
const oldName = emp.name;
|
|
1116
|
+
emp.name = emp.name.toLowerCase();
|
|
1117
|
+
changed = true;
|
|
1118
|
+
try {
|
|
1119
|
+
const identityDir = path5.join(os4.homedir(), ".exe-os", "identity");
|
|
1120
|
+
const oldPath = path5.join(identityDir, `${oldName}.md`);
|
|
1121
|
+
const newPath = path5.join(identityDir, `${emp.name}.md`);
|
|
1122
|
+
if (existsSync6(oldPath) && !existsSync6(newPath)) {
|
|
1123
|
+
renameSync3(oldPath, newPath);
|
|
1124
|
+
} else if (existsSync6(oldPath) && oldPath !== newPath) {
|
|
1125
|
+
const content = readFileSync5(oldPath, "utf-8");
|
|
1126
|
+
writeFileSync4(newPath, content, "utf-8");
|
|
1127
|
+
if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
|
|
1128
|
+
unlinkSync(oldPath);
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
} catch {
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
if (changed) {
|
|
1136
|
+
await saveEmployees(employees, rosterPath);
|
|
1137
|
+
}
|
|
1138
|
+
return changed;
|
|
1139
|
+
}
|
|
1140
|
+
function findExeBin() {
|
|
1141
|
+
try {
|
|
1142
|
+
return execSync3(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
|
|
1143
|
+
} catch {
|
|
1144
|
+
return null;
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
function registerBinSymlinks(name) {
|
|
1148
|
+
const created = [];
|
|
1149
|
+
const skipped = [];
|
|
1150
|
+
const errors = [];
|
|
1151
|
+
const exeBinPath = findExeBin();
|
|
1152
|
+
if (!exeBinPath) {
|
|
1153
|
+
errors.push("Could not find 'exe-os' in PATH");
|
|
1154
|
+
return { created, skipped, errors };
|
|
1155
|
+
}
|
|
1156
|
+
const binDir = path5.dirname(exeBinPath);
|
|
1157
|
+
let target;
|
|
1158
|
+
try {
|
|
1159
|
+
target = readlinkSync(exeBinPath);
|
|
1160
|
+
} catch {
|
|
1161
|
+
errors.push("Could not read 'exe' symlink");
|
|
1162
|
+
return { created, skipped, errors };
|
|
1163
|
+
}
|
|
1164
|
+
for (const suffix of ["", "-opencode"]) {
|
|
1165
|
+
const linkName = `${name}${suffix}`;
|
|
1166
|
+
const linkPath = path5.join(binDir, linkName);
|
|
1167
|
+
if (existsSync6(linkPath)) {
|
|
1168
|
+
skipped.push(linkName);
|
|
1169
|
+
continue;
|
|
1170
|
+
}
|
|
1171
|
+
try {
|
|
1172
|
+
symlinkSync(target, linkPath);
|
|
1173
|
+
created.push(linkName);
|
|
1174
|
+
} catch (err) {
|
|
1175
|
+
errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
return { created, skipped, errors };
|
|
1179
|
+
}
|
|
1180
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR, TEAM_SECTION_RE;
|
|
927
1181
|
var init_employees = __esm({
|
|
928
1182
|
"src/lib/employees.ts"() {
|
|
929
1183
|
"use strict";
|
|
@@ -933,6 +1187,7 @@ var init_employees = __esm({
|
|
|
933
1187
|
COORDINATOR_ROLE = "COO";
|
|
934
1188
|
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
935
1189
|
IDENTITY_DIR = path5.join(EXE_AI_DIR, "identity");
|
|
1190
|
+
TEAM_SECTION_RE = /^## Team\b.*$/m;
|
|
936
1191
|
}
|
|
937
1192
|
});
|
|
938
1193
|
|
|
@@ -3837,6 +4092,22 @@ async function ensureSchema() {
|
|
|
3837
4092
|
} catch (e) {
|
|
3838
4093
|
logCatchDebug("migration", e);
|
|
3839
4094
|
}
|
|
4095
|
+
try {
|
|
4096
|
+
await client.execute({
|
|
4097
|
+
sql: `ALTER TABLE memories ADD COLUMN visibility TEXT DEFAULT 'private'`,
|
|
4098
|
+
args: []
|
|
4099
|
+
});
|
|
4100
|
+
} catch (e) {
|
|
4101
|
+
logCatchDebug("migration", e);
|
|
4102
|
+
}
|
|
4103
|
+
try {
|
|
4104
|
+
await client.execute({
|
|
4105
|
+
sql: `ALTER TABLE memories ADD COLUMN strength REAL DEFAULT 1.0`,
|
|
4106
|
+
args: []
|
|
4107
|
+
});
|
|
4108
|
+
} catch (e) {
|
|
4109
|
+
logCatchDebug("migration", e);
|
|
4110
|
+
}
|
|
3840
4111
|
}
|
|
3841
4112
|
async function disposeDatabase() {
|
|
3842
4113
|
if (_walCheckpointTimer) {
|
|
@@ -4255,7 +4526,7 @@ async function assertVpsLicense(opts) {
|
|
|
4255
4526
|
}
|
|
4256
4527
|
if (!transientFailure) {
|
|
4257
4528
|
throw new Error(
|
|
4258
|
-
"License validation failed: unknown backend state. Restore network connectivity to https://askexe.com
|
|
4529
|
+
"License validation failed: unknown backend state. Restore network connectivity to https://cloud.askexe.com and retry."
|
|
4259
4530
|
);
|
|
4260
4531
|
}
|
|
4261
4532
|
const fresh = await getCachedLicense();
|
|
@@ -4292,7 +4563,7 @@ async function assertVpsLicense(opts) {
|
|
|
4292
4563
|
} catch {
|
|
4293
4564
|
}
|
|
4294
4565
|
throw new Error(
|
|
4295
|
-
`License validation unreachable for more than ${graceDays} days. Restore network connectivity to https://askexe.com
|
|
4566
|
+
`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.`
|
|
4296
4567
|
);
|
|
4297
4568
|
}
|
|
4298
4569
|
function startLicenseRevalidation(intervalMs = 36e5) {
|
|
@@ -4324,7 +4595,7 @@ var init_license = __esm({
|
|
|
4324
4595
|
LICENSE_PATH = path9.join(EXE_AI_DIR, "license.key");
|
|
4325
4596
|
CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
|
|
4326
4597
|
DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
|
|
4327
|
-
API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com
|
|
4598
|
+
API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://cloud.askexe.com";
|
|
4328
4599
|
RETRY_DELAY_MS = 500;
|
|
4329
4600
|
LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
4330
4601
|
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
|
|
@@ -4845,6 +5116,19 @@ async function resolveTask(client, identifier, scopeSession) {
|
|
|
4845
5116
|
args: [identifier, ...scope.args]
|
|
4846
5117
|
});
|
|
4847
5118
|
if (result.rows.length === 1) return result.rows[0];
|
|
5119
|
+
if (/^[a-f0-9]{7,12}$/i.test(identifier)) {
|
|
5120
|
+
result = await client.execute({
|
|
5121
|
+
sql: `SELECT * FROM tasks WHERE id LIKE ?`,
|
|
5122
|
+
args: [`${identifier}%`]
|
|
5123
|
+
});
|
|
5124
|
+
if (result.rows.length === 1) return result.rows[0];
|
|
5125
|
+
if (result.rows.length > 1) {
|
|
5126
|
+
const matches = result.rows.map((r) => `${String(r.id)} "${String(r.title)}" (${String(r.status)})`).join(", ");
|
|
5127
|
+
throw new Error(
|
|
5128
|
+
`Multiple tasks match short-ID "${identifier}": ${matches}. Use a longer prefix to disambiguate.`
|
|
5129
|
+
);
|
|
5130
|
+
}
|
|
5131
|
+
}
|
|
4848
5132
|
result = await client.execute({
|
|
4849
5133
|
sql: `SELECT * FROM tasks WHERE task_file LIKE ?${scope.sql}`,
|
|
4850
5134
|
args: [`%${identifier}%`, ...scope.args]
|
|
@@ -5699,12 +5983,13 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
5699
5983
|
WHERE blocked_by = ? AND status = 'blocked'`,
|
|
5700
5984
|
args: [now, taskId]
|
|
5701
5985
|
});
|
|
5702
|
-
if (
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
5707
|
-
|
|
5986
|
+
if (unblocked.rowsAffected === 0) return;
|
|
5987
|
+
const ubScope = sessionScopeFilter();
|
|
5988
|
+
const unblockedRows = await client.execute({
|
|
5989
|
+
sql: `SELECT id, title, assigned_to, priority, task_file FROM tasks WHERE blocked_by IS NULL AND updated_at = ?${ubScope.sql}`,
|
|
5990
|
+
args: [now, ...ubScope.args]
|
|
5991
|
+
});
|
|
5992
|
+
if (baseDir) {
|
|
5708
5993
|
for (const ur of unblockedRows.rows) {
|
|
5709
5994
|
try {
|
|
5710
5995
|
const ubFile = path16.join(baseDir, String(ur.task_file));
|
|
@@ -5716,6 +6001,19 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
5716
6001
|
}
|
|
5717
6002
|
}
|
|
5718
6003
|
}
|
|
6004
|
+
if (unblockedRows.rows.length > 0 && !process.env.VITEST) {
|
|
6005
|
+
try {
|
|
6006
|
+
const { queueIntercom: queueIntercom2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
6007
|
+
const dispatched = /* @__PURE__ */ new Set();
|
|
6008
|
+
for (const ur of unblockedRows.rows) {
|
|
6009
|
+
const assignee = String(ur.assigned_to);
|
|
6010
|
+
if (dispatched.has(assignee)) continue;
|
|
6011
|
+
dispatched.add(assignee);
|
|
6012
|
+
queueIntercom2(`${assignee}`, `unblocked: "${String(ur.title)}" is now ready`);
|
|
6013
|
+
}
|
|
6014
|
+
} catch {
|
|
6015
|
+
}
|
|
6016
|
+
}
|
|
5719
6017
|
}
|
|
5720
6018
|
async function findNextTask(assignedTo) {
|
|
5721
6019
|
const client = getClient();
|
|
@@ -5925,6 +6223,15 @@ var init_embedder = __esm({
|
|
|
5925
6223
|
// src/lib/behaviors.ts
|
|
5926
6224
|
import crypto5 from "crypto";
|
|
5927
6225
|
async function storeBehavior(opts) {
|
|
6226
|
+
try {
|
|
6227
|
+
const { loadEmployeesSync: loadEmployeesSync2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
|
|
6228
|
+
const roster = loadEmployeesSync2();
|
|
6229
|
+
if (roster.length > 0 && !roster.some((e) => e.name === opts.agentId)) {
|
|
6230
|
+
throw new Error(`Agent "${opts.agentId}" not found in roster. Cannot store behavior for unregistered agent.`);
|
|
6231
|
+
}
|
|
6232
|
+
} catch (e) {
|
|
6233
|
+
if (e instanceof Error && e.message.includes("not found in roster")) throw e;
|
|
6234
|
+
}
|
|
5928
6235
|
const client = getClient();
|
|
5929
6236
|
const id = crypto5.randomUUID();
|
|
5930
6237
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -6378,6 +6685,12 @@ async function updateTask(input) {
|
|
|
6378
6685
|
}
|
|
6379
6686
|
}
|
|
6380
6687
|
}
|
|
6688
|
+
if (input.status === "cancelled") {
|
|
6689
|
+
try {
|
|
6690
|
+
await cascadeUnblock(taskId, input.baseDir, now);
|
|
6691
|
+
} catch {
|
|
6692
|
+
}
|
|
6693
|
+
}
|
|
6381
6694
|
if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
6382
6695
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
6383
6696
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
@@ -6909,11 +7222,12 @@ function getDispatchedBy(sessionKey) {
|
|
|
6909
7222
|
}
|
|
6910
7223
|
}
|
|
6911
7224
|
function resolveExeSession() {
|
|
7225
|
+
if (process.env.EXE_SESSION_NAME) {
|
|
7226
|
+
const fromEnv = extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
|
|
7227
|
+
if (fromEnv) return fromEnv;
|
|
7228
|
+
}
|
|
6912
7229
|
const mySession = getMySession();
|
|
6913
7230
|
if (!mySession) {
|
|
6914
|
-
if (process.env.EXE_SESSION_NAME) {
|
|
6915
|
-
return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
|
|
6916
|
-
}
|
|
6917
7231
|
return null;
|
|
6918
7232
|
}
|
|
6919
7233
|
const fromSessionName = extractRootExe(mySession);
|
|
@@ -6928,6 +7242,10 @@ function resolveExeSession() {
|
|
|
6928
7242
|
`[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
|
|
6929
7243
|
`
|
|
6930
7244
|
);
|
|
7245
|
+
try {
|
|
7246
|
+
registerParentExe(key, fromSessionName);
|
|
7247
|
+
} catch {
|
|
7248
|
+
}
|
|
6931
7249
|
candidate = fromSessionName;
|
|
6932
7250
|
} else {
|
|
6933
7251
|
candidate = fromCache;
|
|
@@ -8655,11 +8973,17 @@ var init_platform_procedures = __esm({
|
|
|
8655
8973
|
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."
|
|
8656
8974
|
},
|
|
8657
8975
|
{
|
|
8658
|
-
title: "
|
|
8976
|
+
title: "Orchestration phase guidance \u2014 recommend, never trap",
|
|
8659
8977
|
domain: "workflow",
|
|
8660
8978
|
priority: "p1",
|
|
8661
8979
|
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."
|
|
8662
8980
|
},
|
|
8981
|
+
{
|
|
8982
|
+
title: "Routing slot vs display title \u2014 internal 'coo' is plumbing, not your name",
|
|
8983
|
+
domain: "identity",
|
|
8984
|
+
priority: "p0",
|
|
8985
|
+
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."
|
|
8986
|
+
},
|
|
8663
8987
|
{
|
|
8664
8988
|
title: "Single dispatch path \u2014 create_task only",
|
|
8665
8989
|
domain: "workflow",
|
|
@@ -8693,6 +9017,12 @@ var init_platform_procedures = __esm({
|
|
|
8693
9017
|
priority: "p0",
|
|
8694
9018
|
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."
|
|
8695
9019
|
},
|
|
9020
|
+
{
|
|
9021
|
+
title: "Destructive operations \u2014 mandatory reviewer gate",
|
|
9022
|
+
domain: "security",
|
|
9023
|
+
priority: "p0",
|
|
9024
|
+
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."
|
|
9025
|
+
},
|
|
8696
9026
|
{
|
|
8697
9027
|
title: "Customer patch triage \u2014 upstream bug vs customization",
|
|
8698
9028
|
domain: "support",
|
|
@@ -8978,10 +9308,24 @@ function stableId(memoryId, type, content) {
|
|
|
8978
9308
|
return createHash2("sha256").update(`${memoryId}:${type}:${content}`).digest("hex").slice(0, 32);
|
|
8979
9309
|
}
|
|
8980
9310
|
function cleanText(text) {
|
|
8981
|
-
|
|
9311
|
+
let cleaned = text.replace(
|
|
9312
|
+
/```(\w*)\n(.*?)(?:\n[\s\S]*?)```/g,
|
|
9313
|
+
(_m, lang, firstLine) => `[code${lang ? `:${lang}` : ""}] ${firstLine.trim()}`
|
|
9314
|
+
);
|
|
9315
|
+
cleaned = cleaned.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
|
9316
|
+
return cleaned;
|
|
8982
9317
|
}
|
|
8983
|
-
function
|
|
8984
|
-
|
|
9318
|
+
function splitSegments(text) {
|
|
9319
|
+
const cleaned = cleanText(text);
|
|
9320
|
+
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);
|
|
9321
|
+
if (segments.length === 0 && cleaned.length >= MIN_SEGMENT_CHARS) {
|
|
9322
|
+
const lines = cleaned.split(/\n+/).map((l) => l.trim()).filter((l) => l.length >= MIN_SEGMENT_CHARS && l.length <= MAX_SEGMENT_CHARS);
|
|
9323
|
+
if (lines.length > 0) return lines;
|
|
9324
|
+
if (cleaned.length >= MIN_SEGMENT_CHARS) {
|
|
9325
|
+
return [cleaned.slice(0, MAX_SEGMENT_CHARS)];
|
|
9326
|
+
}
|
|
9327
|
+
}
|
|
9328
|
+
return segments;
|
|
8985
9329
|
}
|
|
8986
9330
|
function inferCardType(sentence, toolName) {
|
|
8987
9331
|
const lower = sentence.toLowerCase();
|
|
@@ -9013,12 +9357,12 @@ function predicateFor(type) {
|
|
|
9013
9357
|
}
|
|
9014
9358
|
}
|
|
9015
9359
|
function extractMemoryCards(row) {
|
|
9016
|
-
const
|
|
9360
|
+
const segments = splitSegments(row.raw_text);
|
|
9017
9361
|
const cards = [];
|
|
9018
|
-
for (const sentence of
|
|
9362
|
+
for (const sentence of segments) {
|
|
9019
9363
|
const type = inferCardType(sentence, row.tool_name);
|
|
9020
9364
|
const subject = extractSubject(sentence, row.agent_id);
|
|
9021
|
-
const content = sentence.length >
|
|
9365
|
+
const content = sentence.length > MAX_SEGMENT_CHARS ? `${sentence.slice(0, MAX_SEGMENT_CHARS - 1)}\u2026` : sentence;
|
|
9022
9366
|
cards.push({
|
|
9023
9367
|
id: stableId(row.id, type, content),
|
|
9024
9368
|
memory_id: row.id,
|
|
@@ -9114,13 +9458,14 @@ Source memory: ${String(row.source_ref ?? row.memory_id)}`,
|
|
|
9114
9458
|
last_accessed: String(row.timestamp)
|
|
9115
9459
|
}));
|
|
9116
9460
|
}
|
|
9117
|
-
var MAX_CARDS_PER_MEMORY,
|
|
9461
|
+
var MAX_CARDS_PER_MEMORY, MAX_SEGMENT_CHARS, MIN_SEGMENT_CHARS;
|
|
9118
9462
|
var init_memory_cards = __esm({
|
|
9119
9463
|
"src/lib/memory-cards.ts"() {
|
|
9120
9464
|
"use strict";
|
|
9121
9465
|
init_database();
|
|
9122
|
-
MAX_CARDS_PER_MEMORY =
|
|
9123
|
-
|
|
9466
|
+
MAX_CARDS_PER_MEMORY = 8;
|
|
9467
|
+
MAX_SEGMENT_CHARS = 500;
|
|
9468
|
+
MIN_SEGMENT_CHARS = 20;
|
|
9124
9469
|
}
|
|
9125
9470
|
});
|
|
9126
9471
|
|