@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
|
@@ -375,6 +375,7 @@ __export(agent_config_exports, {
|
|
|
375
375
|
getAgentRuntime: () => getAgentRuntime,
|
|
376
376
|
loadAgentConfig: () => loadAgentConfig,
|
|
377
377
|
saveAgentConfig: () => saveAgentConfig,
|
|
378
|
+
setAgentMcps: () => setAgentMcps,
|
|
378
379
|
setAgentRuntime: () => setAgentRuntime
|
|
379
380
|
});
|
|
380
381
|
import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
|
|
@@ -401,7 +402,7 @@ function getAgentRuntime(agentId) {
|
|
|
401
402
|
if (orgDefault) return orgDefault;
|
|
402
403
|
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
403
404
|
}
|
|
404
|
-
function setAgentRuntime(agentId, runtime, model, reasoning_effort) {
|
|
405
|
+
function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
|
|
405
406
|
const knownModels = KNOWN_RUNTIMES[runtime];
|
|
406
407
|
if (!knownModels) {
|
|
407
408
|
return {
|
|
@@ -416,12 +417,26 @@ function setAgentRuntime(agentId, runtime, model, reasoning_effort) {
|
|
|
416
417
|
};
|
|
417
418
|
}
|
|
418
419
|
const config = loadAgentConfig();
|
|
420
|
+
const existing = config[agentId];
|
|
419
421
|
const entry = { runtime, model };
|
|
420
422
|
if (reasoning_effort) entry.reasoning_effort = reasoning_effort;
|
|
423
|
+
if (mcps !== void 0) {
|
|
424
|
+
entry.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
|
|
425
|
+
} else if (existing?.mcps) {
|
|
426
|
+
entry.mcps = existing.mcps;
|
|
427
|
+
}
|
|
421
428
|
config[agentId] = entry;
|
|
422
429
|
saveAgentConfig(config);
|
|
423
430
|
return { ok: true };
|
|
424
431
|
}
|
|
432
|
+
function setAgentMcps(agentId, mcps) {
|
|
433
|
+
const config = loadAgentConfig();
|
|
434
|
+
const existing = config[agentId] ?? getAgentRuntime(agentId);
|
|
435
|
+
existing.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
|
|
436
|
+
config[agentId] = existing;
|
|
437
|
+
saveAgentConfig(config);
|
|
438
|
+
return { ok: true };
|
|
439
|
+
}
|
|
425
440
|
function clearAgentRuntime(agentId) {
|
|
426
441
|
const config = loadAgentConfig();
|
|
427
442
|
delete config[agentId];
|
|
@@ -1212,6 +1227,23 @@ var init_intercom_queue = __esm({
|
|
|
1212
1227
|
});
|
|
1213
1228
|
|
|
1214
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
|
+
});
|
|
1215
1247
|
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync8, mkdirSync as mkdirSync5 } from "fs";
|
|
1216
1248
|
import { randomUUID } from "crypto";
|
|
1217
1249
|
import { createRequire as createRequire2 } from "module";
|
|
@@ -1219,7 +1251,411 @@ import { pathToFileURL as pathToFileURL2 } from "url";
|
|
|
1219
1251
|
import os6 from "os";
|
|
1220
1252
|
import path7 from "path";
|
|
1221
1253
|
import { jwtVerify, importSPKI } from "jose";
|
|
1222
|
-
|
|
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;
|
|
1223
1659
|
var init_license = __esm({
|
|
1224
1660
|
"src/lib/license.ts"() {
|
|
1225
1661
|
"use strict";
|
|
@@ -1227,7 +1663,13 @@ var init_license = __esm({
|
|
|
1227
1663
|
LICENSE_PATH = path7.join(EXE_AI_DIR, "license.key");
|
|
1228
1664
|
CACHE_PATH = path7.join(EXE_AI_DIR, "license-cache.json");
|
|
1229
1665
|
DEVICE_ID_PATH = path7.join(EXE_AI_DIR, "device-id");
|
|
1230
|
-
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";
|
|
1231
1673
|
PLAN_LIMITS = {
|
|
1232
1674
|
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
1233
1675
|
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
@@ -1235,6 +1677,19 @@ var init_license = __esm({
|
|
|
1235
1677
|
agency: { devices: 50, employees: 100, memories: 1e7 },
|
|
1236
1678
|
enterprise: { devices: -1, employees: -1, memories: -1 }
|
|
1237
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;
|
|
1238
1693
|
}
|
|
1239
1694
|
});
|
|
1240
1695
|
|
|
@@ -2234,11 +2689,12 @@ function getDispatchedBy(sessionKey) {
|
|
|
2234
2689
|
}
|
|
2235
2690
|
}
|
|
2236
2691
|
function resolveExeSession() {
|
|
2692
|
+
if (process.env.EXE_SESSION_NAME) {
|
|
2693
|
+
const fromEnv = extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
|
|
2694
|
+
if (fromEnv) return fromEnv;
|
|
2695
|
+
}
|
|
2237
2696
|
const mySession = getMySession();
|
|
2238
2697
|
if (!mySession) {
|
|
2239
|
-
if (process.env.EXE_SESSION_NAME) {
|
|
2240
|
-
return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
|
|
2241
|
-
}
|
|
2242
2698
|
return null;
|
|
2243
2699
|
}
|
|
2244
2700
|
const fromSessionName = extractRootExe(mySession);
|
|
@@ -2253,6 +2709,10 @@ function resolveExeSession() {
|
|
|
2253
2709
|
`[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
|
|
2254
2710
|
`
|
|
2255
2711
|
);
|
|
2712
|
+
try {
|
|
2713
|
+
registerParentExe(key, fromSessionName);
|
|
2714
|
+
} catch {
|
|
2715
|
+
}
|
|
2256
2716
|
candidate = fromSessionName;
|
|
2257
2717
|
} else {
|
|
2258
2718
|
candidate = fromCache;
|
|
@@ -3206,6 +3666,19 @@ async function resolveTask(client, identifier, scopeSession) {
|
|
|
3206
3666
|
args: [identifier, ...scope.args]
|
|
3207
3667
|
});
|
|
3208
3668
|
if (result.rows.length === 1) return result.rows[0];
|
|
3669
|
+
if (/^[a-f0-9]{7,12}$/i.test(identifier)) {
|
|
3670
|
+
result = await client.execute({
|
|
3671
|
+
sql: `SELECT * FROM tasks WHERE id LIKE ?`,
|
|
3672
|
+
args: [`${identifier}%`]
|
|
3673
|
+
});
|
|
3674
|
+
if (result.rows.length === 1) return result.rows[0];
|
|
3675
|
+
if (result.rows.length > 1) {
|
|
3676
|
+
const matches = result.rows.map((r) => `${String(r.id)} "${String(r.title)}" (${String(r.status)})`).join(", ");
|
|
3677
|
+
throw new Error(
|
|
3678
|
+
`Multiple tasks match short-ID "${identifier}": ${matches}. Use a longer prefix to disambiguate.`
|
|
3679
|
+
);
|
|
3680
|
+
}
|
|
3681
|
+
}
|
|
3209
3682
|
result = await client.execute({
|
|
3210
3683
|
sql: `SELECT * FROM tasks WHERE task_file LIKE ?${scope.sql}`,
|
|
3211
3684
|
args: [`%${identifier}%`, ...scope.args]
|
|
@@ -3752,12 +4225,13 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
3752
4225
|
WHERE blocked_by = ? AND status = 'blocked'`,
|
|
3753
4226
|
args: [now, taskId]
|
|
3754
4227
|
});
|
|
3755
|
-
if (
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
4228
|
+
if (unblocked.rowsAffected === 0) return;
|
|
4229
|
+
const ubScope = sessionScopeFilter();
|
|
4230
|
+
const unblockedRows = await client.execute({
|
|
4231
|
+
sql: `SELECT id, title, assigned_to, priority, task_file FROM tasks WHERE blocked_by IS NULL AND updated_at = ?${ubScope.sql}`,
|
|
4232
|
+
args: [now, ...ubScope.args]
|
|
4233
|
+
});
|
|
4234
|
+
if (baseDir) {
|
|
3761
4235
|
for (const ur of unblockedRows.rows) {
|
|
3762
4236
|
try {
|
|
3763
4237
|
const ubFile = path15.join(baseDir, String(ur.task_file));
|
|
@@ -3769,6 +4243,19 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
3769
4243
|
}
|
|
3770
4244
|
}
|
|
3771
4245
|
}
|
|
4246
|
+
if (unblockedRows.rows.length > 0 && !process.env.VITEST) {
|
|
4247
|
+
try {
|
|
4248
|
+
const { queueIntercom: queueIntercom2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
|
|
4249
|
+
const dispatched = /* @__PURE__ */ new Set();
|
|
4250
|
+
for (const ur of unblockedRows.rows) {
|
|
4251
|
+
const assignee = String(ur.assigned_to);
|
|
4252
|
+
if (dispatched.has(assignee)) continue;
|
|
4253
|
+
dispatched.add(assignee);
|
|
4254
|
+
queueIntercom2(`${assignee}`, `unblocked: "${String(ur.title)}" is now ready`);
|
|
4255
|
+
}
|
|
4256
|
+
} catch {
|
|
4257
|
+
}
|
|
4258
|
+
}
|
|
3772
4259
|
}
|
|
3773
4260
|
async function findNextTask(assignedTo) {
|
|
3774
4261
|
const client = getClient();
|
|
@@ -4427,6 +4914,15 @@ var init_embedder = __esm({
|
|
|
4427
4914
|
// src/lib/behaviors.ts
|
|
4428
4915
|
import crypto5 from "crypto";
|
|
4429
4916
|
async function storeBehavior(opts) {
|
|
4917
|
+
try {
|
|
4918
|
+
const { loadEmployeesSync: loadEmployeesSync2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
|
|
4919
|
+
const roster = loadEmployeesSync2();
|
|
4920
|
+
if (roster.length > 0 && !roster.some((e) => e.name === opts.agentId)) {
|
|
4921
|
+
throw new Error(`Agent "${opts.agentId}" not found in roster. Cannot store behavior for unregistered agent.`);
|
|
4922
|
+
}
|
|
4923
|
+
} catch (e) {
|
|
4924
|
+
if (e instanceof Error && e.message.includes("not found in roster")) throw e;
|
|
4925
|
+
}
|
|
4430
4926
|
const client = getClient();
|
|
4431
4927
|
const id = crypto5.randomUUID();
|
|
4432
4928
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -4437,10 +4933,18 @@ async function storeBehavior(opts) {
|
|
|
4437
4933
|
vector = new Float32Array(vec);
|
|
4438
4934
|
} catch {
|
|
4439
4935
|
}
|
|
4936
|
+
let createdByDevice = null;
|
|
4937
|
+
try {
|
|
4938
|
+
const { loadDeviceId: loadDeviceId2 } = await Promise.resolve().then(() => (init_license(), license_exports));
|
|
4939
|
+
createdByDevice = loadDeviceId2() ?? null;
|
|
4940
|
+
} catch {
|
|
4941
|
+
}
|
|
4942
|
+
const createdByAgent = process.env.AGENT_ID ?? null;
|
|
4943
|
+
const sourceSessionId = process.env.CLAUDE_SESSION_ID ?? process.env.SESSION_ID ?? null;
|
|
4440
4944
|
await client.execute({
|
|
4441
|
-
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at, vector)
|
|
4442
|
-
VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?)`,
|
|
4443
|
-
args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now, vector ? vector.buffer : null]
|
|
4945
|
+
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)
|
|
4946
|
+
VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?)`,
|
|
4947
|
+
args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now, vector ? vector.buffer : null, createdByAgent, createdByDevice, sourceSessionId]
|
|
4444
4948
|
});
|
|
4445
4949
|
return id;
|
|
4446
4950
|
}
|
|
@@ -4872,6 +5376,12 @@ async function updateTask(input) {
|
|
|
4872
5376
|
}
|
|
4873
5377
|
}
|
|
4874
5378
|
}
|
|
5379
|
+
if (input.status === "cancelled") {
|
|
5380
|
+
try {
|
|
5381
|
+
await cascadeUnblock(taskId, input.baseDir, now);
|
|
5382
|
+
} catch {
|
|
5383
|
+
}
|
|
5384
|
+
}
|
|
4875
5385
|
if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
4876
5386
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
4877
5387
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|