@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/bin/scan-tasks.js
CHANGED
|
@@ -663,6 +663,19 @@ var init_runtime_table = __esm({
|
|
|
663
663
|
});
|
|
664
664
|
|
|
665
665
|
// src/lib/agent-config.ts
|
|
666
|
+
var agent_config_exports = {};
|
|
667
|
+
__export(agent_config_exports, {
|
|
668
|
+
AGENT_CONFIG_PATH: () => AGENT_CONFIG_PATH,
|
|
669
|
+
DEFAULT_MODELS: () => DEFAULT_MODELS,
|
|
670
|
+
KNOWN_RUNTIMES: () => KNOWN_RUNTIMES,
|
|
671
|
+
RUNTIME_LABELS: () => RUNTIME_LABELS,
|
|
672
|
+
clearAgentRuntime: () => clearAgentRuntime,
|
|
673
|
+
getAgentRuntime: () => getAgentRuntime,
|
|
674
|
+
loadAgentConfig: () => loadAgentConfig,
|
|
675
|
+
saveAgentConfig: () => saveAgentConfig,
|
|
676
|
+
setAgentMcps: () => setAgentMcps,
|
|
677
|
+
setAgentRuntime: () => setAgentRuntime
|
|
678
|
+
});
|
|
666
679
|
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync4 } from "fs";
|
|
667
680
|
import path3 from "path";
|
|
668
681
|
function loadAgentConfig() {
|
|
@@ -673,6 +686,12 @@ function loadAgentConfig() {
|
|
|
673
686
|
return {};
|
|
674
687
|
}
|
|
675
688
|
}
|
|
689
|
+
function saveAgentConfig(config) {
|
|
690
|
+
const dir = path3.dirname(AGENT_CONFIG_PATH);
|
|
691
|
+
ensurePrivateDirSync(dir);
|
|
692
|
+
writeFileSync2(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
693
|
+
enforcePrivateFileSync(AGENT_CONFIG_PATH);
|
|
694
|
+
}
|
|
676
695
|
function getAgentRuntime(agentId) {
|
|
677
696
|
const config = loadAgentConfig();
|
|
678
697
|
const entry = config[agentId];
|
|
@@ -681,7 +700,47 @@ function getAgentRuntime(agentId) {
|
|
|
681
700
|
if (orgDefault) return orgDefault;
|
|
682
701
|
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
683
702
|
}
|
|
684
|
-
|
|
703
|
+
function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
|
|
704
|
+
const knownModels = KNOWN_RUNTIMES[runtime];
|
|
705
|
+
if (!knownModels) {
|
|
706
|
+
return {
|
|
707
|
+
ok: false,
|
|
708
|
+
error: `Unknown runtime "${runtime}". Valid: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
if (!knownModels.includes(model)) {
|
|
712
|
+
return {
|
|
713
|
+
ok: false,
|
|
714
|
+
error: `Unknown model "${model}" for runtime "${runtime}". Valid: ${knownModels.join(", ")}`
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
const config = loadAgentConfig();
|
|
718
|
+
const existing = config[agentId];
|
|
719
|
+
const entry = { runtime, model };
|
|
720
|
+
if (reasoning_effort) entry.reasoning_effort = reasoning_effort;
|
|
721
|
+
if (mcps !== void 0) {
|
|
722
|
+
entry.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
|
|
723
|
+
} else if (existing?.mcps) {
|
|
724
|
+
entry.mcps = existing.mcps;
|
|
725
|
+
}
|
|
726
|
+
config[agentId] = entry;
|
|
727
|
+
saveAgentConfig(config);
|
|
728
|
+
return { ok: true };
|
|
729
|
+
}
|
|
730
|
+
function setAgentMcps(agentId, mcps) {
|
|
731
|
+
const config = loadAgentConfig();
|
|
732
|
+
const existing = config[agentId] ?? getAgentRuntime(agentId);
|
|
733
|
+
existing.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
|
|
734
|
+
config[agentId] = existing;
|
|
735
|
+
saveAgentConfig(config);
|
|
736
|
+
return { ok: true };
|
|
737
|
+
}
|
|
738
|
+
function clearAgentRuntime(agentId) {
|
|
739
|
+
const config = loadAgentConfig();
|
|
740
|
+
delete config[agentId];
|
|
741
|
+
saveAgentConfig(config);
|
|
742
|
+
}
|
|
743
|
+
var AGENT_CONFIG_PATH, KNOWN_RUNTIMES, RUNTIME_LABELS, DEFAULT_MODELS;
|
|
685
744
|
var init_agent_config = __esm({
|
|
686
745
|
"src/lib/agent-config.ts"() {
|
|
687
746
|
"use strict";
|
|
@@ -689,6 +748,16 @@ var init_agent_config = __esm({
|
|
|
689
748
|
init_runtime_table();
|
|
690
749
|
init_secure_files();
|
|
691
750
|
AGENT_CONFIG_PATH = path3.join(EXE_AI_DIR, "agent-config.json");
|
|
751
|
+
KNOWN_RUNTIMES = {
|
|
752
|
+
claude: ["claude-opus-4.6", "claude-opus-4", "claude-sonnet-4.6", "claude-sonnet-4", "claude-haiku-4.5"],
|
|
753
|
+
codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
|
|
754
|
+
opencode: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4", "google/gemini-2.5-pro", "deepseek/deepseek-r3", "minimax/minimax-m2.5"]
|
|
755
|
+
};
|
|
756
|
+
RUNTIME_LABELS = {
|
|
757
|
+
claude: "Claude Code (Anthropic)",
|
|
758
|
+
codex: "Codex (OpenAI)",
|
|
759
|
+
opencode: "OpenCode (open source)"
|
|
760
|
+
};
|
|
692
761
|
DEFAULT_MODELS = {
|
|
693
762
|
claude: "claude-opus-4.6",
|
|
694
763
|
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
@@ -879,6 +948,32 @@ var init_db_retry = __esm({
|
|
|
879
948
|
});
|
|
880
949
|
|
|
881
950
|
// src/lib/employees.ts
|
|
951
|
+
var employees_exports = {};
|
|
952
|
+
__export(employees_exports, {
|
|
953
|
+
COORDINATOR_ROLE: () => COORDINATOR_ROLE,
|
|
954
|
+
DEFAULT_COORDINATOR_TEMPLATE_NAME: () => DEFAULT_COORDINATOR_TEMPLATE_NAME,
|
|
955
|
+
EMPLOYEES_PATH: () => EMPLOYEES_PATH,
|
|
956
|
+
addEmployee: () => addEmployee,
|
|
957
|
+
baseAgentName: () => baseAgentName,
|
|
958
|
+
canCoordinate: () => canCoordinate,
|
|
959
|
+
getCoordinatorEmployee: () => getCoordinatorEmployee,
|
|
960
|
+
getCoordinatorName: () => getCoordinatorName,
|
|
961
|
+
getEmployee: () => getEmployee,
|
|
962
|
+
getEmployeeByRole: () => getEmployeeByRole,
|
|
963
|
+
getEmployeeNamesByRole: () => getEmployeeNamesByRole,
|
|
964
|
+
hasRole: () => hasRole,
|
|
965
|
+
hireEmployee: () => hireEmployee,
|
|
966
|
+
isCoordinatorName: () => isCoordinatorName,
|
|
967
|
+
isCoordinatorRole: () => isCoordinatorRole,
|
|
968
|
+
isMultiInstance: () => isMultiInstance,
|
|
969
|
+
loadEmployees: () => loadEmployees,
|
|
970
|
+
loadEmployeesSync: () => loadEmployeesSync,
|
|
971
|
+
normalizeRole: () => normalizeRole,
|
|
972
|
+
normalizeRosterCase: () => normalizeRosterCase,
|
|
973
|
+
registerBinSymlinks: () => registerBinSymlinks,
|
|
974
|
+
saveEmployees: () => saveEmployees,
|
|
975
|
+
validateEmployeeName: () => validateEmployeeName
|
|
976
|
+
});
|
|
882
977
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
883
978
|
import { existsSync as existsSync6, symlinkSync, readlinkSync, readFileSync as readFileSync5, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
|
|
884
979
|
import { execSync as execSync3 } from "child_process";
|
|
@@ -900,6 +995,24 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
|
|
|
900
995
|
if (!agentName) return false;
|
|
901
996
|
return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
|
|
902
997
|
}
|
|
998
|
+
function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
|
|
999
|
+
return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
|
|
1000
|
+
}
|
|
1001
|
+
function validateEmployeeName(name) {
|
|
1002
|
+
if (!name) {
|
|
1003
|
+
return { valid: false, error: "Name is required" };
|
|
1004
|
+
}
|
|
1005
|
+
if (name.length > 32) {
|
|
1006
|
+
return { valid: false, error: "Name must be 32 characters or fewer" };
|
|
1007
|
+
}
|
|
1008
|
+
if (!/^[a-z][a-z0-9]*$/.test(name)) {
|
|
1009
|
+
return {
|
|
1010
|
+
valid: false,
|
|
1011
|
+
error: "Name must start with a letter and contain only lowercase alphanumeric characters"
|
|
1012
|
+
};
|
|
1013
|
+
}
|
|
1014
|
+
return { valid: true };
|
|
1015
|
+
}
|
|
903
1016
|
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
904
1017
|
if (!existsSync6(employeesPath)) {
|
|
905
1018
|
return [];
|
|
@@ -911,6 +1024,10 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
|
911
1024
|
return [];
|
|
912
1025
|
}
|
|
913
1026
|
}
|
|
1027
|
+
async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
1028
|
+
await mkdir2(path5.dirname(employeesPath), { recursive: true });
|
|
1029
|
+
await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
1030
|
+
}
|
|
914
1031
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
915
1032
|
if (!existsSync6(employeesPath)) return [];
|
|
916
1033
|
try {
|
|
@@ -922,6 +1039,19 @@ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
|
922
1039
|
function getEmployee(employees, name) {
|
|
923
1040
|
return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
924
1041
|
}
|
|
1042
|
+
function getEmployeeByRole(employees, role) {
|
|
1043
|
+
const lower = role.toLowerCase();
|
|
1044
|
+
return employees.find((e) => e.role.toLowerCase() === lower);
|
|
1045
|
+
}
|
|
1046
|
+
function getEmployeeNamesByRole(employees, role) {
|
|
1047
|
+
const lower = role.toLowerCase();
|
|
1048
|
+
return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
|
|
1049
|
+
}
|
|
1050
|
+
function hasRole(agentName, role) {
|
|
1051
|
+
const employees = loadEmployeesSync();
|
|
1052
|
+
const emp = getEmployee(employees, agentName);
|
|
1053
|
+
return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
|
|
1054
|
+
}
|
|
925
1055
|
function baseAgentName(name, employees) {
|
|
926
1056
|
const match = name.match(/^([a-zA-Z]+)\d+$/);
|
|
927
1057
|
if (!match) return name;
|
|
@@ -936,7 +1066,131 @@ function isMultiInstance(agentName, employees) {
|
|
|
936
1066
|
if (!emp) return false;
|
|
937
1067
|
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
938
1068
|
}
|
|
939
|
-
|
|
1069
|
+
function addEmployee(employees, employee) {
|
|
1070
|
+
const { systemPrompt: _legacyPrompt, ...rest } = employee;
|
|
1071
|
+
const normalized = { ...rest, name: employee.name.toLowerCase() };
|
|
1072
|
+
if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
|
|
1073
|
+
throw new Error(`Employee '${normalized.name}' already exists`);
|
|
1074
|
+
}
|
|
1075
|
+
return [...employees, normalized];
|
|
1076
|
+
}
|
|
1077
|
+
function appendToCoordinatorTeam(employee) {
|
|
1078
|
+
const coordinator = getCoordinatorEmployee(loadEmployeesSync());
|
|
1079
|
+
if (!coordinator) return;
|
|
1080
|
+
const idPath = path5.join(IDENTITY_DIR, `${coordinator.name}.md`);
|
|
1081
|
+
if (!existsSync6(idPath)) return;
|
|
1082
|
+
const content = readFileSync5(idPath, "utf-8");
|
|
1083
|
+
if (content.includes(`**${capitalize(employee.name)}`)) return;
|
|
1084
|
+
const teamMatch = content.match(TEAM_SECTION_RE);
|
|
1085
|
+
if (!teamMatch || teamMatch.index === void 0) return;
|
|
1086
|
+
const afterTeam = content.slice(teamMatch.index + teamMatch[0].length);
|
|
1087
|
+
const nextHeading = afterTeam.match(/\n## /);
|
|
1088
|
+
const entry = `
|
|
1089
|
+
**${capitalize(employee.name)} (${employee.role}):** Newly hired. Update this description as the role develops.
|
|
1090
|
+
`;
|
|
1091
|
+
let updated;
|
|
1092
|
+
if (nextHeading && nextHeading.index !== void 0) {
|
|
1093
|
+
const insertAt = teamMatch.index + teamMatch[0].length + nextHeading.index;
|
|
1094
|
+
updated = content.slice(0, insertAt) + entry + content.slice(insertAt);
|
|
1095
|
+
} else {
|
|
1096
|
+
updated = content.trimEnd() + "\n" + entry;
|
|
1097
|
+
}
|
|
1098
|
+
writeFileSync4(idPath, updated, "utf-8");
|
|
1099
|
+
}
|
|
1100
|
+
function capitalize(s) {
|
|
1101
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
1102
|
+
}
|
|
1103
|
+
async function hireEmployee(employee) {
|
|
1104
|
+
const employees = await loadEmployees();
|
|
1105
|
+
const updated = addEmployee(employees, employee);
|
|
1106
|
+
await saveEmployees(updated);
|
|
1107
|
+
try {
|
|
1108
|
+
appendToCoordinatorTeam(employee);
|
|
1109
|
+
} catch {
|
|
1110
|
+
}
|
|
1111
|
+
try {
|
|
1112
|
+
const { loadAgentConfig: loadAgentConfig2, saveAgentConfig: saveAgentConfig2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
|
|
1113
|
+
const config = loadAgentConfig2();
|
|
1114
|
+
const name = employee.name.toLowerCase();
|
|
1115
|
+
if (!config[name] && config["default"]) {
|
|
1116
|
+
config[name] = { ...config["default"] };
|
|
1117
|
+
saveAgentConfig2(config);
|
|
1118
|
+
}
|
|
1119
|
+
} catch {
|
|
1120
|
+
}
|
|
1121
|
+
return updated;
|
|
1122
|
+
}
|
|
1123
|
+
async function normalizeRosterCase(rosterPath) {
|
|
1124
|
+
const employees = await loadEmployees(rosterPath);
|
|
1125
|
+
let changed = false;
|
|
1126
|
+
for (const emp of employees) {
|
|
1127
|
+
if (emp.name !== emp.name.toLowerCase()) {
|
|
1128
|
+
const oldName = emp.name;
|
|
1129
|
+
emp.name = emp.name.toLowerCase();
|
|
1130
|
+
changed = true;
|
|
1131
|
+
try {
|
|
1132
|
+
const identityDir = path5.join(os4.homedir(), ".exe-os", "identity");
|
|
1133
|
+
const oldPath = path5.join(identityDir, `${oldName}.md`);
|
|
1134
|
+
const newPath = path5.join(identityDir, `${emp.name}.md`);
|
|
1135
|
+
if (existsSync6(oldPath) && !existsSync6(newPath)) {
|
|
1136
|
+
renameSync3(oldPath, newPath);
|
|
1137
|
+
} else if (existsSync6(oldPath) && oldPath !== newPath) {
|
|
1138
|
+
const content = readFileSync5(oldPath, "utf-8");
|
|
1139
|
+
writeFileSync4(newPath, content, "utf-8");
|
|
1140
|
+
if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
|
|
1141
|
+
unlinkSync(oldPath);
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
} catch {
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
if (changed) {
|
|
1149
|
+
await saveEmployees(employees, rosterPath);
|
|
1150
|
+
}
|
|
1151
|
+
return changed;
|
|
1152
|
+
}
|
|
1153
|
+
function findExeBin() {
|
|
1154
|
+
try {
|
|
1155
|
+
return execSync3(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
|
|
1156
|
+
} catch {
|
|
1157
|
+
return null;
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
function registerBinSymlinks(name) {
|
|
1161
|
+
const created = [];
|
|
1162
|
+
const skipped = [];
|
|
1163
|
+
const errors = [];
|
|
1164
|
+
const exeBinPath = findExeBin();
|
|
1165
|
+
if (!exeBinPath) {
|
|
1166
|
+
errors.push("Could not find 'exe-os' in PATH");
|
|
1167
|
+
return { created, skipped, errors };
|
|
1168
|
+
}
|
|
1169
|
+
const binDir = path5.dirname(exeBinPath);
|
|
1170
|
+
let target;
|
|
1171
|
+
try {
|
|
1172
|
+
target = readlinkSync(exeBinPath);
|
|
1173
|
+
} catch {
|
|
1174
|
+
errors.push("Could not read 'exe' symlink");
|
|
1175
|
+
return { created, skipped, errors };
|
|
1176
|
+
}
|
|
1177
|
+
for (const suffix of ["", "-opencode"]) {
|
|
1178
|
+
const linkName = `${name}${suffix}`;
|
|
1179
|
+
const linkPath = path5.join(binDir, linkName);
|
|
1180
|
+
if (existsSync6(linkPath)) {
|
|
1181
|
+
skipped.push(linkName);
|
|
1182
|
+
continue;
|
|
1183
|
+
}
|
|
1184
|
+
try {
|
|
1185
|
+
symlinkSync(target, linkPath);
|
|
1186
|
+
created.push(linkName);
|
|
1187
|
+
} catch (err) {
|
|
1188
|
+
errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
return { created, skipped, errors };
|
|
1192
|
+
}
|
|
1193
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR, TEAM_SECTION_RE;
|
|
940
1194
|
var init_employees = __esm({
|
|
941
1195
|
"src/lib/employees.ts"() {
|
|
942
1196
|
"use strict";
|
|
@@ -946,6 +1200,7 @@ var init_employees = __esm({
|
|
|
946
1200
|
COORDINATOR_ROLE = "COO";
|
|
947
1201
|
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
948
1202
|
IDENTITY_DIR = path5.join(EXE_AI_DIR, "identity");
|
|
1203
|
+
TEAM_SECTION_RE = /^## Team\b.*$/m;
|
|
949
1204
|
}
|
|
950
1205
|
});
|
|
951
1206
|
|
|
@@ -2627,6 +2882,13 @@ async function ensureSchema() {
|
|
|
2627
2882
|
} catch (e) {
|
|
2628
2883
|
logCatchDebug("migration", e);
|
|
2629
2884
|
}
|
|
2885
|
+
for (const col of ["created_by_agent TEXT", "created_by_device TEXT", "source_session_id TEXT"]) {
|
|
2886
|
+
try {
|
|
2887
|
+
await client.execute({ sql: `ALTER TABLE behaviors ADD COLUMN ${col}`, args: [] });
|
|
2888
|
+
} catch (e) {
|
|
2889
|
+
logCatchDebug("migration", e);
|
|
2890
|
+
}
|
|
2891
|
+
}
|
|
2630
2892
|
try {
|
|
2631
2893
|
await client.execute({
|
|
2632
2894
|
sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
|
|
@@ -3843,6 +4105,22 @@ async function ensureSchema() {
|
|
|
3843
4105
|
} catch (e) {
|
|
3844
4106
|
logCatchDebug("migration", e);
|
|
3845
4107
|
}
|
|
4108
|
+
try {
|
|
4109
|
+
await client.execute({
|
|
4110
|
+
sql: `ALTER TABLE memories ADD COLUMN visibility TEXT DEFAULT 'private'`,
|
|
4111
|
+
args: []
|
|
4112
|
+
});
|
|
4113
|
+
} catch (e) {
|
|
4114
|
+
logCatchDebug("migration", e);
|
|
4115
|
+
}
|
|
4116
|
+
try {
|
|
4117
|
+
await client.execute({
|
|
4118
|
+
sql: `ALTER TABLE memories ADD COLUMN strength REAL DEFAULT 1.0`,
|
|
4119
|
+
args: []
|
|
4120
|
+
});
|
|
4121
|
+
} catch (e) {
|
|
4122
|
+
logCatchDebug("migration", e);
|
|
4123
|
+
}
|
|
3846
4124
|
}
|
|
3847
4125
|
async function disposeDatabase() {
|
|
3848
4126
|
if (_walCheckpointTimer) {
|
|
@@ -3894,6 +4172,23 @@ var init_database = __esm({
|
|
|
3894
4172
|
});
|
|
3895
4173
|
|
|
3896
4174
|
// src/lib/license.ts
|
|
4175
|
+
var license_exports = {};
|
|
4176
|
+
__export(license_exports, {
|
|
4177
|
+
LICENSE_PUBLIC_KEY_PEM: () => LICENSE_PUBLIC_KEY_PEM,
|
|
4178
|
+
PLAN_LIMITS: () => PLAN_LIMITS,
|
|
4179
|
+
assertVpsLicense: () => assertVpsLicense,
|
|
4180
|
+
checkLicense: () => checkLicense,
|
|
4181
|
+
getCachedLicense: () => getCachedLicense,
|
|
4182
|
+
isFeatureAllowed: () => isFeatureAllowed,
|
|
4183
|
+
loadDeviceId: () => loadDeviceId,
|
|
4184
|
+
loadLicense: () => loadLicense,
|
|
4185
|
+
mirrorLicenseKey: () => mirrorLicenseKey,
|
|
4186
|
+
readCachedLicenseToken: () => readCachedLicenseToken,
|
|
4187
|
+
saveLicense: () => saveLicense,
|
|
4188
|
+
startLicenseRevalidation: () => startLicenseRevalidation,
|
|
4189
|
+
stopLicenseRevalidation: () => stopLicenseRevalidation,
|
|
4190
|
+
validateLicense: () => validateLicense
|
|
4191
|
+
});
|
|
3897
4192
|
import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync10, mkdirSync as mkdirSync5 } from "fs";
|
|
3898
4193
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
3899
4194
|
import { createRequire as createRequire2 } from "module";
|
|
@@ -3901,7 +4196,411 @@ import { pathToFileURL as pathToFileURL2 } from "url";
|
|
|
3901
4196
|
import os7 from "os";
|
|
3902
4197
|
import path9 from "path";
|
|
3903
4198
|
import { jwtVerify, importSPKI } from "jose";
|
|
3904
|
-
|
|
4199
|
+
async function fetchRetry(url, init) {
|
|
4200
|
+
try {
|
|
4201
|
+
return await fetch(url, init);
|
|
4202
|
+
} catch {
|
|
4203
|
+
await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
|
|
4204
|
+
return fetch(url, { ...init, signal: AbortSignal.timeout(1e4) });
|
|
4205
|
+
}
|
|
4206
|
+
}
|
|
4207
|
+
function loadDeviceId() {
|
|
4208
|
+
const deviceJsonPath = path9.join(EXE_AI_DIR, "device.json");
|
|
4209
|
+
try {
|
|
4210
|
+
if (existsSync10(deviceJsonPath)) {
|
|
4211
|
+
const data = JSON.parse(readFileSync8(deviceJsonPath, "utf8"));
|
|
4212
|
+
if (data.deviceId) return data.deviceId;
|
|
4213
|
+
}
|
|
4214
|
+
} catch {
|
|
4215
|
+
}
|
|
4216
|
+
try {
|
|
4217
|
+
if (existsSync10(DEVICE_ID_PATH)) {
|
|
4218
|
+
const id2 = readFileSync8(DEVICE_ID_PATH, "utf8").trim();
|
|
4219
|
+
if (id2) return id2;
|
|
4220
|
+
}
|
|
4221
|
+
} catch {
|
|
4222
|
+
}
|
|
4223
|
+
const id = randomUUID2();
|
|
4224
|
+
mkdirSync5(EXE_AI_DIR, { recursive: true });
|
|
4225
|
+
writeFileSync6(DEVICE_ID_PATH, id, "utf8");
|
|
4226
|
+
return id;
|
|
4227
|
+
}
|
|
4228
|
+
function loadLicense() {
|
|
4229
|
+
try {
|
|
4230
|
+
if (!existsSync10(LICENSE_PATH)) return null;
|
|
4231
|
+
return readFileSync8(LICENSE_PATH, "utf8").trim();
|
|
4232
|
+
} catch {
|
|
4233
|
+
return null;
|
|
4234
|
+
}
|
|
4235
|
+
}
|
|
4236
|
+
function saveLicense(apiKey) {
|
|
4237
|
+
mkdirSync5(EXE_AI_DIR, { recursive: true });
|
|
4238
|
+
writeFileSync6(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
|
|
4239
|
+
}
|
|
4240
|
+
async function verifyLicenseJwt(token) {
|
|
4241
|
+
try {
|
|
4242
|
+
const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
|
|
4243
|
+
const { payload } = await jwtVerify(token, key, {
|
|
4244
|
+
algorithms: [LICENSE_JWT_ALG]
|
|
4245
|
+
});
|
|
4246
|
+
const plan = payload.plan ?? "free";
|
|
4247
|
+
const email = payload.sub ?? "";
|
|
4248
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
4249
|
+
return {
|
|
4250
|
+
valid: true,
|
|
4251
|
+
plan,
|
|
4252
|
+
email,
|
|
4253
|
+
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
4254
|
+
deviceLimit: limits.devices,
|
|
4255
|
+
employeeLimit: limits.employees,
|
|
4256
|
+
memoryLimit: limits.memories
|
|
4257
|
+
};
|
|
4258
|
+
} catch {
|
|
4259
|
+
return null;
|
|
4260
|
+
}
|
|
4261
|
+
}
|
|
4262
|
+
async function getCachedLicense() {
|
|
4263
|
+
try {
|
|
4264
|
+
if (!existsSync10(CACHE_PATH)) return null;
|
|
4265
|
+
const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
|
|
4266
|
+
if (!raw.token || typeof raw.token !== "string") return null;
|
|
4267
|
+
return await verifyLicenseJwt(raw.token);
|
|
4268
|
+
} catch {
|
|
4269
|
+
return null;
|
|
4270
|
+
}
|
|
4271
|
+
}
|
|
4272
|
+
function readCachedLicenseToken() {
|
|
4273
|
+
try {
|
|
4274
|
+
if (!existsSync10(CACHE_PATH)) return null;
|
|
4275
|
+
const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
|
|
4276
|
+
return typeof raw.token === "string" ? raw.token : null;
|
|
4277
|
+
} catch {
|
|
4278
|
+
return null;
|
|
4279
|
+
}
|
|
4280
|
+
}
|
|
4281
|
+
function getRawCachedPlan() {
|
|
4282
|
+
try {
|
|
4283
|
+
const token = readCachedLicenseToken();
|
|
4284
|
+
if (!token) return null;
|
|
4285
|
+
const parts = token.split(".");
|
|
4286
|
+
if (parts.length !== 3) return null;
|
|
4287
|
+
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
|
|
4288
|
+
const plan = payload.plan ?? "free";
|
|
4289
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
4290
|
+
process.stderr.write(
|
|
4291
|
+
`[license] WARN: using unverified cached plan (API unreachable, JWT expired). Plan: ${plan}
|
|
4292
|
+
`
|
|
4293
|
+
);
|
|
4294
|
+
return {
|
|
4295
|
+
valid: true,
|
|
4296
|
+
plan,
|
|
4297
|
+
email: payload.sub ?? "",
|
|
4298
|
+
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
4299
|
+
deviceLimit: limits.devices,
|
|
4300
|
+
employeeLimit: limits.employees,
|
|
4301
|
+
memoryLimit: limits.memories
|
|
4302
|
+
};
|
|
4303
|
+
} catch {
|
|
4304
|
+
return null;
|
|
4305
|
+
}
|
|
4306
|
+
}
|
|
4307
|
+
function cacheResponse(token) {
|
|
4308
|
+
try {
|
|
4309
|
+
writeFileSync6(CACHE_PATH, JSON.stringify({ token }), "utf8");
|
|
4310
|
+
} catch {
|
|
4311
|
+
}
|
|
4312
|
+
}
|
|
4313
|
+
function loadPrismaForLicense() {
|
|
4314
|
+
if (_prismaFailed) return null;
|
|
4315
|
+
const dbUrl = process.env.DATABASE_URL;
|
|
4316
|
+
if (!dbUrl) {
|
|
4317
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path9.join(os7.homedir(), "exe-db");
|
|
4318
|
+
if (!existsSync10(path9.join(exeDbRoot, "package.json"))) {
|
|
4319
|
+
_prismaFailed = true;
|
|
4320
|
+
return null;
|
|
4321
|
+
}
|
|
4322
|
+
}
|
|
4323
|
+
if (!_prismaPromise) {
|
|
4324
|
+
_prismaPromise = (async () => {
|
|
4325
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
4326
|
+
if (explicitPath) {
|
|
4327
|
+
const mod2 = await import(pathToFileURL2(explicitPath).href);
|
|
4328
|
+
const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
|
|
4329
|
+
if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
|
|
4330
|
+
return new Ctor2();
|
|
4331
|
+
}
|
|
4332
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path9.join(os7.homedir(), "exe-db");
|
|
4333
|
+
const req = createRequire2(path9.join(exeDbRoot, "package.json"));
|
|
4334
|
+
const entry = req.resolve("@prisma/client");
|
|
4335
|
+
const mod = await import(pathToFileURL2(entry).href);
|
|
4336
|
+
const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
|
|
4337
|
+
if (!Ctor) throw new Error(`No PrismaClient in ${entry}`);
|
|
4338
|
+
return new Ctor();
|
|
4339
|
+
})().catch((err) => {
|
|
4340
|
+
_prismaFailed = true;
|
|
4341
|
+
_prismaPromise = null;
|
|
4342
|
+
throw err;
|
|
4343
|
+
});
|
|
4344
|
+
}
|
|
4345
|
+
return _prismaPromise;
|
|
4346
|
+
}
|
|
4347
|
+
async function validateViaPostgres(apiKey) {
|
|
4348
|
+
const loader = loadPrismaForLicense();
|
|
4349
|
+
if (!loader) return null;
|
|
4350
|
+
try {
|
|
4351
|
+
const prisma = await loader;
|
|
4352
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
4353
|
+
`SELECT plan, email, status, device_limit, employee_limit, memory_limit, expires_at
|
|
4354
|
+
FROM billing.licenses WHERE key = $1 LIMIT 1`,
|
|
4355
|
+
apiKey
|
|
4356
|
+
);
|
|
4357
|
+
if (!rows || rows.length === 0) return null;
|
|
4358
|
+
const row = rows[0];
|
|
4359
|
+
if (row.status !== "active") return null;
|
|
4360
|
+
if (row.expires_at && new Date(row.expires_at) < /* @__PURE__ */ new Date()) return null;
|
|
4361
|
+
const plan = row.plan;
|
|
4362
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
4363
|
+
return {
|
|
4364
|
+
valid: true,
|
|
4365
|
+
plan,
|
|
4366
|
+
email: row.email,
|
|
4367
|
+
expiresAt: row.expires_at ? new Date(row.expires_at).toISOString() : null,
|
|
4368
|
+
deviceLimit: row.device_limit ?? limits.devices,
|
|
4369
|
+
employeeLimit: row.employee_limit ?? limits.employees,
|
|
4370
|
+
memoryLimit: row.memory_limit ?? limits.memories
|
|
4371
|
+
};
|
|
4372
|
+
} catch {
|
|
4373
|
+
return null;
|
|
4374
|
+
}
|
|
4375
|
+
}
|
|
4376
|
+
async function validateViaCFWorker(apiKey, deviceId) {
|
|
4377
|
+
try {
|
|
4378
|
+
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
4379
|
+
method: "POST",
|
|
4380
|
+
headers: { "Content-Type": "application/json" },
|
|
4381
|
+
body: JSON.stringify({ apiKey, deviceId }),
|
|
4382
|
+
signal: AbortSignal.timeout(1e4)
|
|
4383
|
+
});
|
|
4384
|
+
if (!res.ok) return null;
|
|
4385
|
+
const data = await res.json();
|
|
4386
|
+
if (data.error === "device_limit_exceeded") return null;
|
|
4387
|
+
if (!data.valid) return null;
|
|
4388
|
+
if (data.token) {
|
|
4389
|
+
cacheResponse(data.token);
|
|
4390
|
+
const verified = await verifyLicenseJwt(data.token);
|
|
4391
|
+
if (verified) return verified;
|
|
4392
|
+
}
|
|
4393
|
+
const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
|
|
4394
|
+
return {
|
|
4395
|
+
valid: data.valid,
|
|
4396
|
+
plan: data.plan,
|
|
4397
|
+
email: data.email,
|
|
4398
|
+
expiresAt: data.expiresAt,
|
|
4399
|
+
deviceLimit: limits.devices,
|
|
4400
|
+
employeeLimit: limits.employees,
|
|
4401
|
+
memoryLimit: limits.memories
|
|
4402
|
+
};
|
|
4403
|
+
} catch {
|
|
4404
|
+
return null;
|
|
4405
|
+
}
|
|
4406
|
+
}
|
|
4407
|
+
async function validateLicense(apiKey, deviceId) {
|
|
4408
|
+
const did = deviceId ?? loadDeviceId();
|
|
4409
|
+
const pgResult = await validateViaPostgres(apiKey);
|
|
4410
|
+
if (pgResult) {
|
|
4411
|
+
try {
|
|
4412
|
+
writeFileSync6(CACHE_PATH, JSON.stringify({ pgLicense: pgResult, ts: Date.now() }), "utf8");
|
|
4413
|
+
} catch {
|
|
4414
|
+
}
|
|
4415
|
+
return pgResult;
|
|
4416
|
+
}
|
|
4417
|
+
const cfResult = await validateViaCFWorker(apiKey, did);
|
|
4418
|
+
if (cfResult) return cfResult;
|
|
4419
|
+
const cached = await getCachedLicense();
|
|
4420
|
+
if (cached) return cached;
|
|
4421
|
+
try {
|
|
4422
|
+
if (existsSync10(CACHE_PATH)) {
|
|
4423
|
+
const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
|
|
4424
|
+
if (raw.pgLicense && raw.ts && Date.now() - raw.ts < 7 * 24 * 60 * 60 * 1e3) {
|
|
4425
|
+
return raw.pgLicense;
|
|
4426
|
+
}
|
|
4427
|
+
}
|
|
4428
|
+
} catch {
|
|
4429
|
+
}
|
|
4430
|
+
const rawFallback = getRawCachedPlan();
|
|
4431
|
+
if (rawFallback) return rawFallback;
|
|
4432
|
+
return { ...FREE_LICENSE, valid: false };
|
|
4433
|
+
}
|
|
4434
|
+
function getCacheAgeMs() {
|
|
4435
|
+
try {
|
|
4436
|
+
const { statSync: statSync5 } = __require("fs");
|
|
4437
|
+
const s = statSync5(CACHE_PATH);
|
|
4438
|
+
return Date.now() - s.mtimeMs;
|
|
4439
|
+
} catch {
|
|
4440
|
+
return Infinity;
|
|
4441
|
+
}
|
|
4442
|
+
}
|
|
4443
|
+
async function checkLicense() {
|
|
4444
|
+
let key = loadLicense();
|
|
4445
|
+
if (!key) {
|
|
4446
|
+
try {
|
|
4447
|
+
const configPath = path9.join(EXE_AI_DIR, "config.json");
|
|
4448
|
+
if (existsSync10(configPath)) {
|
|
4449
|
+
const raw = JSON.parse(readFileSync8(configPath, "utf8"));
|
|
4450
|
+
const cloud = raw.cloud;
|
|
4451
|
+
if (cloud?.apiKey) {
|
|
4452
|
+
key = cloud.apiKey;
|
|
4453
|
+
saveLicense(key);
|
|
4454
|
+
}
|
|
4455
|
+
}
|
|
4456
|
+
} catch {
|
|
4457
|
+
}
|
|
4458
|
+
}
|
|
4459
|
+
if (!key) return FREE_LICENSE;
|
|
4460
|
+
const cached = await getCachedLicense();
|
|
4461
|
+
if (cached && getCacheAgeMs() < CACHE_MAX_AGE_MS) return cached;
|
|
4462
|
+
const deviceId = loadDeviceId();
|
|
4463
|
+
return validateLicense(key, deviceId);
|
|
4464
|
+
}
|
|
4465
|
+
function isFeatureAllowed(license, feature) {
|
|
4466
|
+
switch (feature) {
|
|
4467
|
+
case "cloud_sync":
|
|
4468
|
+
case "external_agents":
|
|
4469
|
+
case "wiki":
|
|
4470
|
+
return license.plan !== "free";
|
|
4471
|
+
case "unlimited_employees":
|
|
4472
|
+
return license.plan === "team" || license.plan === "agency" || license.plan === "enterprise";
|
|
4473
|
+
}
|
|
4474
|
+
}
|
|
4475
|
+
function mirrorLicenseKey(apiKey) {
|
|
4476
|
+
const trimmed = apiKey.trim();
|
|
4477
|
+
if (!trimmed) return;
|
|
4478
|
+
saveLicense(trimmed);
|
|
4479
|
+
}
|
|
4480
|
+
async function assertVpsLicense(opts) {
|
|
4481
|
+
const env = opts?.env ?? process.env;
|
|
4482
|
+
const inProduction = env.NODE_ENV === "production";
|
|
4483
|
+
if (!opts?.force && !inProduction) {
|
|
4484
|
+
return { ...FREE_LICENSE, plan: "free" };
|
|
4485
|
+
}
|
|
4486
|
+
const envKey = env.EXE_LICENSE_KEY?.trim();
|
|
4487
|
+
if (envKey) {
|
|
4488
|
+
saveLicense(envKey);
|
|
4489
|
+
}
|
|
4490
|
+
const apiKey = envKey ?? loadLicense();
|
|
4491
|
+
if (!apiKey) {
|
|
4492
|
+
throw new Error(
|
|
4493
|
+
"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."
|
|
4494
|
+
);
|
|
4495
|
+
}
|
|
4496
|
+
const deviceId = loadDeviceId();
|
|
4497
|
+
let backendResponse = null;
|
|
4498
|
+
let explicitRejection = false;
|
|
4499
|
+
let transientFailure = false;
|
|
4500
|
+
try {
|
|
4501
|
+
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
4502
|
+
method: "POST",
|
|
4503
|
+
headers: { "Content-Type": "application/json" },
|
|
4504
|
+
body: JSON.stringify({ apiKey, deviceId }),
|
|
4505
|
+
signal: AbortSignal.timeout(1e4)
|
|
4506
|
+
});
|
|
4507
|
+
if (res.ok) {
|
|
4508
|
+
backendResponse = await res.json();
|
|
4509
|
+
if (!backendResponse.valid) explicitRejection = true;
|
|
4510
|
+
} else if (res.status === 401 || res.status === 403) {
|
|
4511
|
+
explicitRejection = true;
|
|
4512
|
+
} else {
|
|
4513
|
+
transientFailure = true;
|
|
4514
|
+
}
|
|
4515
|
+
} catch {
|
|
4516
|
+
transientFailure = true;
|
|
4517
|
+
}
|
|
4518
|
+
if (backendResponse?.valid) {
|
|
4519
|
+
if (backendResponse.token) {
|
|
4520
|
+
cacheResponse(backendResponse.token);
|
|
4521
|
+
const verified = await verifyLicenseJwt(backendResponse.token);
|
|
4522
|
+
if (verified) return verified;
|
|
4523
|
+
}
|
|
4524
|
+
const limits = PLAN_LIMITS[backendResponse.plan] ?? PLAN_LIMITS.free;
|
|
4525
|
+
return {
|
|
4526
|
+
valid: true,
|
|
4527
|
+
plan: backendResponse.plan,
|
|
4528
|
+
email: backendResponse.email,
|
|
4529
|
+
expiresAt: backendResponse.expiresAt,
|
|
4530
|
+
deviceLimit: limits.devices,
|
|
4531
|
+
employeeLimit: limits.employees,
|
|
4532
|
+
memoryLimit: limits.memories
|
|
4533
|
+
};
|
|
4534
|
+
}
|
|
4535
|
+
if (explicitRejection) {
|
|
4536
|
+
throw new Error(
|
|
4537
|
+
`License invalid or expired. Renew at https://askexe.com, then restart. Backend rejected key ending in ...${apiKey.slice(-4)}.`
|
|
4538
|
+
);
|
|
4539
|
+
}
|
|
4540
|
+
if (!transientFailure) {
|
|
4541
|
+
throw new Error(
|
|
4542
|
+
"License validation failed: unknown backend state. Restore network connectivity to https://cloud.askexe.com and retry."
|
|
4543
|
+
);
|
|
4544
|
+
}
|
|
4545
|
+
const fresh = await getCachedLicense();
|
|
4546
|
+
if (fresh && fresh.valid) return fresh;
|
|
4547
|
+
const graceDays = opts?.offlineGraceDays ?? 7;
|
|
4548
|
+
const graceMs = graceDays * 24 * 60 * 60 * 1e3;
|
|
4549
|
+
try {
|
|
4550
|
+
const token = readCachedLicenseToken();
|
|
4551
|
+
if (token) {
|
|
4552
|
+
const payloadB64 = token.split(".")[1];
|
|
4553
|
+
if (payloadB64) {
|
|
4554
|
+
const payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString());
|
|
4555
|
+
const expMs = (payload.exp ?? 0) * 1e3;
|
|
4556
|
+
if (Date.now() < expMs + graceMs) {
|
|
4557
|
+
const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
|
|
4558
|
+
const { payload: verified } = await jwtVerify(token, key, {
|
|
4559
|
+
algorithms: [LICENSE_JWT_ALG],
|
|
4560
|
+
clockTolerance: graceDays * 24 * 60 * 60
|
|
4561
|
+
});
|
|
4562
|
+
const plan = verified.plan ?? "free";
|
|
4563
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
4564
|
+
return {
|
|
4565
|
+
valid: true,
|
|
4566
|
+
plan,
|
|
4567
|
+
email: verified.sub ?? "",
|
|
4568
|
+
expiresAt: verified.exp ? new Date(verified.exp * 1e3).toISOString() : null,
|
|
4569
|
+
deviceLimit: limits.devices,
|
|
4570
|
+
employeeLimit: limits.employees,
|
|
4571
|
+
memoryLimit: limits.memories
|
|
4572
|
+
};
|
|
4573
|
+
}
|
|
4574
|
+
}
|
|
4575
|
+
}
|
|
4576
|
+
} catch {
|
|
4577
|
+
}
|
|
4578
|
+
throw new Error(
|
|
4579
|
+
`License validation unreachable for more than ${graceDays} days. Restore network connectivity to https://cloud.askexe.com and retry. This VPS image refuses to boot after the offline grace window.`
|
|
4580
|
+
);
|
|
4581
|
+
}
|
|
4582
|
+
function startLicenseRevalidation(intervalMs = 36e5) {
|
|
4583
|
+
if (_revalTimer) return;
|
|
4584
|
+
_revalTimer = setInterval(async () => {
|
|
4585
|
+
try {
|
|
4586
|
+
const license = await checkLicense();
|
|
4587
|
+
if (!license.valid) {
|
|
4588
|
+
process.stderr.write("[exe-os] License expired or invalid \u2014 features may be restricted\n");
|
|
4589
|
+
}
|
|
4590
|
+
} catch {
|
|
4591
|
+
}
|
|
4592
|
+
}, intervalMs);
|
|
4593
|
+
if (_revalTimer && typeof _revalTimer === "object" && "unref" in _revalTimer) {
|
|
4594
|
+
_revalTimer.unref();
|
|
4595
|
+
}
|
|
4596
|
+
}
|
|
4597
|
+
function stopLicenseRevalidation() {
|
|
4598
|
+
if (_revalTimer) {
|
|
4599
|
+
clearInterval(_revalTimer);
|
|
4600
|
+
_revalTimer = null;
|
|
4601
|
+
}
|
|
4602
|
+
}
|
|
4603
|
+
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;
|
|
3905
4604
|
var init_license = __esm({
|
|
3906
4605
|
"src/lib/license.ts"() {
|
|
3907
4606
|
"use strict";
|
|
@@ -3909,7 +4608,13 @@ var init_license = __esm({
|
|
|
3909
4608
|
LICENSE_PATH = path9.join(EXE_AI_DIR, "license.key");
|
|
3910
4609
|
CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
|
|
3911
4610
|
DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
|
|
3912
|
-
API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com
|
|
4611
|
+
API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://cloud.askexe.com";
|
|
4612
|
+
RETRY_DELAY_MS = 500;
|
|
4613
|
+
LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
4614
|
+
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
|
|
4615
|
+
4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
|
|
4616
|
+
-----END PUBLIC KEY-----`;
|
|
4617
|
+
LICENSE_JWT_ALG = "ES256";
|
|
3913
4618
|
PLAN_LIMITS = {
|
|
3914
4619
|
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
3915
4620
|
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
@@ -3917,6 +4622,19 @@ var init_license = __esm({
|
|
|
3917
4622
|
agency: { devices: 50, employees: 100, memories: 1e7 },
|
|
3918
4623
|
enterprise: { devices: -1, employees: -1, memories: -1 }
|
|
3919
4624
|
};
|
|
4625
|
+
FREE_LICENSE = {
|
|
4626
|
+
valid: true,
|
|
4627
|
+
plan: "free",
|
|
4628
|
+
email: "",
|
|
4629
|
+
expiresAt: null,
|
|
4630
|
+
deviceLimit: 1,
|
|
4631
|
+
employeeLimit: 1,
|
|
4632
|
+
memoryLimit: 5e3
|
|
4633
|
+
};
|
|
4634
|
+
_prismaPromise = null;
|
|
4635
|
+
_prismaFailed = false;
|
|
4636
|
+
CACHE_MAX_AGE_MS = 36e5;
|
|
4637
|
+
_revalTimer = null;
|
|
3920
4638
|
}
|
|
3921
4639
|
});
|
|
3922
4640
|
|
|
@@ -4404,6 +5122,19 @@ async function resolveTask(client, identifier, scopeSession) {
|
|
|
4404
5122
|
args: [identifier, ...scope.args]
|
|
4405
5123
|
});
|
|
4406
5124
|
if (result.rows.length === 1) return result.rows[0];
|
|
5125
|
+
if (/^[a-f0-9]{7,12}$/i.test(identifier)) {
|
|
5126
|
+
result = await client.execute({
|
|
5127
|
+
sql: `SELECT * FROM tasks WHERE id LIKE ?`,
|
|
5128
|
+
args: [`${identifier}%`]
|
|
5129
|
+
});
|
|
5130
|
+
if (result.rows.length === 1) return result.rows[0];
|
|
5131
|
+
if (result.rows.length > 1) {
|
|
5132
|
+
const matches = result.rows.map((r) => `${String(r.id)} "${String(r.title)}" (${String(r.status)})`).join(", ");
|
|
5133
|
+
throw new Error(
|
|
5134
|
+
`Multiple tasks match short-ID "${identifier}": ${matches}. Use a longer prefix to disambiguate.`
|
|
5135
|
+
);
|
|
5136
|
+
}
|
|
5137
|
+
}
|
|
4407
5138
|
result = await client.execute({
|
|
4408
5139
|
sql: `SELECT * FROM tasks WHERE task_file LIKE ?${scope.sql}`,
|
|
4409
5140
|
args: [`%${identifier}%`, ...scope.args]
|
|
@@ -5258,12 +5989,13 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
5258
5989
|
WHERE blocked_by = ? AND status = 'blocked'`,
|
|
5259
5990
|
args: [now, taskId]
|
|
5260
5991
|
});
|
|
5261
|
-
if (
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
|
|
5266
|
-
|
|
5992
|
+
if (unblocked.rowsAffected === 0) return;
|
|
5993
|
+
const ubScope = sessionScopeFilter();
|
|
5994
|
+
const unblockedRows = await client.execute({
|
|
5995
|
+
sql: `SELECT id, title, assigned_to, priority, task_file FROM tasks WHERE blocked_by IS NULL AND updated_at = ?${ubScope.sql}`,
|
|
5996
|
+
args: [now, ...ubScope.args]
|
|
5997
|
+
});
|
|
5998
|
+
if (baseDir) {
|
|
5267
5999
|
for (const ur of unblockedRows.rows) {
|
|
5268
6000
|
try {
|
|
5269
6001
|
const ubFile = path16.join(baseDir, String(ur.task_file));
|
|
@@ -5275,6 +6007,19 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
5275
6007
|
}
|
|
5276
6008
|
}
|
|
5277
6009
|
}
|
|
6010
|
+
if (unblockedRows.rows.length > 0 && !process.env.VITEST) {
|
|
6011
|
+
try {
|
|
6012
|
+
const { queueIntercom: queueIntercom2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
6013
|
+
const dispatched = /* @__PURE__ */ new Set();
|
|
6014
|
+
for (const ur of unblockedRows.rows) {
|
|
6015
|
+
const assignee = String(ur.assigned_to);
|
|
6016
|
+
if (dispatched.has(assignee)) continue;
|
|
6017
|
+
dispatched.add(assignee);
|
|
6018
|
+
queueIntercom2(`${assignee}`, `unblocked: "${String(ur.title)}" is now ready`);
|
|
6019
|
+
}
|
|
6020
|
+
} catch {
|
|
6021
|
+
}
|
|
6022
|
+
}
|
|
5278
6023
|
}
|
|
5279
6024
|
async function findNextTask(assignedTo) {
|
|
5280
6025
|
const client = getClient();
|
|
@@ -5484,6 +6229,15 @@ var init_embedder = __esm({
|
|
|
5484
6229
|
// src/lib/behaviors.ts
|
|
5485
6230
|
import crypto5 from "crypto";
|
|
5486
6231
|
async function storeBehavior(opts) {
|
|
6232
|
+
try {
|
|
6233
|
+
const { loadEmployeesSync: loadEmployeesSync2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
|
|
6234
|
+
const roster = loadEmployeesSync2();
|
|
6235
|
+
if (roster.length > 0 && !roster.some((e) => e.name === opts.agentId)) {
|
|
6236
|
+
throw new Error(`Agent "${opts.agentId}" not found in roster. Cannot store behavior for unregistered agent.`);
|
|
6237
|
+
}
|
|
6238
|
+
} catch (e) {
|
|
6239
|
+
if (e instanceof Error && e.message.includes("not found in roster")) throw e;
|
|
6240
|
+
}
|
|
5487
6241
|
const client = getClient();
|
|
5488
6242
|
const id = crypto5.randomUUID();
|
|
5489
6243
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -5494,10 +6248,18 @@ async function storeBehavior(opts) {
|
|
|
5494
6248
|
vector = new Float32Array(vec);
|
|
5495
6249
|
} catch {
|
|
5496
6250
|
}
|
|
6251
|
+
let createdByDevice = null;
|
|
6252
|
+
try {
|
|
6253
|
+
const { loadDeviceId: loadDeviceId2 } = await Promise.resolve().then(() => (init_license(), license_exports));
|
|
6254
|
+
createdByDevice = loadDeviceId2() ?? null;
|
|
6255
|
+
} catch {
|
|
6256
|
+
}
|
|
6257
|
+
const createdByAgent = process.env.AGENT_ID ?? null;
|
|
6258
|
+
const sourceSessionId = process.env.CLAUDE_SESSION_ID ?? process.env.SESSION_ID ?? null;
|
|
5497
6259
|
await client.execute({
|
|
5498
|
-
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at, vector)
|
|
5499
|
-
VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?)`,
|
|
5500
|
-
args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now, vector ? vector.buffer : null]
|
|
6260
|
+
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)
|
|
6261
|
+
VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?)`,
|
|
6262
|
+
args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now, vector ? vector.buffer : null, createdByAgent, createdByDevice, sourceSessionId]
|
|
5501
6263
|
});
|
|
5502
6264
|
return id;
|
|
5503
6265
|
}
|
|
@@ -5929,6 +6691,12 @@ async function updateTask(input) {
|
|
|
5929
6691
|
}
|
|
5930
6692
|
}
|
|
5931
6693
|
}
|
|
6694
|
+
if (input.status === "cancelled") {
|
|
6695
|
+
try {
|
|
6696
|
+
await cascadeUnblock(taskId, input.baseDir, now);
|
|
6697
|
+
} catch {
|
|
6698
|
+
}
|
|
6699
|
+
}
|
|
5932
6700
|
if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
5933
6701
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
5934
6702
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
@@ -6460,11 +7228,12 @@ function getDispatchedBy(sessionKey) {
|
|
|
6460
7228
|
}
|
|
6461
7229
|
}
|
|
6462
7230
|
function resolveExeSession() {
|
|
7231
|
+
if (process.env.EXE_SESSION_NAME) {
|
|
7232
|
+
const fromEnv = extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
|
|
7233
|
+
if (fromEnv) return fromEnv;
|
|
7234
|
+
}
|
|
6463
7235
|
const mySession = getMySession();
|
|
6464
7236
|
if (!mySession) {
|
|
6465
|
-
if (process.env.EXE_SESSION_NAME) {
|
|
6466
|
-
return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
|
|
6467
|
-
}
|
|
6468
7237
|
return null;
|
|
6469
7238
|
}
|
|
6470
7239
|
const fromSessionName = extractRootExe(mySession);
|
|
@@ -6479,6 +7248,10 @@ function resolveExeSession() {
|
|
|
6479
7248
|
`[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
|
|
6480
7249
|
`
|
|
6481
7250
|
);
|
|
7251
|
+
try {
|
|
7252
|
+
registerParentExe(key, fromSessionName);
|
|
7253
|
+
} catch {
|
|
7254
|
+
}
|
|
6482
7255
|
candidate = fromSessionName;
|
|
6483
7256
|
} else {
|
|
6484
7257
|
candidate = fromCache;
|
|
@@ -8206,11 +8979,17 @@ var init_platform_procedures = __esm({
|
|
|
8206
8979
|
content: "Founder -> coordinator (the executive agent, internally routed as 'COO') -> CTO/CMO. CTO -> engineers. CMO -> content production. Never skip levels: the coordinator does not bypass managers for specialist work. Specialists report to their manager. If you need cross-team info, use ask_team_memory \u2014 don't read other agents' task folders. Each level owns dispatch downward and review upward."
|
|
8207
8980
|
},
|
|
8208
8981
|
{
|
|
8209
|
-
title: "
|
|
8982
|
+
title: "Orchestration phase guidance \u2014 recommend, never trap",
|
|
8210
8983
|
domain: "workflow",
|
|
8211
8984
|
priority: "p1",
|
|
8212
8985
|
content: "New customers start best in Phase 1: founder \u2194 coordinator/Chief of Staff, building company context. Suggest Phase 2 executives when domain work repeats; suggest Phase 3 parallel execution only when review/permission gates are ready. This is guidance, not a blocker: users may jump phases anytime. Never overwrite their phase, role titles, identities, or custom org design."
|
|
8213
8986
|
},
|
|
8987
|
+
{
|
|
8988
|
+
title: "Routing slot vs display title \u2014 internal 'coo' is plumbing, not your name",
|
|
8989
|
+
domain: "identity",
|
|
8990
|
+
priority: "p0",
|
|
8991
|
+
content: "These procedures reference 'COO' as a shorthand for the coordinator role. This is an INTERNAL routing slot used by exe-os code (chain-of-command checks, dispatch logic, session detection). It is NOT your display title. Your actual title comes from your identity file's `title:` field \u2014 that is what you use externally: introductions, sign-offs, team comms, and any user-facing text. If your identity says `title: AI Chief of Staff`, you are the AI Chief of Staff. The routing slot stays `role: coo` for code compatibility \u2014 never rename it, but also never introduce yourself as 'COO' unless your identity file explicitly says so. The founder chose your title; respect it."
|
|
8992
|
+
},
|
|
8214
8993
|
{
|
|
8215
8994
|
title: "Single dispatch path \u2014 create_task only",
|
|
8216
8995
|
domain: "workflow",
|
|
@@ -8244,6 +9023,12 @@ var init_platform_procedures = __esm({
|
|
|
8244
9023
|
priority: "p0",
|
|
8245
9024
|
content: "NEVER: (1) Access the database directly \u2014 it's SQLCipher encrypted, always fails. Use MCP tools only. (2) Manually spawn tmux sessions \u2014 create_task handles it. (3) Run git checkout main \u2014 agents work in worktrees. (4) Modify another agent's in-progress task. (5) Push to remote \u2014 the COO reviews and pushes. (6) Skip update_task(done) \u2014 it's the ONLY way your work gets reviewed. (7) Run git init."
|
|
8246
9025
|
},
|
|
9026
|
+
{
|
|
9027
|
+
title: "Destructive operations \u2014 mandatory reviewer gate",
|
|
9028
|
+
domain: "security",
|
|
9029
|
+
priority: "p0",
|
|
9030
|
+
content: "Before ANY destructive operation (delete, remove, overwrite, drop, reset, force-push, truncate), you MUST: (1) Have your full task spec accessible \u2014 if you cannot read it, STOP and report to your reviewer. Never improvise destructive actions. (2) Confirm with your reviewer (assigned_by or COO) before executing. (3) If the task spec explicitly authorizes the operation, proceed \u2014 but log it. Violation = immediate task failure. This applies to ALL agents regardless of role."
|
|
9031
|
+
},
|
|
8247
9032
|
{
|
|
8248
9033
|
title: "Customer patch triage \u2014 upstream bug vs customization",
|
|
8249
9034
|
domain: "support",
|
|
@@ -8395,7 +9180,7 @@ var init_platform_procedures = __esm({
|
|
|
8395
9180
|
title: "MCP tool dispatch \u2014 all tools use action parameter",
|
|
8396
9181
|
domain: "tool-use",
|
|
8397
9182
|
priority: "p0",
|
|
8398
|
-
content: 'exe-os MCP tools
|
|
9183
|
+
content: 'exe-os MCP tools use consolidated action-based dispatch by default (19 tools). Call domain tools with an action parameter: memory(action="recall"), task(action="create"), config(action="list_employees"), etc. Legacy mode (108 separate tools like recall_my_memory, create_task) is still available via EXE_MCP_TOOL_SURFACE=legacy but will be removed in a future version. If you see specific tool names, call them directly \u2014 both surfaces are identical. Consolidated is the default and recommended surface.'
|
|
8399
9184
|
},
|
|
8400
9185
|
{
|
|
8401
9186
|
title: "MCP tools \u2014 memory, decision, and search",
|
|
@@ -8529,10 +9314,24 @@ function stableId(memoryId, type, content) {
|
|
|
8529
9314
|
return createHash2("sha256").update(`${memoryId}:${type}:${content}`).digest("hex").slice(0, 32);
|
|
8530
9315
|
}
|
|
8531
9316
|
function cleanText(text) {
|
|
8532
|
-
|
|
9317
|
+
let cleaned = text.replace(
|
|
9318
|
+
/```(\w*)\n(.*?)(?:\n[\s\S]*?)```/g,
|
|
9319
|
+
(_m, lang, firstLine) => `[code${lang ? `:${lang}` : ""}] ${firstLine.trim()}`
|
|
9320
|
+
);
|
|
9321
|
+
cleaned = cleaned.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
|
9322
|
+
return cleaned;
|
|
8533
9323
|
}
|
|
8534
|
-
function
|
|
8535
|
-
|
|
9324
|
+
function splitSegments(text) {
|
|
9325
|
+
const cleaned = cleanText(text);
|
|
9326
|
+
const segments = cleaned.split(/(?<=[.!?:;])\s+|\n{2,}|(?<=\))\s+(?=[A-Z])|\s*[|│]\s*/).map((s) => s.trim()).filter((s) => s.length >= MIN_SEGMENT_CHARS && s.length <= MAX_SEGMENT_CHARS);
|
|
9327
|
+
if (segments.length === 0 && cleaned.length >= MIN_SEGMENT_CHARS) {
|
|
9328
|
+
const lines = cleaned.split(/\n+/).map((l) => l.trim()).filter((l) => l.length >= MIN_SEGMENT_CHARS && l.length <= MAX_SEGMENT_CHARS);
|
|
9329
|
+
if (lines.length > 0) return lines;
|
|
9330
|
+
if (cleaned.length >= MIN_SEGMENT_CHARS) {
|
|
9331
|
+
return [cleaned.slice(0, MAX_SEGMENT_CHARS)];
|
|
9332
|
+
}
|
|
9333
|
+
}
|
|
9334
|
+
return segments;
|
|
8536
9335
|
}
|
|
8537
9336
|
function inferCardType(sentence, toolName) {
|
|
8538
9337
|
const lower = sentence.toLowerCase();
|
|
@@ -8564,12 +9363,12 @@ function predicateFor(type) {
|
|
|
8564
9363
|
}
|
|
8565
9364
|
}
|
|
8566
9365
|
function extractMemoryCards(row) {
|
|
8567
|
-
const
|
|
9366
|
+
const segments = splitSegments(row.raw_text);
|
|
8568
9367
|
const cards = [];
|
|
8569
|
-
for (const sentence of
|
|
9368
|
+
for (const sentence of segments) {
|
|
8570
9369
|
const type = inferCardType(sentence, row.tool_name);
|
|
8571
9370
|
const subject = extractSubject(sentence, row.agent_id);
|
|
8572
|
-
const content = sentence.length >
|
|
9371
|
+
const content = sentence.length > MAX_SEGMENT_CHARS ? `${sentence.slice(0, MAX_SEGMENT_CHARS - 1)}\u2026` : sentence;
|
|
8573
9372
|
cards.push({
|
|
8574
9373
|
id: stableId(row.id, type, content),
|
|
8575
9374
|
memory_id: row.id,
|
|
@@ -8665,13 +9464,14 @@ Source memory: ${String(row.source_ref ?? row.memory_id)}`,
|
|
|
8665
9464
|
last_accessed: String(row.timestamp)
|
|
8666
9465
|
}));
|
|
8667
9466
|
}
|
|
8668
|
-
var MAX_CARDS_PER_MEMORY,
|
|
9467
|
+
var MAX_CARDS_PER_MEMORY, MAX_SEGMENT_CHARS, MIN_SEGMENT_CHARS;
|
|
8669
9468
|
var init_memory_cards = __esm({
|
|
8670
9469
|
"src/lib/memory-cards.ts"() {
|
|
8671
9470
|
"use strict";
|
|
8672
9471
|
init_database();
|
|
8673
|
-
MAX_CARDS_PER_MEMORY =
|
|
8674
|
-
|
|
9472
|
+
MAX_CARDS_PER_MEMORY = 8;
|
|
9473
|
+
MAX_SEGMENT_CHARS = 500;
|
|
9474
|
+
MIN_SEGMENT_CHARS = 20;
|
|
8675
9475
|
}
|
|
8676
9476
|
});
|
|
8677
9477
|
|