@askexenow/exe-os 0.9.110 → 0.9.112
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/dist/bin/agentic-ontology-backfill.js +8 -1
- package/dist/bin/agentic-reflection-backfill.js +8 -1
- package/dist/bin/agentic-semantic-label.js +8 -1
- package/dist/bin/backfill-conversations.js +8 -1
- package/dist/bin/backfill-responses.js +8 -1
- package/dist/bin/backfill-vectors.js +8 -1
- package/dist/bin/bulk-sync-postgres.js +8 -1
- package/dist/bin/cleanup-stale-review-tasks.js +8 -1
- package/dist/bin/cli.js +19 -4
- package/dist/bin/exe-agent.js +1 -1
- package/dist/bin/exe-assign.js +8 -1
- package/dist/bin/exe-boot.js +19 -4
- package/dist/bin/exe-call.js +1 -1
- package/dist/bin/exe-cloud.js +8 -1
- package/dist/bin/exe-dispatch.js +460 -5
- package/dist/bin/exe-doctor.js +8 -1
- package/dist/bin/exe-export-behaviors.js +13 -3
- package/dist/bin/exe-forget.js +8 -1
- package/dist/bin/exe-gateway.js +19 -4
- package/dist/bin/exe-heartbeat.js +8 -1
- package/dist/bin/exe-kill.js +8 -1
- package/dist/bin/exe-launch-agent.js +13 -3
- package/dist/bin/exe-new-employee.js +1 -1
- package/dist/bin/exe-pending-messages.js +8 -1
- package/dist/bin/exe-pending-notifications.js +8 -1
- package/dist/bin/exe-pending-reviews.js +8 -1
- package/dist/bin/exe-rename.js +8 -1
- package/dist/bin/exe-review.js +8 -1
- package/dist/bin/exe-search.js +8 -1
- package/dist/bin/exe-session-cleanup.js +460 -5
- package/dist/bin/exe-start-codex.js +13 -3
- package/dist/bin/exe-start-opencode.js +13 -3
- package/dist/bin/exe-status.js +8 -1
- package/dist/bin/exe-team.js +8 -1
- package/dist/bin/git-sweep.js +460 -5
- package/dist/bin/graph-backfill.js +8 -1
- package/dist/bin/graph-export.js +8 -1
- package/dist/bin/intercom-check.js +460 -5
- package/dist/bin/pre-publish.js +1 -1
- package/dist/bin/scan-tasks.js +460 -5
- package/dist/bin/setup.js +8 -1
- package/dist/bin/shard-migrate.js +8 -1
- package/dist/gateway/index.js +460 -5
- package/dist/hooks/bug-report-worker.js +460 -5
- package/dist/hooks/codex-stop-task-finalizer.js +467 -5
- package/dist/hooks/commit-complete.js +460 -5
- package/dist/hooks/error-recall.js +8 -1
- package/dist/hooks/ingest.js +8 -1
- package/dist/hooks/instructions-loaded.js +8 -1
- package/dist/hooks/notification.js +8 -1
- package/dist/hooks/post-compact.js +8 -1
- package/dist/hooks/post-tool-combined.js +8 -1
- package/dist/hooks/pre-compact.js +460 -5
- package/dist/hooks/pre-tool-use.js +8 -1
- package/dist/hooks/prompt-submit.js +469 -8
- package/dist/hooks/session-end.js +460 -5
- package/dist/hooks/session-start.js +8 -1
- package/dist/hooks/stop.js +8 -1
- package/dist/hooks/subagent-stop.js +8 -1
- package/dist/hooks/summary-worker.js +8 -1
- package/dist/index.js +469 -8
- package/dist/lib/cloud-sync.js +7 -0
- package/dist/lib/database.js +7 -0
- package/dist/lib/db.js +7 -0
- package/dist/lib/device-registry.js +7 -0
- package/dist/lib/employee-templates.js +1 -1
- package/dist/lib/exe-daemon.js +216 -12
- package/dist/lib/hybrid-search.js +8 -1
- package/dist/lib/schedules.js +8 -1
- package/dist/lib/skill-learning.js +488 -7
- package/dist/lib/store.js +8 -1
- package/dist/lib/tasks.js +452 -4
- package/dist/lib/tmux-routing.js +452 -4
- package/dist/mcp/server.js +69 -11
- package/dist/mcp/tools/create-task.js +452 -4
- package/dist/mcp/tools/update-task.js +452 -4
- package/dist/runtime/index.js +469 -8
- package/dist/tui/App.js +19 -4
- package/package.json +1 -1
package/dist/bin/scan-tasks.js
CHANGED
|
@@ -2627,6 +2627,13 @@ async function ensureSchema() {
|
|
|
2627
2627
|
} catch (e) {
|
|
2628
2628
|
logCatchDebug("migration", e);
|
|
2629
2629
|
}
|
|
2630
|
+
for (const col of ["created_by_agent TEXT", "created_by_device TEXT", "source_session_id TEXT"]) {
|
|
2631
|
+
try {
|
|
2632
|
+
await client.execute({ sql: `ALTER TABLE behaviors ADD COLUMN ${col}`, args: [] });
|
|
2633
|
+
} catch (e) {
|
|
2634
|
+
logCatchDebug("migration", e);
|
|
2635
|
+
}
|
|
2636
|
+
}
|
|
2630
2637
|
try {
|
|
2631
2638
|
await client.execute({
|
|
2632
2639
|
sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
|
|
@@ -3894,6 +3901,23 @@ var init_database = __esm({
|
|
|
3894
3901
|
});
|
|
3895
3902
|
|
|
3896
3903
|
// src/lib/license.ts
|
|
3904
|
+
var license_exports = {};
|
|
3905
|
+
__export(license_exports, {
|
|
3906
|
+
LICENSE_PUBLIC_KEY_PEM: () => LICENSE_PUBLIC_KEY_PEM,
|
|
3907
|
+
PLAN_LIMITS: () => PLAN_LIMITS,
|
|
3908
|
+
assertVpsLicense: () => assertVpsLicense,
|
|
3909
|
+
checkLicense: () => checkLicense,
|
|
3910
|
+
getCachedLicense: () => getCachedLicense,
|
|
3911
|
+
isFeatureAllowed: () => isFeatureAllowed,
|
|
3912
|
+
loadDeviceId: () => loadDeviceId,
|
|
3913
|
+
loadLicense: () => loadLicense,
|
|
3914
|
+
mirrorLicenseKey: () => mirrorLicenseKey,
|
|
3915
|
+
readCachedLicenseToken: () => readCachedLicenseToken,
|
|
3916
|
+
saveLicense: () => saveLicense,
|
|
3917
|
+
startLicenseRevalidation: () => startLicenseRevalidation,
|
|
3918
|
+
stopLicenseRevalidation: () => stopLicenseRevalidation,
|
|
3919
|
+
validateLicense: () => validateLicense
|
|
3920
|
+
});
|
|
3897
3921
|
import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync10, mkdirSync as mkdirSync5 } from "fs";
|
|
3898
3922
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
3899
3923
|
import { createRequire as createRequire2 } from "module";
|
|
@@ -3901,7 +3925,411 @@ import { pathToFileURL as pathToFileURL2 } from "url";
|
|
|
3901
3925
|
import os7 from "os";
|
|
3902
3926
|
import path9 from "path";
|
|
3903
3927
|
import { jwtVerify, importSPKI } from "jose";
|
|
3904
|
-
|
|
3928
|
+
async function fetchRetry(url, init) {
|
|
3929
|
+
try {
|
|
3930
|
+
return await fetch(url, init);
|
|
3931
|
+
} catch {
|
|
3932
|
+
await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
|
|
3933
|
+
return fetch(url, { ...init, signal: AbortSignal.timeout(1e4) });
|
|
3934
|
+
}
|
|
3935
|
+
}
|
|
3936
|
+
function loadDeviceId() {
|
|
3937
|
+
const deviceJsonPath = path9.join(EXE_AI_DIR, "device.json");
|
|
3938
|
+
try {
|
|
3939
|
+
if (existsSync10(deviceJsonPath)) {
|
|
3940
|
+
const data = JSON.parse(readFileSync8(deviceJsonPath, "utf8"));
|
|
3941
|
+
if (data.deviceId) return data.deviceId;
|
|
3942
|
+
}
|
|
3943
|
+
} catch {
|
|
3944
|
+
}
|
|
3945
|
+
try {
|
|
3946
|
+
if (existsSync10(DEVICE_ID_PATH)) {
|
|
3947
|
+
const id2 = readFileSync8(DEVICE_ID_PATH, "utf8").trim();
|
|
3948
|
+
if (id2) return id2;
|
|
3949
|
+
}
|
|
3950
|
+
} catch {
|
|
3951
|
+
}
|
|
3952
|
+
const id = randomUUID2();
|
|
3953
|
+
mkdirSync5(EXE_AI_DIR, { recursive: true });
|
|
3954
|
+
writeFileSync6(DEVICE_ID_PATH, id, "utf8");
|
|
3955
|
+
return id;
|
|
3956
|
+
}
|
|
3957
|
+
function loadLicense() {
|
|
3958
|
+
try {
|
|
3959
|
+
if (!existsSync10(LICENSE_PATH)) return null;
|
|
3960
|
+
return readFileSync8(LICENSE_PATH, "utf8").trim();
|
|
3961
|
+
} catch {
|
|
3962
|
+
return null;
|
|
3963
|
+
}
|
|
3964
|
+
}
|
|
3965
|
+
function saveLicense(apiKey) {
|
|
3966
|
+
mkdirSync5(EXE_AI_DIR, { recursive: true });
|
|
3967
|
+
writeFileSync6(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
|
|
3968
|
+
}
|
|
3969
|
+
async function verifyLicenseJwt(token) {
|
|
3970
|
+
try {
|
|
3971
|
+
const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
|
|
3972
|
+
const { payload } = await jwtVerify(token, key, {
|
|
3973
|
+
algorithms: [LICENSE_JWT_ALG]
|
|
3974
|
+
});
|
|
3975
|
+
const plan = payload.plan ?? "free";
|
|
3976
|
+
const email = payload.sub ?? "";
|
|
3977
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
3978
|
+
return {
|
|
3979
|
+
valid: true,
|
|
3980
|
+
plan,
|
|
3981
|
+
email,
|
|
3982
|
+
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
3983
|
+
deviceLimit: limits.devices,
|
|
3984
|
+
employeeLimit: limits.employees,
|
|
3985
|
+
memoryLimit: limits.memories
|
|
3986
|
+
};
|
|
3987
|
+
} catch {
|
|
3988
|
+
return null;
|
|
3989
|
+
}
|
|
3990
|
+
}
|
|
3991
|
+
async function getCachedLicense() {
|
|
3992
|
+
try {
|
|
3993
|
+
if (!existsSync10(CACHE_PATH)) return null;
|
|
3994
|
+
const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
|
|
3995
|
+
if (!raw.token || typeof raw.token !== "string") return null;
|
|
3996
|
+
return await verifyLicenseJwt(raw.token);
|
|
3997
|
+
} catch {
|
|
3998
|
+
return null;
|
|
3999
|
+
}
|
|
4000
|
+
}
|
|
4001
|
+
function readCachedLicenseToken() {
|
|
4002
|
+
try {
|
|
4003
|
+
if (!existsSync10(CACHE_PATH)) return null;
|
|
4004
|
+
const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
|
|
4005
|
+
return typeof raw.token === "string" ? raw.token : null;
|
|
4006
|
+
} catch {
|
|
4007
|
+
return null;
|
|
4008
|
+
}
|
|
4009
|
+
}
|
|
4010
|
+
function getRawCachedPlan() {
|
|
4011
|
+
try {
|
|
4012
|
+
const token = readCachedLicenseToken();
|
|
4013
|
+
if (!token) return null;
|
|
4014
|
+
const parts = token.split(".");
|
|
4015
|
+
if (parts.length !== 3) return null;
|
|
4016
|
+
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
|
|
4017
|
+
const plan = payload.plan ?? "free";
|
|
4018
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
4019
|
+
process.stderr.write(
|
|
4020
|
+
`[license] WARN: using unverified cached plan (API unreachable, JWT expired). Plan: ${plan}
|
|
4021
|
+
`
|
|
4022
|
+
);
|
|
4023
|
+
return {
|
|
4024
|
+
valid: true,
|
|
4025
|
+
plan,
|
|
4026
|
+
email: payload.sub ?? "",
|
|
4027
|
+
expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
|
|
4028
|
+
deviceLimit: limits.devices,
|
|
4029
|
+
employeeLimit: limits.employees,
|
|
4030
|
+
memoryLimit: limits.memories
|
|
4031
|
+
};
|
|
4032
|
+
} catch {
|
|
4033
|
+
return null;
|
|
4034
|
+
}
|
|
4035
|
+
}
|
|
4036
|
+
function cacheResponse(token) {
|
|
4037
|
+
try {
|
|
4038
|
+
writeFileSync6(CACHE_PATH, JSON.stringify({ token }), "utf8");
|
|
4039
|
+
} catch {
|
|
4040
|
+
}
|
|
4041
|
+
}
|
|
4042
|
+
function loadPrismaForLicense() {
|
|
4043
|
+
if (_prismaFailed) return null;
|
|
4044
|
+
const dbUrl = process.env.DATABASE_URL;
|
|
4045
|
+
if (!dbUrl) {
|
|
4046
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path9.join(os7.homedir(), "exe-db");
|
|
4047
|
+
if (!existsSync10(path9.join(exeDbRoot, "package.json"))) {
|
|
4048
|
+
_prismaFailed = true;
|
|
4049
|
+
return null;
|
|
4050
|
+
}
|
|
4051
|
+
}
|
|
4052
|
+
if (!_prismaPromise) {
|
|
4053
|
+
_prismaPromise = (async () => {
|
|
4054
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
4055
|
+
if (explicitPath) {
|
|
4056
|
+
const mod2 = await import(pathToFileURL2(explicitPath).href);
|
|
4057
|
+
const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
|
|
4058
|
+
if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
|
|
4059
|
+
return new Ctor2();
|
|
4060
|
+
}
|
|
4061
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path9.join(os7.homedir(), "exe-db");
|
|
4062
|
+
const req = createRequire2(path9.join(exeDbRoot, "package.json"));
|
|
4063
|
+
const entry = req.resolve("@prisma/client");
|
|
4064
|
+
const mod = await import(pathToFileURL2(entry).href);
|
|
4065
|
+
const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
|
|
4066
|
+
if (!Ctor) throw new Error(`No PrismaClient in ${entry}`);
|
|
4067
|
+
return new Ctor();
|
|
4068
|
+
})().catch((err) => {
|
|
4069
|
+
_prismaFailed = true;
|
|
4070
|
+
_prismaPromise = null;
|
|
4071
|
+
throw err;
|
|
4072
|
+
});
|
|
4073
|
+
}
|
|
4074
|
+
return _prismaPromise;
|
|
4075
|
+
}
|
|
4076
|
+
async function validateViaPostgres(apiKey) {
|
|
4077
|
+
const loader = loadPrismaForLicense();
|
|
4078
|
+
if (!loader) return null;
|
|
4079
|
+
try {
|
|
4080
|
+
const prisma = await loader;
|
|
4081
|
+
const rows = await prisma.$queryRawUnsafe(
|
|
4082
|
+
`SELECT plan, email, status, device_limit, employee_limit, memory_limit, expires_at
|
|
4083
|
+
FROM billing.licenses WHERE key = $1 LIMIT 1`,
|
|
4084
|
+
apiKey
|
|
4085
|
+
);
|
|
4086
|
+
if (!rows || rows.length === 0) return null;
|
|
4087
|
+
const row = rows[0];
|
|
4088
|
+
if (row.status !== "active") return null;
|
|
4089
|
+
if (row.expires_at && new Date(row.expires_at) < /* @__PURE__ */ new Date()) return null;
|
|
4090
|
+
const plan = row.plan;
|
|
4091
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
4092
|
+
return {
|
|
4093
|
+
valid: true,
|
|
4094
|
+
plan,
|
|
4095
|
+
email: row.email,
|
|
4096
|
+
expiresAt: row.expires_at ? new Date(row.expires_at).toISOString() : null,
|
|
4097
|
+
deviceLimit: row.device_limit ?? limits.devices,
|
|
4098
|
+
employeeLimit: row.employee_limit ?? limits.employees,
|
|
4099
|
+
memoryLimit: row.memory_limit ?? limits.memories
|
|
4100
|
+
};
|
|
4101
|
+
} catch {
|
|
4102
|
+
return null;
|
|
4103
|
+
}
|
|
4104
|
+
}
|
|
4105
|
+
async function validateViaCFWorker(apiKey, deviceId) {
|
|
4106
|
+
try {
|
|
4107
|
+
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
4108
|
+
method: "POST",
|
|
4109
|
+
headers: { "Content-Type": "application/json" },
|
|
4110
|
+
body: JSON.stringify({ apiKey, deviceId }),
|
|
4111
|
+
signal: AbortSignal.timeout(1e4)
|
|
4112
|
+
});
|
|
4113
|
+
if (!res.ok) return null;
|
|
4114
|
+
const data = await res.json();
|
|
4115
|
+
if (data.error === "device_limit_exceeded") return null;
|
|
4116
|
+
if (!data.valid) return null;
|
|
4117
|
+
if (data.token) {
|
|
4118
|
+
cacheResponse(data.token);
|
|
4119
|
+
const verified = await verifyLicenseJwt(data.token);
|
|
4120
|
+
if (verified) return verified;
|
|
4121
|
+
}
|
|
4122
|
+
const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
|
|
4123
|
+
return {
|
|
4124
|
+
valid: data.valid,
|
|
4125
|
+
plan: data.plan,
|
|
4126
|
+
email: data.email,
|
|
4127
|
+
expiresAt: data.expiresAt,
|
|
4128
|
+
deviceLimit: limits.devices,
|
|
4129
|
+
employeeLimit: limits.employees,
|
|
4130
|
+
memoryLimit: limits.memories
|
|
4131
|
+
};
|
|
4132
|
+
} catch {
|
|
4133
|
+
return null;
|
|
4134
|
+
}
|
|
4135
|
+
}
|
|
4136
|
+
async function validateLicense(apiKey, deviceId) {
|
|
4137
|
+
const did = deviceId ?? loadDeviceId();
|
|
4138
|
+
const pgResult = await validateViaPostgres(apiKey);
|
|
4139
|
+
if (pgResult) {
|
|
4140
|
+
try {
|
|
4141
|
+
writeFileSync6(CACHE_PATH, JSON.stringify({ pgLicense: pgResult, ts: Date.now() }), "utf8");
|
|
4142
|
+
} catch {
|
|
4143
|
+
}
|
|
4144
|
+
return pgResult;
|
|
4145
|
+
}
|
|
4146
|
+
const cfResult = await validateViaCFWorker(apiKey, did);
|
|
4147
|
+
if (cfResult) return cfResult;
|
|
4148
|
+
const cached = await getCachedLicense();
|
|
4149
|
+
if (cached) return cached;
|
|
4150
|
+
try {
|
|
4151
|
+
if (existsSync10(CACHE_PATH)) {
|
|
4152
|
+
const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
|
|
4153
|
+
if (raw.pgLicense && raw.ts && Date.now() - raw.ts < 7 * 24 * 60 * 60 * 1e3) {
|
|
4154
|
+
return raw.pgLicense;
|
|
4155
|
+
}
|
|
4156
|
+
}
|
|
4157
|
+
} catch {
|
|
4158
|
+
}
|
|
4159
|
+
const rawFallback = getRawCachedPlan();
|
|
4160
|
+
if (rawFallback) return rawFallback;
|
|
4161
|
+
return { ...FREE_LICENSE, valid: false };
|
|
4162
|
+
}
|
|
4163
|
+
function getCacheAgeMs() {
|
|
4164
|
+
try {
|
|
4165
|
+
const { statSync: statSync5 } = __require("fs");
|
|
4166
|
+
const s = statSync5(CACHE_PATH);
|
|
4167
|
+
return Date.now() - s.mtimeMs;
|
|
4168
|
+
} catch {
|
|
4169
|
+
return Infinity;
|
|
4170
|
+
}
|
|
4171
|
+
}
|
|
4172
|
+
async function checkLicense() {
|
|
4173
|
+
let key = loadLicense();
|
|
4174
|
+
if (!key) {
|
|
4175
|
+
try {
|
|
4176
|
+
const configPath = path9.join(EXE_AI_DIR, "config.json");
|
|
4177
|
+
if (existsSync10(configPath)) {
|
|
4178
|
+
const raw = JSON.parse(readFileSync8(configPath, "utf8"));
|
|
4179
|
+
const cloud = raw.cloud;
|
|
4180
|
+
if (cloud?.apiKey) {
|
|
4181
|
+
key = cloud.apiKey;
|
|
4182
|
+
saveLicense(key);
|
|
4183
|
+
}
|
|
4184
|
+
}
|
|
4185
|
+
} catch {
|
|
4186
|
+
}
|
|
4187
|
+
}
|
|
4188
|
+
if (!key) return FREE_LICENSE;
|
|
4189
|
+
const cached = await getCachedLicense();
|
|
4190
|
+
if (cached && getCacheAgeMs() < CACHE_MAX_AGE_MS) return cached;
|
|
4191
|
+
const deviceId = loadDeviceId();
|
|
4192
|
+
return validateLicense(key, deviceId);
|
|
4193
|
+
}
|
|
4194
|
+
function isFeatureAllowed(license, feature) {
|
|
4195
|
+
switch (feature) {
|
|
4196
|
+
case "cloud_sync":
|
|
4197
|
+
case "external_agents":
|
|
4198
|
+
case "wiki":
|
|
4199
|
+
return license.plan !== "free";
|
|
4200
|
+
case "unlimited_employees":
|
|
4201
|
+
return license.plan === "team" || license.plan === "agency" || license.plan === "enterprise";
|
|
4202
|
+
}
|
|
4203
|
+
}
|
|
4204
|
+
function mirrorLicenseKey(apiKey) {
|
|
4205
|
+
const trimmed = apiKey.trim();
|
|
4206
|
+
if (!trimmed) return;
|
|
4207
|
+
saveLicense(trimmed);
|
|
4208
|
+
}
|
|
4209
|
+
async function assertVpsLicense(opts) {
|
|
4210
|
+
const env = opts?.env ?? process.env;
|
|
4211
|
+
const inProduction = env.NODE_ENV === "production";
|
|
4212
|
+
if (!opts?.force && !inProduction) {
|
|
4213
|
+
return { ...FREE_LICENSE, plan: "free" };
|
|
4214
|
+
}
|
|
4215
|
+
const envKey = env.EXE_LICENSE_KEY?.trim();
|
|
4216
|
+
if (envKey) {
|
|
4217
|
+
saveLicense(envKey);
|
|
4218
|
+
}
|
|
4219
|
+
const apiKey = envKey ?? loadLicense();
|
|
4220
|
+
if (!apiKey) {
|
|
4221
|
+
throw new Error(
|
|
4222
|
+
"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."
|
|
4223
|
+
);
|
|
4224
|
+
}
|
|
4225
|
+
const deviceId = loadDeviceId();
|
|
4226
|
+
let backendResponse = null;
|
|
4227
|
+
let explicitRejection = false;
|
|
4228
|
+
let transientFailure = false;
|
|
4229
|
+
try {
|
|
4230
|
+
const res = await fetchRetry(`${API_BASE}/auth/activate`, {
|
|
4231
|
+
method: "POST",
|
|
4232
|
+
headers: { "Content-Type": "application/json" },
|
|
4233
|
+
body: JSON.stringify({ apiKey, deviceId }),
|
|
4234
|
+
signal: AbortSignal.timeout(1e4)
|
|
4235
|
+
});
|
|
4236
|
+
if (res.ok) {
|
|
4237
|
+
backendResponse = await res.json();
|
|
4238
|
+
if (!backendResponse.valid) explicitRejection = true;
|
|
4239
|
+
} else if (res.status === 401 || res.status === 403) {
|
|
4240
|
+
explicitRejection = true;
|
|
4241
|
+
} else {
|
|
4242
|
+
transientFailure = true;
|
|
4243
|
+
}
|
|
4244
|
+
} catch {
|
|
4245
|
+
transientFailure = true;
|
|
4246
|
+
}
|
|
4247
|
+
if (backendResponse?.valid) {
|
|
4248
|
+
if (backendResponse.token) {
|
|
4249
|
+
cacheResponse(backendResponse.token);
|
|
4250
|
+
const verified = await verifyLicenseJwt(backendResponse.token);
|
|
4251
|
+
if (verified) return verified;
|
|
4252
|
+
}
|
|
4253
|
+
const limits = PLAN_LIMITS[backendResponse.plan] ?? PLAN_LIMITS.free;
|
|
4254
|
+
return {
|
|
4255
|
+
valid: true,
|
|
4256
|
+
plan: backendResponse.plan,
|
|
4257
|
+
email: backendResponse.email,
|
|
4258
|
+
expiresAt: backendResponse.expiresAt,
|
|
4259
|
+
deviceLimit: limits.devices,
|
|
4260
|
+
employeeLimit: limits.employees,
|
|
4261
|
+
memoryLimit: limits.memories
|
|
4262
|
+
};
|
|
4263
|
+
}
|
|
4264
|
+
if (explicitRejection) {
|
|
4265
|
+
throw new Error(
|
|
4266
|
+
`License invalid or expired. Renew at https://askexe.com, then restart. Backend rejected key ending in ...${apiKey.slice(-4)}.`
|
|
4267
|
+
);
|
|
4268
|
+
}
|
|
4269
|
+
if (!transientFailure) {
|
|
4270
|
+
throw new Error(
|
|
4271
|
+
"License validation failed: unknown backend state. Restore network connectivity to https://askexe.com/cloud and retry."
|
|
4272
|
+
);
|
|
4273
|
+
}
|
|
4274
|
+
const fresh = await getCachedLicense();
|
|
4275
|
+
if (fresh && fresh.valid) return fresh;
|
|
4276
|
+
const graceDays = opts?.offlineGraceDays ?? 7;
|
|
4277
|
+
const graceMs = graceDays * 24 * 60 * 60 * 1e3;
|
|
4278
|
+
try {
|
|
4279
|
+
const token = readCachedLicenseToken();
|
|
4280
|
+
if (token) {
|
|
4281
|
+
const payloadB64 = token.split(".")[1];
|
|
4282
|
+
if (payloadB64) {
|
|
4283
|
+
const payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString());
|
|
4284
|
+
const expMs = (payload.exp ?? 0) * 1e3;
|
|
4285
|
+
if (Date.now() < expMs + graceMs) {
|
|
4286
|
+
const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
|
|
4287
|
+
const { payload: verified } = await jwtVerify(token, key, {
|
|
4288
|
+
algorithms: [LICENSE_JWT_ALG],
|
|
4289
|
+
clockTolerance: graceDays * 24 * 60 * 60
|
|
4290
|
+
});
|
|
4291
|
+
const plan = verified.plan ?? "free";
|
|
4292
|
+
const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
|
|
4293
|
+
return {
|
|
4294
|
+
valid: true,
|
|
4295
|
+
plan,
|
|
4296
|
+
email: verified.sub ?? "",
|
|
4297
|
+
expiresAt: verified.exp ? new Date(verified.exp * 1e3).toISOString() : null,
|
|
4298
|
+
deviceLimit: limits.devices,
|
|
4299
|
+
employeeLimit: limits.employees,
|
|
4300
|
+
memoryLimit: limits.memories
|
|
4301
|
+
};
|
|
4302
|
+
}
|
|
4303
|
+
}
|
|
4304
|
+
}
|
|
4305
|
+
} catch {
|
|
4306
|
+
}
|
|
4307
|
+
throw new Error(
|
|
4308
|
+
`License validation unreachable for more than ${graceDays} days. Restore network connectivity to https://askexe.com/cloud and retry. This VPS image refuses to boot after the offline grace window.`
|
|
4309
|
+
);
|
|
4310
|
+
}
|
|
4311
|
+
function startLicenseRevalidation(intervalMs = 36e5) {
|
|
4312
|
+
if (_revalTimer) return;
|
|
4313
|
+
_revalTimer = setInterval(async () => {
|
|
4314
|
+
try {
|
|
4315
|
+
const license = await checkLicense();
|
|
4316
|
+
if (!license.valid) {
|
|
4317
|
+
process.stderr.write("[exe-os] License expired or invalid \u2014 features may be restricted\n");
|
|
4318
|
+
}
|
|
4319
|
+
} catch {
|
|
4320
|
+
}
|
|
4321
|
+
}, intervalMs);
|
|
4322
|
+
if (_revalTimer && typeof _revalTimer === "object" && "unref" in _revalTimer) {
|
|
4323
|
+
_revalTimer.unref();
|
|
4324
|
+
}
|
|
4325
|
+
}
|
|
4326
|
+
function stopLicenseRevalidation() {
|
|
4327
|
+
if (_revalTimer) {
|
|
4328
|
+
clearInterval(_revalTimer);
|
|
4329
|
+
_revalTimer = null;
|
|
4330
|
+
}
|
|
4331
|
+
}
|
|
4332
|
+
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
4333
|
var init_license = __esm({
|
|
3906
4334
|
"src/lib/license.ts"() {
|
|
3907
4335
|
"use strict";
|
|
@@ -3910,6 +4338,12 @@ var init_license = __esm({
|
|
|
3910
4338
|
CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
|
|
3911
4339
|
DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
|
|
3912
4340
|
API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com/cloud";
|
|
4341
|
+
RETRY_DELAY_MS = 500;
|
|
4342
|
+
LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
4343
|
+
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
|
|
4344
|
+
4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
|
|
4345
|
+
-----END PUBLIC KEY-----`;
|
|
4346
|
+
LICENSE_JWT_ALG = "ES256";
|
|
3913
4347
|
PLAN_LIMITS = {
|
|
3914
4348
|
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
3915
4349
|
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
@@ -3917,6 +4351,19 @@ var init_license = __esm({
|
|
|
3917
4351
|
agency: { devices: 50, employees: 100, memories: 1e7 },
|
|
3918
4352
|
enterprise: { devices: -1, employees: -1, memories: -1 }
|
|
3919
4353
|
};
|
|
4354
|
+
FREE_LICENSE = {
|
|
4355
|
+
valid: true,
|
|
4356
|
+
plan: "free",
|
|
4357
|
+
email: "",
|
|
4358
|
+
expiresAt: null,
|
|
4359
|
+
deviceLimit: 1,
|
|
4360
|
+
employeeLimit: 1,
|
|
4361
|
+
memoryLimit: 5e3
|
|
4362
|
+
};
|
|
4363
|
+
_prismaPromise = null;
|
|
4364
|
+
_prismaFailed = false;
|
|
4365
|
+
CACHE_MAX_AGE_MS = 36e5;
|
|
4366
|
+
_revalTimer = null;
|
|
3920
4367
|
}
|
|
3921
4368
|
});
|
|
3922
4369
|
|
|
@@ -5494,10 +5941,18 @@ async function storeBehavior(opts) {
|
|
|
5494
5941
|
vector = new Float32Array(vec);
|
|
5495
5942
|
} catch {
|
|
5496
5943
|
}
|
|
5944
|
+
let createdByDevice = null;
|
|
5945
|
+
try {
|
|
5946
|
+
const { loadDeviceId: loadDeviceId2 } = await Promise.resolve().then(() => (init_license(), license_exports));
|
|
5947
|
+
createdByDevice = loadDeviceId2() ?? null;
|
|
5948
|
+
} catch {
|
|
5949
|
+
}
|
|
5950
|
+
const createdByAgent = process.env.AGENT_ID ?? null;
|
|
5951
|
+
const sourceSessionId = process.env.CLAUDE_SESSION_ID ?? process.env.SESSION_ID ?? null;
|
|
5497
5952
|
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]
|
|
5953
|
+
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)
|
|
5954
|
+
VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?)`,
|
|
5955
|
+
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
5956
|
});
|
|
5502
5957
|
return id;
|
|
5503
5958
|
}
|
|
@@ -8395,7 +8850,7 @@ var init_platform_procedures = __esm({
|
|
|
8395
8850
|
title: "MCP tool dispatch \u2014 all tools use action parameter",
|
|
8396
8851
|
domain: "tool-use",
|
|
8397
8852
|
priority: "p0",
|
|
8398
|
-
content: 'exe-os MCP tools
|
|
8853
|
+
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
8854
|
},
|
|
8400
8855
|
{
|
|
8401
8856
|
title: "MCP tools \u2014 memory, decision, and search",
|
package/dist/bin/setup.js
CHANGED
|
@@ -3450,6 +3450,13 @@ async function ensureSchema() {
|
|
|
3450
3450
|
} catch (e) {
|
|
3451
3451
|
logCatchDebug("migration", e);
|
|
3452
3452
|
}
|
|
3453
|
+
for (const col of ["created_by_agent TEXT", "created_by_device TEXT", "source_session_id TEXT"]) {
|
|
3454
|
+
try {
|
|
3455
|
+
await client.execute({ sql: `ALTER TABLE behaviors ADD COLUMN ${col}`, args: [] });
|
|
3456
|
+
} catch (e) {
|
|
3457
|
+
logCatchDebug("migration", e);
|
|
3458
|
+
}
|
|
3459
|
+
}
|
|
3453
3460
|
try {
|
|
3454
3461
|
await client.execute({
|
|
3455
3462
|
sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
|
|
@@ -6934,7 +6941,7 @@ var init_platform_procedures = __esm({
|
|
|
6934
6941
|
title: "MCP tool dispatch \u2014 all tools use action parameter",
|
|
6935
6942
|
domain: "tool-use",
|
|
6936
6943
|
priority: "p0",
|
|
6937
|
-
content: 'exe-os MCP tools
|
|
6944
|
+
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.'
|
|
6938
6945
|
},
|
|
6939
6946
|
{
|
|
6940
6947
|
title: "MCP tools \u2014 memory, decision, and search",
|
|
@@ -1811,6 +1811,13 @@ async function ensureSchema() {
|
|
|
1811
1811
|
} catch (e) {
|
|
1812
1812
|
logCatchDebug("migration", e);
|
|
1813
1813
|
}
|
|
1814
|
+
for (const col of ["created_by_agent TEXT", "created_by_device TEXT", "source_session_id TEXT"]) {
|
|
1815
|
+
try {
|
|
1816
|
+
await client.execute({ sql: `ALTER TABLE behaviors ADD COLUMN ${col}`, args: [] });
|
|
1817
|
+
} catch (e) {
|
|
1818
|
+
logCatchDebug("migration", e);
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1814
1821
|
try {
|
|
1815
1822
|
await client.execute({
|
|
1816
1823
|
sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
|
|
@@ -3782,7 +3789,7 @@ var init_platform_procedures = __esm({
|
|
|
3782
3789
|
title: "MCP tool dispatch \u2014 all tools use action parameter",
|
|
3783
3790
|
domain: "tool-use",
|
|
3784
3791
|
priority: "p0",
|
|
3785
|
-
content: 'exe-os MCP tools
|
|
3792
|
+
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.'
|
|
3786
3793
|
},
|
|
3787
3794
|
{
|
|
3788
3795
|
title: "MCP tools \u2014 memory, decision, and search",
|