@askexenow/exe-os 0.9.111 → 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 +62 -12
- package/dist/bin/agentic-reflection-backfill.js +37 -2
- package/dist/bin/agentic-semantic-label.js +37 -2
- package/dist/bin/backfill-conversations.js +61 -11
- package/dist/bin/backfill-responses.js +62 -12
- package/dist/bin/backfill-vectors.js +37 -2
- package/dist/bin/bulk-sync-postgres.js +63 -13
- package/dist/bin/cleanup-stale-review-tasks.js +83 -16
- package/dist/bin/cli.js +312 -80
- package/dist/bin/exe-agent-config.js +7 -1
- package/dist/bin/exe-agent.js +29 -3
- package/dist/bin/exe-assign.js +62 -12
- package/dist/bin/exe-boot.js +500 -151
- package/dist/bin/exe-call.js +46 -5
- package/dist/bin/exe-cloud.js +101 -16
- package/dist/bin/exe-dispatch.js +827 -27
- package/dist/bin/exe-doctor.js +61 -11
- package/dist/bin/exe-export-behaviors.js +67 -14
- package/dist/bin/exe-forget.js +62 -12
- package/dist/bin/exe-gateway.js +147 -27
- package/dist/bin/exe-heartbeat.js +83 -16
- package/dist/bin/exe-kill.js +62 -12
- package/dist/bin/exe-launch-agent.js +83 -15
- package/dist/bin/exe-new-employee.js +176 -8
- package/dist/bin/exe-pending-messages.js +83 -16
- package/dist/bin/exe-pending-notifications.js +83 -16
- package/dist/bin/exe-pending-reviews.js +83 -16
- package/dist/bin/exe-rename.js +62 -12
- package/dist/bin/exe-review.js +62 -12
- package/dist/bin/exe-search.js +62 -12
- package/dist/bin/exe-session-cleanup.js +949 -149
- package/dist/bin/exe-settings.js +10 -4
- package/dist/bin/exe-start-codex.js +537 -248
- package/dist/bin/exe-start-opencode.js +547 -168
- package/dist/bin/exe-status.js +83 -16
- package/dist/bin/exe-support.js +1 -1
- package/dist/bin/exe-team.js +62 -12
- package/dist/bin/git-sweep.js +827 -27
- package/dist/bin/graph-backfill.js +62 -12
- package/dist/bin/graph-export.js +62 -12
- package/dist/bin/install.js +62 -4
- package/dist/bin/intercom-check.js +949 -149
- package/dist/bin/pre-publish.js +14 -2
- package/dist/bin/scan-tasks.js +827 -27
- package/dist/bin/setup.js +99 -14
- package/dist/bin/shard-migrate.js +62 -12
- package/dist/bin/stack-update.js +1 -1
- package/dist/bin/update.js +3 -3
- package/dist/gateway/index.js +586 -26
- package/dist/hooks/bug-report-worker.js +586 -26
- package/dist/hooks/codex-stop-task-finalizer.js +977 -143
- package/dist/hooks/commit-complete.js +827 -27
- package/dist/hooks/error-recall.js +62 -12
- package/dist/hooks/ingest.js +4579 -249
- package/dist/hooks/instructions-loaded.js +62 -12
- package/dist/hooks/notification.js +62 -12
- package/dist/hooks/post-compact.js +83 -16
- package/dist/hooks/post-tool-combined.js +83 -16
- package/dist/hooks/pre-compact.js +907 -107
- package/dist/hooks/pre-tool-use.js +98 -16
- package/dist/hooks/prompt-submit.js +596 -30
- package/dist/hooks/session-end.js +909 -112
- package/dist/hooks/session-start.js +112 -17
- package/dist/hooks/stop.js +82 -15
- package/dist/hooks/subagent-stop.js +83 -16
- package/dist/hooks/summary-worker.js +81 -8
- package/dist/index.js +595 -29
- package/dist/lib/agent-config.js +16 -1
- package/dist/lib/cloud-sync.js +45 -1
- package/dist/lib/consolidation.js +16 -1
- package/dist/lib/database.js +23 -0
- package/dist/lib/db.js +23 -0
- package/dist/lib/device-registry.js +23 -0
- package/dist/lib/employee-templates.js +30 -4
- package/dist/lib/employees.js +16 -1
- package/dist/lib/exe-daemon.js +482 -52
- package/dist/lib/hybrid-search.js +62 -12
- package/dist/lib/license.js +3 -3
- package/dist/lib/messaging.js +21 -4
- package/dist/lib/schedules.js +37 -2
- package/dist/lib/skill-learning.js +910 -41
- package/dist/lib/status-brief.js +14 -1
- package/dist/lib/store.js +62 -12
- package/dist/lib/tasks.js +843 -93
- package/dist/lib/tmux-routing.js +766 -16
- package/dist/mcp/server.js +238 -41
- package/dist/mcp/tools/create-task.js +525 -15
- 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 +840 -93
- package/dist/runtime/index.js +913 -107
- package/dist/tui/App.js +227 -58
- package/package.json +1 -1
package/dist/lib/tmux-routing.js
CHANGED
|
@@ -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",
|
|
@@ -818,6 +887,32 @@ var init_db_retry = __esm({
|
|
|
818
887
|
});
|
|
819
888
|
|
|
820
889
|
// src/lib/employees.ts
|
|
890
|
+
var employees_exports = {};
|
|
891
|
+
__export(employees_exports, {
|
|
892
|
+
COORDINATOR_ROLE: () => COORDINATOR_ROLE,
|
|
893
|
+
DEFAULT_COORDINATOR_TEMPLATE_NAME: () => DEFAULT_COORDINATOR_TEMPLATE_NAME,
|
|
894
|
+
EMPLOYEES_PATH: () => EMPLOYEES_PATH,
|
|
895
|
+
addEmployee: () => addEmployee,
|
|
896
|
+
baseAgentName: () => baseAgentName,
|
|
897
|
+
canCoordinate: () => canCoordinate,
|
|
898
|
+
getCoordinatorEmployee: () => getCoordinatorEmployee,
|
|
899
|
+
getCoordinatorName: () => getCoordinatorName,
|
|
900
|
+
getEmployee: () => getEmployee,
|
|
901
|
+
getEmployeeByRole: () => getEmployeeByRole,
|
|
902
|
+
getEmployeeNamesByRole: () => getEmployeeNamesByRole,
|
|
903
|
+
hasRole: () => hasRole,
|
|
904
|
+
hireEmployee: () => hireEmployee,
|
|
905
|
+
isCoordinatorName: () => isCoordinatorName,
|
|
906
|
+
isCoordinatorRole: () => isCoordinatorRole,
|
|
907
|
+
isMultiInstance: () => isMultiInstance,
|
|
908
|
+
loadEmployees: () => loadEmployees,
|
|
909
|
+
loadEmployeesSync: () => loadEmployeesSync,
|
|
910
|
+
normalizeRole: () => normalizeRole,
|
|
911
|
+
normalizeRosterCase: () => normalizeRosterCase,
|
|
912
|
+
registerBinSymlinks: () => registerBinSymlinks,
|
|
913
|
+
saveEmployees: () => saveEmployees,
|
|
914
|
+
validateEmployeeName: () => validateEmployeeName
|
|
915
|
+
});
|
|
821
916
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
822
917
|
import { existsSync as existsSync6, symlinkSync, readlinkSync, readFileSync as readFileSync5, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
|
|
823
918
|
import { execSync as execSync3 } from "child_process";
|
|
@@ -839,6 +934,24 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
|
|
|
839
934
|
if (!agentName) return false;
|
|
840
935
|
return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
|
|
841
936
|
}
|
|
937
|
+
function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
|
|
938
|
+
return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
|
|
939
|
+
}
|
|
940
|
+
function validateEmployeeName(name) {
|
|
941
|
+
if (!name) {
|
|
942
|
+
return { valid: false, error: "Name is required" };
|
|
943
|
+
}
|
|
944
|
+
if (name.length > 32) {
|
|
945
|
+
return { valid: false, error: "Name must be 32 characters or fewer" };
|
|
946
|
+
}
|
|
947
|
+
if (!/^[a-z][a-z0-9]*$/.test(name)) {
|
|
948
|
+
return {
|
|
949
|
+
valid: false,
|
|
950
|
+
error: "Name must start with a letter and contain only lowercase alphanumeric characters"
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
return { valid: true };
|
|
954
|
+
}
|
|
842
955
|
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
843
956
|
if (!existsSync6(employeesPath)) {
|
|
844
957
|
return [];
|
|
@@ -850,6 +963,10 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
|
850
963
|
return [];
|
|
851
964
|
}
|
|
852
965
|
}
|
|
966
|
+
async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
967
|
+
await mkdir2(path5.dirname(employeesPath), { recursive: true });
|
|
968
|
+
await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
969
|
+
}
|
|
853
970
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
854
971
|
if (!existsSync6(employeesPath)) return [];
|
|
855
972
|
try {
|
|
@@ -861,6 +978,19 @@ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
|
861
978
|
function getEmployee(employees, name) {
|
|
862
979
|
return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
863
980
|
}
|
|
981
|
+
function getEmployeeByRole(employees, role) {
|
|
982
|
+
const lower = role.toLowerCase();
|
|
983
|
+
return employees.find((e) => e.role.toLowerCase() === lower);
|
|
984
|
+
}
|
|
985
|
+
function getEmployeeNamesByRole(employees, role) {
|
|
986
|
+
const lower = role.toLowerCase();
|
|
987
|
+
return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
|
|
988
|
+
}
|
|
989
|
+
function hasRole(agentName, role) {
|
|
990
|
+
const employees = loadEmployeesSync();
|
|
991
|
+
const emp = getEmployee(employees, agentName);
|
|
992
|
+
return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
|
|
993
|
+
}
|
|
864
994
|
function baseAgentName(name, employees) {
|
|
865
995
|
const match = name.match(/^([a-zA-Z]+)\d+$/);
|
|
866
996
|
if (!match) return name;
|
|
@@ -875,7 +1005,131 @@ function isMultiInstance(agentName, employees) {
|
|
|
875
1005
|
if (!emp) return false;
|
|
876
1006
|
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
877
1007
|
}
|
|
878
|
-
|
|
1008
|
+
function addEmployee(employees, employee) {
|
|
1009
|
+
const { systemPrompt: _legacyPrompt, ...rest } = employee;
|
|
1010
|
+
const normalized = { ...rest, name: employee.name.toLowerCase() };
|
|
1011
|
+
if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
|
|
1012
|
+
throw new Error(`Employee '${normalized.name}' already exists`);
|
|
1013
|
+
}
|
|
1014
|
+
return [...employees, normalized];
|
|
1015
|
+
}
|
|
1016
|
+
function appendToCoordinatorTeam(employee) {
|
|
1017
|
+
const coordinator = getCoordinatorEmployee(loadEmployeesSync());
|
|
1018
|
+
if (!coordinator) return;
|
|
1019
|
+
const idPath = path5.join(IDENTITY_DIR, `${coordinator.name}.md`);
|
|
1020
|
+
if (!existsSync6(idPath)) return;
|
|
1021
|
+
const content = readFileSync5(idPath, "utf-8");
|
|
1022
|
+
if (content.includes(`**${capitalize(employee.name)}`)) return;
|
|
1023
|
+
const teamMatch = content.match(TEAM_SECTION_RE);
|
|
1024
|
+
if (!teamMatch || teamMatch.index === void 0) return;
|
|
1025
|
+
const afterTeam = content.slice(teamMatch.index + teamMatch[0].length);
|
|
1026
|
+
const nextHeading = afterTeam.match(/\n## /);
|
|
1027
|
+
const entry = `
|
|
1028
|
+
**${capitalize(employee.name)} (${employee.role}):** Newly hired. Update this description as the role develops.
|
|
1029
|
+
`;
|
|
1030
|
+
let updated;
|
|
1031
|
+
if (nextHeading && nextHeading.index !== void 0) {
|
|
1032
|
+
const insertAt = teamMatch.index + teamMatch[0].length + nextHeading.index;
|
|
1033
|
+
updated = content.slice(0, insertAt) + entry + content.slice(insertAt);
|
|
1034
|
+
} else {
|
|
1035
|
+
updated = content.trimEnd() + "\n" + entry;
|
|
1036
|
+
}
|
|
1037
|
+
writeFileSync4(idPath, updated, "utf-8");
|
|
1038
|
+
}
|
|
1039
|
+
function capitalize(s) {
|
|
1040
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
1041
|
+
}
|
|
1042
|
+
async function hireEmployee(employee) {
|
|
1043
|
+
const employees = await loadEmployees();
|
|
1044
|
+
const updated = addEmployee(employees, employee);
|
|
1045
|
+
await saveEmployees(updated);
|
|
1046
|
+
try {
|
|
1047
|
+
appendToCoordinatorTeam(employee);
|
|
1048
|
+
} catch {
|
|
1049
|
+
}
|
|
1050
|
+
try {
|
|
1051
|
+
const { loadAgentConfig: loadAgentConfig2, saveAgentConfig: saveAgentConfig2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
|
|
1052
|
+
const config = loadAgentConfig2();
|
|
1053
|
+
const name = employee.name.toLowerCase();
|
|
1054
|
+
if (!config[name] && config["default"]) {
|
|
1055
|
+
config[name] = { ...config["default"] };
|
|
1056
|
+
saveAgentConfig2(config);
|
|
1057
|
+
}
|
|
1058
|
+
} catch {
|
|
1059
|
+
}
|
|
1060
|
+
return updated;
|
|
1061
|
+
}
|
|
1062
|
+
async function normalizeRosterCase(rosterPath) {
|
|
1063
|
+
const employees = await loadEmployees(rosterPath);
|
|
1064
|
+
let changed = false;
|
|
1065
|
+
for (const emp of employees) {
|
|
1066
|
+
if (emp.name !== emp.name.toLowerCase()) {
|
|
1067
|
+
const oldName = emp.name;
|
|
1068
|
+
emp.name = emp.name.toLowerCase();
|
|
1069
|
+
changed = true;
|
|
1070
|
+
try {
|
|
1071
|
+
const identityDir = path5.join(os4.homedir(), ".exe-os", "identity");
|
|
1072
|
+
const oldPath = path5.join(identityDir, `${oldName}.md`);
|
|
1073
|
+
const newPath = path5.join(identityDir, `${emp.name}.md`);
|
|
1074
|
+
if (existsSync6(oldPath) && !existsSync6(newPath)) {
|
|
1075
|
+
renameSync3(oldPath, newPath);
|
|
1076
|
+
} else if (existsSync6(oldPath) && oldPath !== newPath) {
|
|
1077
|
+
const content = readFileSync5(oldPath, "utf-8");
|
|
1078
|
+
writeFileSync4(newPath, content, "utf-8");
|
|
1079
|
+
if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
|
|
1080
|
+
unlinkSync(oldPath);
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
} catch {
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
if (changed) {
|
|
1088
|
+
await saveEmployees(employees, rosterPath);
|
|
1089
|
+
}
|
|
1090
|
+
return changed;
|
|
1091
|
+
}
|
|
1092
|
+
function findExeBin() {
|
|
1093
|
+
try {
|
|
1094
|
+
return execSync3(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
|
|
1095
|
+
} catch {
|
|
1096
|
+
return null;
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
function registerBinSymlinks(name) {
|
|
1100
|
+
const created = [];
|
|
1101
|
+
const skipped = [];
|
|
1102
|
+
const errors = [];
|
|
1103
|
+
const exeBinPath = findExeBin();
|
|
1104
|
+
if (!exeBinPath) {
|
|
1105
|
+
errors.push("Could not find 'exe-os' in PATH");
|
|
1106
|
+
return { created, skipped, errors };
|
|
1107
|
+
}
|
|
1108
|
+
const binDir = path5.dirname(exeBinPath);
|
|
1109
|
+
let target;
|
|
1110
|
+
try {
|
|
1111
|
+
target = readlinkSync(exeBinPath);
|
|
1112
|
+
} catch {
|
|
1113
|
+
errors.push("Could not read 'exe' symlink");
|
|
1114
|
+
return { created, skipped, errors };
|
|
1115
|
+
}
|
|
1116
|
+
for (const suffix of ["", "-opencode"]) {
|
|
1117
|
+
const linkName = `${name}${suffix}`;
|
|
1118
|
+
const linkPath = path5.join(binDir, linkName);
|
|
1119
|
+
if (existsSync6(linkPath)) {
|
|
1120
|
+
skipped.push(linkName);
|
|
1121
|
+
continue;
|
|
1122
|
+
}
|
|
1123
|
+
try {
|
|
1124
|
+
symlinkSync(target, linkPath);
|
|
1125
|
+
created.push(linkName);
|
|
1126
|
+
} catch (err) {
|
|
1127
|
+
errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
return { created, skipped, errors };
|
|
1131
|
+
}
|
|
1132
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR, TEAM_SECTION_RE;
|
|
879
1133
|
var init_employees = __esm({
|
|
880
1134
|
"src/lib/employees.ts"() {
|
|
881
1135
|
"use strict";
|
|
@@ -885,6 +1139,7 @@ var init_employees = __esm({
|
|
|
885
1139
|
COORDINATOR_ROLE = "COO";
|
|
886
1140
|
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
887
1141
|
IDENTITY_DIR = path5.join(EXE_AI_DIR, "identity");
|
|
1142
|
+
TEAM_SECTION_RE = /^## Team\b.*$/m;
|
|
888
1143
|
}
|
|
889
1144
|
});
|
|
890
1145
|
|
|
@@ -972,6 +1227,23 @@ var init_database = __esm({
|
|
|
972
1227
|
});
|
|
973
1228
|
|
|
974
1229
|
// src/lib/license.ts
|
|
1230
|
+
var license_exports = {};
|
|
1231
|
+
__export(license_exports, {
|
|
1232
|
+
LICENSE_PUBLIC_KEY_PEM: () => LICENSE_PUBLIC_KEY_PEM,
|
|
1233
|
+
PLAN_LIMITS: () => PLAN_LIMITS,
|
|
1234
|
+
assertVpsLicense: () => assertVpsLicense,
|
|
1235
|
+
checkLicense: () => checkLicense,
|
|
1236
|
+
getCachedLicense: () => getCachedLicense,
|
|
1237
|
+
isFeatureAllowed: () => isFeatureAllowed,
|
|
1238
|
+
loadDeviceId: () => loadDeviceId,
|
|
1239
|
+
loadLicense: () => loadLicense,
|
|
1240
|
+
mirrorLicenseKey: () => mirrorLicenseKey,
|
|
1241
|
+
readCachedLicenseToken: () => readCachedLicenseToken,
|
|
1242
|
+
saveLicense: () => saveLicense,
|
|
1243
|
+
startLicenseRevalidation: () => startLicenseRevalidation,
|
|
1244
|
+
stopLicenseRevalidation: () => stopLicenseRevalidation,
|
|
1245
|
+
validateLicense: () => validateLicense
|
|
1246
|
+
});
|
|
975
1247
|
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync8, mkdirSync as mkdirSync5 } from "fs";
|
|
976
1248
|
import { randomUUID } from "crypto";
|
|
977
1249
|
import { createRequire as createRequire2 } from "module";
|
|
@@ -979,7 +1251,411 @@ import { pathToFileURL as pathToFileURL2 } from "url";
|
|
|
979
1251
|
import os6 from "os";
|
|
980
1252
|
import path7 from "path";
|
|
981
1253
|
import { jwtVerify, importSPKI } from "jose";
|
|
982
|
-
|
|
1254
|
+
async function fetchRetry(url, init) {
|
|
1255
|
+
try {
|
|
1256
|
+
return await fetch(url, init);
|
|
1257
|
+
} catch {
|
|
1258
|
+
await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
|
|
1259
|
+
return fetch(url, { ...init, signal: AbortSignal.timeout(1e4) });
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
function loadDeviceId() {
|
|
1263
|
+
const deviceJsonPath = path7.join(EXE_AI_DIR, "device.json");
|
|
1264
|
+
try {
|
|
1265
|
+
if (existsSync8(deviceJsonPath)) {
|
|
1266
|
+
const data = JSON.parse(readFileSync6(deviceJsonPath, "utf8"));
|
|
1267
|
+
if (data.deviceId) return data.deviceId;
|
|
1268
|
+
}
|
|
1269
|
+
} catch {
|
|
1270
|
+
}
|
|
1271
|
+
try {
|
|
1272
|
+
if (existsSync8(DEVICE_ID_PATH)) {
|
|
1273
|
+
const id2 = readFileSync6(DEVICE_ID_PATH, "utf8").trim();
|
|
1274
|
+
if (id2) return id2;
|
|
1275
|
+
}
|
|
1276
|
+
} catch {
|
|
1277
|
+
}
|
|
1278
|
+
const id = randomUUID();
|
|
1279
|
+
mkdirSync5(EXE_AI_DIR, { recursive: true });
|
|
1280
|
+
writeFileSync5(DEVICE_ID_PATH, id, "utf8");
|
|
1281
|
+
return id;
|
|
1282
|
+
}
|
|
1283
|
+
function loadLicense() {
|
|
1284
|
+
try {
|
|
1285
|
+
if (!existsSync8(LICENSE_PATH)) return null;
|
|
1286
|
+
return readFileSync6(LICENSE_PATH, "utf8").trim();
|
|
1287
|
+
} catch {
|
|
1288
|
+
return null;
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
function saveLicense(apiKey) {
|
|
1292
|
+
mkdirSync5(EXE_AI_DIR, { recursive: true });
|
|
1293
|
+
writeFileSync5(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
|
|
1294
|
+
}
|
|
1295
|
+
async function verifyLicenseJwt(token) {
|
|
1296
|
+
try {
|
|
1297
|
+
const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
|
|
1298
|
+
const { payload } = await jwtVerify(token, key, {
|
|
1299
|
+
algorithms: [LICENSE_JWT_ALG]
|
|
1300
|
+
});
|
|
1301
|
+
const plan = payload.plan ?? "free";
|
|
1302
|
+
const email = payload.sub ?? "";
|
|
1303
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
1304
|
+
return {
|
|
1305
|
+
valid: true,
|
|
1306
|
+
plan,
|
|
1307
|
+
email,
|
|
1308
|
+
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
1309
|
+
deviceLimit: limits.devices,
|
|
1310
|
+
employeeLimit: limits.employees,
|
|
1311
|
+
memoryLimit: limits.memories
|
|
1312
|
+
};
|
|
1313
|
+
} catch {
|
|
1314
|
+
return null;
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
async function getCachedLicense() {
|
|
1318
|
+
try {
|
|
1319
|
+
if (!existsSync8(CACHE_PATH)) return null;
|
|
1320
|
+
const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
|
|
1321
|
+
if (!raw.token || typeof raw.token !== "string") return null;
|
|
1322
|
+
return await verifyLicenseJwt(raw.token);
|
|
1323
|
+
} catch {
|
|
1324
|
+
return null;
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
function readCachedLicenseToken() {
|
|
1328
|
+
try {
|
|
1329
|
+
if (!existsSync8(CACHE_PATH)) return null;
|
|
1330
|
+
const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
|
|
1331
|
+
return typeof raw.token === "string" ? raw.token : null;
|
|
1332
|
+
} catch {
|
|
1333
|
+
return null;
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
function getRawCachedPlan() {
|
|
1337
|
+
try {
|
|
1338
|
+
const token = readCachedLicenseToken();
|
|
1339
|
+
if (!token) return null;
|
|
1340
|
+
const parts = token.split(".");
|
|
1341
|
+
if (parts.length !== 3) return null;
|
|
1342
|
+
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
|
|
1343
|
+
const plan = payload.plan ?? "free";
|
|
1344
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
1345
|
+
process.stderr.write(
|
|
1346
|
+
`[license] WARN: using unverified cached plan (API unreachable, JWT expired). Plan: ${plan}
|
|
1347
|
+
`
|
|
1348
|
+
);
|
|
1349
|
+
return {
|
|
1350
|
+
valid: true,
|
|
1351
|
+
plan,
|
|
1352
|
+
email: payload.sub ?? "",
|
|
1353
|
+
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
1354
|
+
deviceLimit: limits.devices,
|
|
1355
|
+
employeeLimit: limits.employees,
|
|
1356
|
+
memoryLimit: limits.memories
|
|
1357
|
+
};
|
|
1358
|
+
} catch {
|
|
1359
|
+
return null;
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
function cacheResponse(token) {
|
|
1363
|
+
try {
|
|
1364
|
+
writeFileSync5(CACHE_PATH, JSON.stringify({ token }), "utf8");
|
|
1365
|
+
} catch {
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
function loadPrismaForLicense() {
|
|
1369
|
+
if (_prismaFailed) return null;
|
|
1370
|
+
const dbUrl = process.env.DATABASE_URL;
|
|
1371
|
+
if (!dbUrl) {
|
|
1372
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path7.join(os6.homedir(), "exe-db");
|
|
1373
|
+
if (!existsSync8(path7.join(exeDbRoot, "package.json"))) {
|
|
1374
|
+
_prismaFailed = true;
|
|
1375
|
+
return null;
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
if (!_prismaPromise) {
|
|
1379
|
+
_prismaPromise = (async () => {
|
|
1380
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
1381
|
+
if (explicitPath) {
|
|
1382
|
+
const mod2 = await import(pathToFileURL2(explicitPath).href);
|
|
1383
|
+
const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
|
|
1384
|
+
if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
|
|
1385
|
+
return new Ctor2();
|
|
1386
|
+
}
|
|
1387
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path7.join(os6.homedir(), "exe-db");
|
|
1388
|
+
const req = createRequire2(path7.join(exeDbRoot, "package.json"));
|
|
1389
|
+
const entry = req.resolve("@prisma/client");
|
|
1390
|
+
const mod = await import(pathToFileURL2(entry).href);
|
|
1391
|
+
const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
|
|
1392
|
+
if (!Ctor) throw new Error(`No PrismaClient in ${entry}`);
|
|
1393
|
+
return new Ctor();
|
|
1394
|
+
})().catch((err) => {
|
|
1395
|
+
_prismaFailed = true;
|
|
1396
|
+
_prismaPromise = null;
|
|
1397
|
+
throw err;
|
|
1398
|
+
});
|
|
1399
|
+
}
|
|
1400
|
+
return _prismaPromise;
|
|
1401
|
+
}
|
|
1402
|
+
async function validateViaPostgres(apiKey) {
|
|
1403
|
+
const loader = loadPrismaForLicense();
|
|
1404
|
+
if (!loader) return null;
|
|
1405
|
+
try {
|
|
1406
|
+
const prisma = await loader;
|
|
1407
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
1408
|
+
`SELECT plan, email, status, device_limit, employee_limit, memory_limit, expires_at
|
|
1409
|
+
FROM billing.licenses WHERE key = $1 LIMIT 1`,
|
|
1410
|
+
apiKey
|
|
1411
|
+
);
|
|
1412
|
+
if (!rows || rows.length === 0) return null;
|
|
1413
|
+
const row = rows[0];
|
|
1414
|
+
if (row.status !== "active") return null;
|
|
1415
|
+
if (row.expires_at && new Date(row.expires_at) < /* @__PURE__ */ new Date()) return null;
|
|
1416
|
+
const plan = row.plan;
|
|
1417
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
1418
|
+
return {
|
|
1419
|
+
valid: true,
|
|
1420
|
+
plan,
|
|
1421
|
+
email: row.email,
|
|
1422
|
+
expiresAt: row.expires_at ? new Date(row.expires_at).toISOString() : null,
|
|
1423
|
+
deviceLimit: row.device_limit ?? limits.devices,
|
|
1424
|
+
employeeLimit: row.employee_limit ?? limits.employees,
|
|
1425
|
+
memoryLimit: row.memory_limit ?? limits.memories
|
|
1426
|
+
};
|
|
1427
|
+
} catch {
|
|
1428
|
+
return null;
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
async function validateViaCFWorker(apiKey, deviceId) {
|
|
1432
|
+
try {
|
|
1433
|
+
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
1434
|
+
method: "POST",
|
|
1435
|
+
headers: { "Content-Type": "application/json" },
|
|
1436
|
+
body: JSON.stringify({ apiKey, deviceId }),
|
|
1437
|
+
signal: AbortSignal.timeout(1e4)
|
|
1438
|
+
});
|
|
1439
|
+
if (!res.ok) return null;
|
|
1440
|
+
const data = await res.json();
|
|
1441
|
+
if (data.error === "device_limit_exceeded") return null;
|
|
1442
|
+
if (!data.valid) return null;
|
|
1443
|
+
if (data.token) {
|
|
1444
|
+
cacheResponse(data.token);
|
|
1445
|
+
const verified = await verifyLicenseJwt(data.token);
|
|
1446
|
+
if (verified) return verified;
|
|
1447
|
+
}
|
|
1448
|
+
const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
|
|
1449
|
+
return {
|
|
1450
|
+
valid: data.valid,
|
|
1451
|
+
plan: data.plan,
|
|
1452
|
+
email: data.email,
|
|
1453
|
+
expiresAt: data.expiresAt,
|
|
1454
|
+
deviceLimit: limits.devices,
|
|
1455
|
+
employeeLimit: limits.employees,
|
|
1456
|
+
memoryLimit: limits.memories
|
|
1457
|
+
};
|
|
1458
|
+
} catch {
|
|
1459
|
+
return null;
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
async function validateLicense(apiKey, deviceId) {
|
|
1463
|
+
const did = deviceId ?? loadDeviceId();
|
|
1464
|
+
const pgResult = await validateViaPostgres(apiKey);
|
|
1465
|
+
if (pgResult) {
|
|
1466
|
+
try {
|
|
1467
|
+
writeFileSync5(CACHE_PATH, JSON.stringify({ pgLicense: pgResult, ts: Date.now() }), "utf8");
|
|
1468
|
+
} catch {
|
|
1469
|
+
}
|
|
1470
|
+
return pgResult;
|
|
1471
|
+
}
|
|
1472
|
+
const cfResult = await validateViaCFWorker(apiKey, did);
|
|
1473
|
+
if (cfResult) return cfResult;
|
|
1474
|
+
const cached = await getCachedLicense();
|
|
1475
|
+
if (cached) return cached;
|
|
1476
|
+
try {
|
|
1477
|
+
if (existsSync8(CACHE_PATH)) {
|
|
1478
|
+
const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
|
|
1479
|
+
if (raw.pgLicense && raw.ts && Date.now() - raw.ts < 7 * 24 * 60 * 60 * 1e3) {
|
|
1480
|
+
return raw.pgLicense;
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
} catch {
|
|
1484
|
+
}
|
|
1485
|
+
const rawFallback = getRawCachedPlan();
|
|
1486
|
+
if (rawFallback) return rawFallback;
|
|
1487
|
+
return { ...FREE_LICENSE, valid: false };
|
|
1488
|
+
}
|
|
1489
|
+
function getCacheAgeMs() {
|
|
1490
|
+
try {
|
|
1491
|
+
const { statSync: statSync3 } = __require("fs");
|
|
1492
|
+
const s = statSync3(CACHE_PATH);
|
|
1493
|
+
return Date.now() - s.mtimeMs;
|
|
1494
|
+
} catch {
|
|
1495
|
+
return Infinity;
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
async function checkLicense() {
|
|
1499
|
+
let key = loadLicense();
|
|
1500
|
+
if (!key) {
|
|
1501
|
+
try {
|
|
1502
|
+
const configPath = path7.join(EXE_AI_DIR, "config.json");
|
|
1503
|
+
if (existsSync8(configPath)) {
|
|
1504
|
+
const raw = JSON.parse(readFileSync6(configPath, "utf8"));
|
|
1505
|
+
const cloud = raw.cloud;
|
|
1506
|
+
if (cloud?.apiKey) {
|
|
1507
|
+
key = cloud.apiKey;
|
|
1508
|
+
saveLicense(key);
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
} catch {
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
if (!key) return FREE_LICENSE;
|
|
1515
|
+
const cached = await getCachedLicense();
|
|
1516
|
+
if (cached && getCacheAgeMs() < CACHE_MAX_AGE_MS) return cached;
|
|
1517
|
+
const deviceId = loadDeviceId();
|
|
1518
|
+
return validateLicense(key, deviceId);
|
|
1519
|
+
}
|
|
1520
|
+
function isFeatureAllowed(license, feature) {
|
|
1521
|
+
switch (feature) {
|
|
1522
|
+
case "cloud_sync":
|
|
1523
|
+
case "external_agents":
|
|
1524
|
+
case "wiki":
|
|
1525
|
+
return license.plan !== "free";
|
|
1526
|
+
case "unlimited_employees":
|
|
1527
|
+
return license.plan === "team" || license.plan === "agency" || license.plan === "enterprise";
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
function mirrorLicenseKey(apiKey) {
|
|
1531
|
+
const trimmed = apiKey.trim();
|
|
1532
|
+
if (!trimmed) return;
|
|
1533
|
+
saveLicense(trimmed);
|
|
1534
|
+
}
|
|
1535
|
+
async function assertVpsLicense(opts) {
|
|
1536
|
+
const env = opts?.env ?? process.env;
|
|
1537
|
+
const inProduction = env.NODE_ENV === "production";
|
|
1538
|
+
if (!opts?.force && !inProduction) {
|
|
1539
|
+
return { ...FREE_LICENSE, plan: "free" };
|
|
1540
|
+
}
|
|
1541
|
+
const envKey = env.EXE_LICENSE_KEY?.trim();
|
|
1542
|
+
if (envKey) {
|
|
1543
|
+
saveLicense(envKey);
|
|
1544
|
+
}
|
|
1545
|
+
const apiKey = envKey ?? loadLicense();
|
|
1546
|
+
if (!apiKey) {
|
|
1547
|
+
throw new Error(
|
|
1548
|
+
"License required: set EXE_LICENSE_KEY env var with your exe_sk_* key. Purchase at https://askexe.com. This VPS image refuses to boot without a valid license."
|
|
1549
|
+
);
|
|
1550
|
+
}
|
|
1551
|
+
const deviceId = loadDeviceId();
|
|
1552
|
+
let backendResponse = null;
|
|
1553
|
+
let explicitRejection = false;
|
|
1554
|
+
let transientFailure = false;
|
|
1555
|
+
try {
|
|
1556
|
+
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
1557
|
+
method: "POST",
|
|
1558
|
+
headers: { "Content-Type": "application/json" },
|
|
1559
|
+
body: JSON.stringify({ apiKey, deviceId }),
|
|
1560
|
+
signal: AbortSignal.timeout(1e4)
|
|
1561
|
+
});
|
|
1562
|
+
if (res.ok) {
|
|
1563
|
+
backendResponse = await res.json();
|
|
1564
|
+
if (!backendResponse.valid) explicitRejection = true;
|
|
1565
|
+
} else if (res.status === 401 || res.status === 403) {
|
|
1566
|
+
explicitRejection = true;
|
|
1567
|
+
} else {
|
|
1568
|
+
transientFailure = true;
|
|
1569
|
+
}
|
|
1570
|
+
} catch {
|
|
1571
|
+
transientFailure = true;
|
|
1572
|
+
}
|
|
1573
|
+
if (backendResponse?.valid) {
|
|
1574
|
+
if (backendResponse.token) {
|
|
1575
|
+
cacheResponse(backendResponse.token);
|
|
1576
|
+
const verified = await verifyLicenseJwt(backendResponse.token);
|
|
1577
|
+
if (verified) return verified;
|
|
1578
|
+
}
|
|
1579
|
+
const limits = PLAN_LIMITS[backendResponse.plan] ?? PLAN_LIMITS.free;
|
|
1580
|
+
return {
|
|
1581
|
+
valid: true,
|
|
1582
|
+
plan: backendResponse.plan,
|
|
1583
|
+
email: backendResponse.email,
|
|
1584
|
+
expiresAt: backendResponse.expiresAt,
|
|
1585
|
+
deviceLimit: limits.devices,
|
|
1586
|
+
employeeLimit: limits.employees,
|
|
1587
|
+
memoryLimit: limits.memories
|
|
1588
|
+
};
|
|
1589
|
+
}
|
|
1590
|
+
if (explicitRejection) {
|
|
1591
|
+
throw new Error(
|
|
1592
|
+
`License invalid or expired. Renew at https://askexe.com, then restart. Backend rejected key ending in ...${apiKey.slice(-4)}.`
|
|
1593
|
+
);
|
|
1594
|
+
}
|
|
1595
|
+
if (!transientFailure) {
|
|
1596
|
+
throw new Error(
|
|
1597
|
+
"License validation failed: unknown backend state. Restore network connectivity to https://cloud.askexe.com and retry."
|
|
1598
|
+
);
|
|
1599
|
+
}
|
|
1600
|
+
const fresh = await getCachedLicense();
|
|
1601
|
+
if (fresh && fresh.valid) return fresh;
|
|
1602
|
+
const graceDays = opts?.offlineGraceDays ?? 7;
|
|
1603
|
+
const graceMs = graceDays * 24 * 60 * 60 * 1e3;
|
|
1604
|
+
try {
|
|
1605
|
+
const token = readCachedLicenseToken();
|
|
1606
|
+
if (token) {
|
|
1607
|
+
const payloadB64 = token.split(".")[1];
|
|
1608
|
+
if (payloadB64) {
|
|
1609
|
+
const payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString());
|
|
1610
|
+
const expMs = (payload.exp ?? 0) * 1e3;
|
|
1611
|
+
if (Date.now() < expMs + graceMs) {
|
|
1612
|
+
const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
|
|
1613
|
+
const { payload: verified } = await jwtVerify(token, key, {
|
|
1614
|
+
algorithms: [LICENSE_JWT_ALG],
|
|
1615
|
+
clockTolerance: graceDays * 24 * 60 * 60
|
|
1616
|
+
});
|
|
1617
|
+
const plan = verified.plan ?? "free";
|
|
1618
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
1619
|
+
return {
|
|
1620
|
+
valid: true,
|
|
1621
|
+
plan,
|
|
1622
|
+
email: verified.sub ?? "",
|
|
1623
|
+
expiresAt: verified.exp ? new Date(verified.exp * 1e3).toISOString() : null,
|
|
1624
|
+
deviceLimit: limits.devices,
|
|
1625
|
+
employeeLimit: limits.employees,
|
|
1626
|
+
memoryLimit: limits.memories
|
|
1627
|
+
};
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
} catch {
|
|
1632
|
+
}
|
|
1633
|
+
throw new Error(
|
|
1634
|
+
`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.`
|
|
1635
|
+
);
|
|
1636
|
+
}
|
|
1637
|
+
function startLicenseRevalidation(intervalMs = 36e5) {
|
|
1638
|
+
if (_revalTimer) return;
|
|
1639
|
+
_revalTimer = setInterval(async () => {
|
|
1640
|
+
try {
|
|
1641
|
+
const license = await checkLicense();
|
|
1642
|
+
if (!license.valid) {
|
|
1643
|
+
process.stderr.write("[exe-os] License expired or invalid \u2014 features may be restricted\n");
|
|
1644
|
+
}
|
|
1645
|
+
} catch {
|
|
1646
|
+
}
|
|
1647
|
+
}, intervalMs);
|
|
1648
|
+
if (_revalTimer && typeof _revalTimer === "object" && "unref" in _revalTimer) {
|
|
1649
|
+
_revalTimer.unref();
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
function stopLicenseRevalidation() {
|
|
1653
|
+
if (_revalTimer) {
|
|
1654
|
+
clearInterval(_revalTimer);
|
|
1655
|
+
_revalTimer = null;
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, RETRY_DELAY_MS, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE, _prismaPromise, _prismaFailed, CACHE_MAX_AGE_MS, _revalTimer;
|
|
983
1659
|
var init_license = __esm({
|
|
984
1660
|
"src/lib/license.ts"() {
|
|
985
1661
|
"use strict";
|
|
@@ -987,7 +1663,13 @@ var init_license = __esm({
|
|
|
987
1663
|
LICENSE_PATH = path7.join(EXE_AI_DIR, "license.key");
|
|
988
1664
|
CACHE_PATH = path7.join(EXE_AI_DIR, "license-cache.json");
|
|
989
1665
|
DEVICE_ID_PATH = path7.join(EXE_AI_DIR, "device-id");
|
|
990
|
-
API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com
|
|
1666
|
+
API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://cloud.askexe.com";
|
|
1667
|
+
RETRY_DELAY_MS = 500;
|
|
1668
|
+
LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
1669
|
+
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
|
|
1670
|
+
4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
|
|
1671
|
+
-----END PUBLIC KEY-----`;
|
|
1672
|
+
LICENSE_JWT_ALG = "ES256";
|
|
991
1673
|
PLAN_LIMITS = {
|
|
992
1674
|
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
993
1675
|
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
@@ -995,6 +1677,19 @@ var init_license = __esm({
|
|
|
995
1677
|
agency: { devices: 50, employees: 100, memories: 1e7 },
|
|
996
1678
|
enterprise: { devices: -1, employees: -1, memories: -1 }
|
|
997
1679
|
};
|
|
1680
|
+
FREE_LICENSE = {
|
|
1681
|
+
valid: true,
|
|
1682
|
+
plan: "free",
|
|
1683
|
+
email: "",
|
|
1684
|
+
expiresAt: null,
|
|
1685
|
+
deviceLimit: 1,
|
|
1686
|
+
employeeLimit: 1,
|
|
1687
|
+
memoryLimit: 5e3
|
|
1688
|
+
};
|
|
1689
|
+
_prismaPromise = null;
|
|
1690
|
+
_prismaFailed = false;
|
|
1691
|
+
CACHE_MAX_AGE_MS = 36e5;
|
|
1692
|
+
_revalTimer = null;
|
|
998
1693
|
}
|
|
999
1694
|
});
|
|
1000
1695
|
|
|
@@ -1506,6 +2201,19 @@ async function resolveTask(client, identifier, scopeSession) {
|
|
|
1506
2201
|
args: [identifier, ...scope.args]
|
|
1507
2202
|
});
|
|
1508
2203
|
if (result.rows.length === 1) return result.rows[0];
|
|
2204
|
+
if (/^[a-f0-9]{7,12}$/i.test(identifier)) {
|
|
2205
|
+
result = await client.execute({
|
|
2206
|
+
sql: `SELECT * FROM tasks WHERE id LIKE ?`,
|
|
2207
|
+
args: [`${identifier}%`]
|
|
2208
|
+
});
|
|
2209
|
+
if (result.rows.length === 1) return result.rows[0];
|
|
2210
|
+
if (result.rows.length > 1) {
|
|
2211
|
+
const matches = result.rows.map((r) => `${String(r.id)} "${String(r.title)}" (${String(r.status)})`).join(", ");
|
|
2212
|
+
throw new Error(
|
|
2213
|
+
`Multiple tasks match short-ID "${identifier}": ${matches}. Use a longer prefix to disambiguate.`
|
|
2214
|
+
);
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
1509
2217
|
result = await client.execute({
|
|
1510
2218
|
sql: `SELECT * FROM tasks WHERE task_file LIKE ?${scope.sql}`,
|
|
1511
2219
|
args: [`%${identifier}%`, ...scope.args]
|
|
@@ -2360,12 +3068,13 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
2360
3068
|
WHERE blocked_by = ? AND status = 'blocked'`,
|
|
2361
3069
|
args: [now, taskId]
|
|
2362
3070
|
});
|
|
2363
|
-
if (
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
3071
|
+
if (unblocked.rowsAffected === 0) return;
|
|
3072
|
+
const ubScope = sessionScopeFilter();
|
|
3073
|
+
const unblockedRows = await client.execute({
|
|
3074
|
+
sql: `SELECT id, title, assigned_to, priority, task_file FROM tasks WHERE blocked_by IS NULL AND updated_at = ?${ubScope.sql}`,
|
|
3075
|
+
args: [now, ...ubScope.args]
|
|
3076
|
+
});
|
|
3077
|
+
if (baseDir) {
|
|
2369
3078
|
for (const ur of unblockedRows.rows) {
|
|
2370
3079
|
try {
|
|
2371
3080
|
const ubFile = path14.join(baseDir, String(ur.task_file));
|
|
@@ -2377,6 +3086,19 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
2377
3086
|
}
|
|
2378
3087
|
}
|
|
2379
3088
|
}
|
|
3089
|
+
if (unblockedRows.rows.length > 0 && !process.env.VITEST) {
|
|
3090
|
+
try {
|
|
3091
|
+
const { queueIntercom: queueIntercom2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
3092
|
+
const dispatched = /* @__PURE__ */ new Set();
|
|
3093
|
+
for (const ur of unblockedRows.rows) {
|
|
3094
|
+
const assignee = String(ur.assigned_to);
|
|
3095
|
+
if (dispatched.has(assignee)) continue;
|
|
3096
|
+
dispatched.add(assignee);
|
|
3097
|
+
queueIntercom2(`${assignee}`, `unblocked: "${String(ur.title)}" is now ready`);
|
|
3098
|
+
}
|
|
3099
|
+
} catch {
|
|
3100
|
+
}
|
|
3101
|
+
}
|
|
2380
3102
|
}
|
|
2381
3103
|
async function findNextTask(assignedTo) {
|
|
2382
3104
|
const client = getClient();
|
|
@@ -3035,6 +3757,15 @@ var init_embedder = __esm({
|
|
|
3035
3757
|
// src/lib/behaviors.ts
|
|
3036
3758
|
import crypto5 from "crypto";
|
|
3037
3759
|
async function storeBehavior(opts) {
|
|
3760
|
+
try {
|
|
3761
|
+
const { loadEmployeesSync: loadEmployeesSync2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
|
|
3762
|
+
const roster = loadEmployeesSync2();
|
|
3763
|
+
if (roster.length > 0 && !roster.some((e) => e.name === opts.agentId)) {
|
|
3764
|
+
throw new Error(`Agent "${opts.agentId}" not found in roster. Cannot store behavior for unregistered agent.`);
|
|
3765
|
+
}
|
|
3766
|
+
} catch (e) {
|
|
3767
|
+
if (e instanceof Error && e.message.includes("not found in roster")) throw e;
|
|
3768
|
+
}
|
|
3038
3769
|
const client = getClient();
|
|
3039
3770
|
const id = crypto5.randomUUID();
|
|
3040
3771
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -3045,10 +3776,18 @@ async function storeBehavior(opts) {
|
|
|
3045
3776
|
vector = new Float32Array(vec);
|
|
3046
3777
|
} catch {
|
|
3047
3778
|
}
|
|
3779
|
+
let createdByDevice = null;
|
|
3780
|
+
try {
|
|
3781
|
+
const { loadDeviceId: loadDeviceId2 } = await Promise.resolve().then(() => (init_license(), license_exports));
|
|
3782
|
+
createdByDevice = loadDeviceId2() ?? null;
|
|
3783
|
+
} catch {
|
|
3784
|
+
}
|
|
3785
|
+
const createdByAgent = process.env.AGENT_ID ?? null;
|
|
3786
|
+
const sourceSessionId = process.env.CLAUDE_SESSION_ID ?? process.env.SESSION_ID ?? null;
|
|
3048
3787
|
await client.execute({
|
|
3049
|
-
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at, vector)
|
|
3050
|
-
VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?)`,
|
|
3051
|
-
args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now, vector ? vector.buffer : null]
|
|
3788
|
+
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at, vector, created_by_agent, created_by_device, source_session_id)
|
|
3789
|
+
VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?)`,
|
|
3790
|
+
args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now, vector ? vector.buffer : null, createdByAgent, createdByDevice, sourceSessionId]
|
|
3052
3791
|
});
|
|
3053
3792
|
return id;
|
|
3054
3793
|
}
|
|
@@ -3480,6 +4219,12 @@ async function updateTask(input) {
|
|
|
3480
4219
|
}
|
|
3481
4220
|
}
|
|
3482
4221
|
}
|
|
4222
|
+
if (input.status === "cancelled") {
|
|
4223
|
+
try {
|
|
4224
|
+
await cascadeUnblock(taskId, input.baseDir, now);
|
|
4225
|
+
} catch {
|
|
4226
|
+
}
|
|
4227
|
+
}
|
|
3483
4228
|
if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
3484
4229
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
3485
4230
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
@@ -4011,11 +4756,12 @@ function getDispatchedBy(sessionKey) {
|
|
|
4011
4756
|
}
|
|
4012
4757
|
}
|
|
4013
4758
|
function resolveExeSession() {
|
|
4759
|
+
if (process.env.EXE_SESSION_NAME) {
|
|
4760
|
+
const fromEnv = extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
|
|
4761
|
+
if (fromEnv) return fromEnv;
|
|
4762
|
+
}
|
|
4014
4763
|
const mySession = getMySession();
|
|
4015
4764
|
if (!mySession) {
|
|
4016
|
-
if (process.env.EXE_SESSION_NAME) {
|
|
4017
|
-
return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
|
|
4018
|
-
}
|
|
4019
4765
|
return null;
|
|
4020
4766
|
}
|
|
4021
4767
|
const fromSessionName = extractRootExe(mySession);
|
|
@@ -4030,6 +4776,10 @@ function resolveExeSession() {
|
|
|
4030
4776
|
`[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
|
|
4031
4777
|
`
|
|
4032
4778
|
);
|
|
4779
|
+
try {
|
|
4780
|
+
registerParentExe(key, fromSessionName);
|
|
4781
|
+
} catch {
|
|
4782
|
+
}
|
|
4033
4783
|
candidate = fromSessionName;
|
|
4034
4784
|
} else {
|
|
4035
4785
|
candidate = fromCache;
|