@askexenow/exe-os 0.9.111 → 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.
Files changed (79) hide show
  1. package/dist/bin/agentic-ontology-backfill.js +8 -1
  2. package/dist/bin/agentic-reflection-backfill.js +8 -1
  3. package/dist/bin/agentic-semantic-label.js +8 -1
  4. package/dist/bin/backfill-conversations.js +8 -1
  5. package/dist/bin/backfill-responses.js +8 -1
  6. package/dist/bin/backfill-vectors.js +8 -1
  7. package/dist/bin/bulk-sync-postgres.js +8 -1
  8. package/dist/bin/cleanup-stale-review-tasks.js +8 -1
  9. package/dist/bin/cli.js +19 -4
  10. package/dist/bin/exe-agent.js +1 -1
  11. package/dist/bin/exe-assign.js +8 -1
  12. package/dist/bin/exe-boot.js +19 -4
  13. package/dist/bin/exe-call.js +1 -1
  14. package/dist/bin/exe-cloud.js +8 -1
  15. package/dist/bin/exe-dispatch.js +460 -5
  16. package/dist/bin/exe-doctor.js +8 -1
  17. package/dist/bin/exe-export-behaviors.js +13 -3
  18. package/dist/bin/exe-forget.js +8 -1
  19. package/dist/bin/exe-gateway.js +19 -4
  20. package/dist/bin/exe-heartbeat.js +8 -1
  21. package/dist/bin/exe-kill.js +8 -1
  22. package/dist/bin/exe-launch-agent.js +13 -3
  23. package/dist/bin/exe-new-employee.js +1 -1
  24. package/dist/bin/exe-pending-messages.js +8 -1
  25. package/dist/bin/exe-pending-notifications.js +8 -1
  26. package/dist/bin/exe-pending-reviews.js +8 -1
  27. package/dist/bin/exe-rename.js +8 -1
  28. package/dist/bin/exe-review.js +8 -1
  29. package/dist/bin/exe-search.js +8 -1
  30. package/dist/bin/exe-session-cleanup.js +460 -5
  31. package/dist/bin/exe-start-codex.js +13 -3
  32. package/dist/bin/exe-start-opencode.js +13 -3
  33. package/dist/bin/exe-status.js +8 -1
  34. package/dist/bin/exe-team.js +8 -1
  35. package/dist/bin/git-sweep.js +460 -5
  36. package/dist/bin/graph-backfill.js +8 -1
  37. package/dist/bin/graph-export.js +8 -1
  38. package/dist/bin/intercom-check.js +460 -5
  39. package/dist/bin/pre-publish.js +1 -1
  40. package/dist/bin/scan-tasks.js +460 -5
  41. package/dist/bin/setup.js +8 -1
  42. package/dist/bin/shard-migrate.js +8 -1
  43. package/dist/gateway/index.js +460 -5
  44. package/dist/hooks/bug-report-worker.js +460 -5
  45. package/dist/hooks/codex-stop-task-finalizer.js +467 -5
  46. package/dist/hooks/commit-complete.js +460 -5
  47. package/dist/hooks/error-recall.js +8 -1
  48. package/dist/hooks/ingest.js +8 -1
  49. package/dist/hooks/instructions-loaded.js +8 -1
  50. package/dist/hooks/notification.js +8 -1
  51. package/dist/hooks/post-compact.js +8 -1
  52. package/dist/hooks/post-tool-combined.js +8 -1
  53. package/dist/hooks/pre-compact.js +460 -5
  54. package/dist/hooks/pre-tool-use.js +8 -1
  55. package/dist/hooks/prompt-submit.js +469 -8
  56. package/dist/hooks/session-end.js +460 -5
  57. package/dist/hooks/session-start.js +8 -1
  58. package/dist/hooks/stop.js +8 -1
  59. package/dist/hooks/subagent-stop.js +8 -1
  60. package/dist/hooks/summary-worker.js +8 -1
  61. package/dist/index.js +469 -8
  62. package/dist/lib/cloud-sync.js +7 -0
  63. package/dist/lib/database.js +7 -0
  64. package/dist/lib/db.js +7 -0
  65. package/dist/lib/device-registry.js +7 -0
  66. package/dist/lib/employee-templates.js +1 -1
  67. package/dist/lib/exe-daemon.js +216 -12
  68. package/dist/lib/hybrid-search.js +8 -1
  69. package/dist/lib/schedules.js +8 -1
  70. package/dist/lib/skill-learning.js +488 -7
  71. package/dist/lib/store.js +8 -1
  72. package/dist/lib/tasks.js +452 -4
  73. package/dist/lib/tmux-routing.js +452 -4
  74. package/dist/mcp/server.js +69 -11
  75. package/dist/mcp/tools/create-task.js +452 -4
  76. package/dist/mcp/tools/update-task.js +452 -4
  77. package/dist/runtime/index.js +469 -8
  78. package/dist/tui/App.js +19 -4
  79. package/package.json +1 -1
@@ -2069,6 +2069,13 @@ async function ensureSchema() {
2069
2069
  } catch (e) {
2070
2070
  logCatchDebug("migration", e);
2071
2071
  }
2072
+ for (const col of ["created_by_agent TEXT", "created_by_device TEXT", "source_session_id TEXT"]) {
2073
+ try {
2074
+ await client.execute({ sql: `ALTER TABLE behaviors ADD COLUMN ${col}`, args: [] });
2075
+ } catch (e) {
2076
+ logCatchDebug("migration", e);
2077
+ }
2078
+ }
2072
2079
  try {
2073
2080
  await client.execute({
2074
2081
  sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
@@ -4597,7 +4604,7 @@ var init_platform_procedures = __esm({
4597
4604
  title: "MCP tool dispatch \u2014 all tools use action parameter",
4598
4605
  domain: "tool-use",
4599
4606
  priority: "p0",
4600
- content: 'exe-os MCP tools come in two surfaces depending on EXE_MCP_TOOL_SURFACE config. Consolidated (19 tools): action-based dispatch \u2014 memory(action="recall"), task(action="create"), etc. Legacy (108 tools): one tool per operation \u2014 recall_my_memory, create_task, etc. Both surfaces have identical functionality. Use whichever tool names are available in your session. If you see domain tools (memory, task, config, etc.), use the action parameter. If you see specific tools (recall_my_memory, create_task, etc.), call them directly.'
4607
+ 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.'
4601
4608
  },
4602
4609
  {
4603
4610
  title: "MCP tools \u2014 memory, decision, and search",
@@ -6289,6 +6296,23 @@ var init_intercom_queue = __esm({
6289
6296
  });
6290
6297
 
6291
6298
  // src/lib/license.ts
6299
+ var license_exports = {};
6300
+ __export(license_exports, {
6301
+ LICENSE_PUBLIC_KEY_PEM: () => LICENSE_PUBLIC_KEY_PEM,
6302
+ PLAN_LIMITS: () => PLAN_LIMITS,
6303
+ assertVpsLicense: () => assertVpsLicense,
6304
+ checkLicense: () => checkLicense,
6305
+ getCachedLicense: () => getCachedLicense,
6306
+ isFeatureAllowed: () => isFeatureAllowed,
6307
+ loadDeviceId: () => loadDeviceId,
6308
+ loadLicense: () => loadLicense,
6309
+ mirrorLicenseKey: () => mirrorLicenseKey,
6310
+ readCachedLicenseToken: () => readCachedLicenseToken,
6311
+ saveLicense: () => saveLicense,
6312
+ startLicenseRevalidation: () => startLicenseRevalidation,
6313
+ stopLicenseRevalidation: () => stopLicenseRevalidation,
6314
+ validateLicense: () => validateLicense
6315
+ });
6292
6316
  import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync12, mkdirSync as mkdirSync6 } from "fs";
6293
6317
  import { randomUUID as randomUUID3 } from "crypto";
6294
6318
  import { createRequire as createRequire2 } from "module";
@@ -6296,7 +6320,411 @@ import { pathToFileURL as pathToFileURL2 } from "url";
6296
6320
  import os8 from "os";
6297
6321
  import path11 from "path";
6298
6322
  import { jwtVerify, importSPKI } from "jose";
6299
- var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, PLAN_LIMITS;
6323
+ async function fetchRetry(url, init) {
6324
+ try {
6325
+ return await fetch(url, init);
6326
+ } catch {
6327
+ await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
6328
+ return fetch(url, { ...init, signal: AbortSignal.timeout(1e4) });
6329
+ }
6330
+ }
6331
+ function loadDeviceId() {
6332
+ const deviceJsonPath = path11.join(EXE_AI_DIR, "device.json");
6333
+ try {
6334
+ if (existsSync12(deviceJsonPath)) {
6335
+ const data = JSON.parse(readFileSync8(deviceJsonPath, "utf8"));
6336
+ if (data.deviceId) return data.deviceId;
6337
+ }
6338
+ } catch {
6339
+ }
6340
+ try {
6341
+ if (existsSync12(DEVICE_ID_PATH)) {
6342
+ const id2 = readFileSync8(DEVICE_ID_PATH, "utf8").trim();
6343
+ if (id2) return id2;
6344
+ }
6345
+ } catch {
6346
+ }
6347
+ const id = randomUUID3();
6348
+ mkdirSync6(EXE_AI_DIR, { recursive: true });
6349
+ writeFileSync6(DEVICE_ID_PATH, id, "utf8");
6350
+ return id;
6351
+ }
6352
+ function loadLicense() {
6353
+ try {
6354
+ if (!existsSync12(LICENSE_PATH)) return null;
6355
+ return readFileSync8(LICENSE_PATH, "utf8").trim();
6356
+ } catch {
6357
+ return null;
6358
+ }
6359
+ }
6360
+ function saveLicense(apiKey) {
6361
+ mkdirSync6(EXE_AI_DIR, { recursive: true });
6362
+ writeFileSync6(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
6363
+ }
6364
+ async function verifyLicenseJwt(token) {
6365
+ try {
6366
+ const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
6367
+ const { payload } = await jwtVerify(token, key, {
6368
+ algorithms: [LICENSE_JWT_ALG]
6369
+ });
6370
+ const plan = payload.plan ?? "free";
6371
+ const email = payload.sub ?? "";
6372
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
6373
+ return {
6374
+ valid: true,
6375
+ plan,
6376
+ email,
6377
+ expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
6378
+ deviceLimit: limits.devices,
6379
+ employeeLimit: limits.employees,
6380
+ memoryLimit: limits.memories
6381
+ };
6382
+ } catch {
6383
+ return null;
6384
+ }
6385
+ }
6386
+ async function getCachedLicense() {
6387
+ try {
6388
+ if (!existsSync12(CACHE_PATH)) return null;
6389
+ const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
6390
+ if (!raw.token || typeof raw.token !== "string") return null;
6391
+ return await verifyLicenseJwt(raw.token);
6392
+ } catch {
6393
+ return null;
6394
+ }
6395
+ }
6396
+ function readCachedLicenseToken() {
6397
+ try {
6398
+ if (!existsSync12(CACHE_PATH)) return null;
6399
+ const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
6400
+ return typeof raw.token === "string" ? raw.token : null;
6401
+ } catch {
6402
+ return null;
6403
+ }
6404
+ }
6405
+ function getRawCachedPlan() {
6406
+ try {
6407
+ const token = readCachedLicenseToken();
6408
+ if (!token) return null;
6409
+ const parts = token.split(".");
6410
+ if (parts.length !== 3) return null;
6411
+ const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
6412
+ const plan = payload.plan ?? "free";
6413
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
6414
+ process.stderr.write(
6415
+ `[license] WARN: using unverified cached plan (API unreachable, JWT expired). Plan: ${plan}
6416
+ `
6417
+ );
6418
+ return {
6419
+ valid: true,
6420
+ plan,
6421
+ email: payload.sub ?? "",
6422
+ expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
6423
+ deviceLimit: limits.devices,
6424
+ employeeLimit: limits.employees,
6425
+ memoryLimit: limits.memories
6426
+ };
6427
+ } catch {
6428
+ return null;
6429
+ }
6430
+ }
6431
+ function cacheResponse(token) {
6432
+ try {
6433
+ writeFileSync6(CACHE_PATH, JSON.stringify({ token }), "utf8");
6434
+ } catch {
6435
+ }
6436
+ }
6437
+ function loadPrismaForLicense() {
6438
+ if (_prismaFailed) return null;
6439
+ const dbUrl = process.env.DATABASE_URL;
6440
+ if (!dbUrl) {
6441
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path11.join(os8.homedir(), "exe-db");
6442
+ if (!existsSync12(path11.join(exeDbRoot, "package.json"))) {
6443
+ _prismaFailed = true;
6444
+ return null;
6445
+ }
6446
+ }
6447
+ if (!_prismaPromise) {
6448
+ _prismaPromise = (async () => {
6449
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
6450
+ if (explicitPath) {
6451
+ const mod2 = await import(pathToFileURL2(explicitPath).href);
6452
+ const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
6453
+ if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
6454
+ return new Ctor2();
6455
+ }
6456
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path11.join(os8.homedir(), "exe-db");
6457
+ const req = createRequire2(path11.join(exeDbRoot, "package.json"));
6458
+ const entry = req.resolve("@prisma/client");
6459
+ const mod = await import(pathToFileURL2(entry).href);
6460
+ const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
6461
+ if (!Ctor) throw new Error(`No PrismaClient in ${entry}`);
6462
+ return new Ctor();
6463
+ })().catch((err) => {
6464
+ _prismaFailed = true;
6465
+ _prismaPromise = null;
6466
+ throw err;
6467
+ });
6468
+ }
6469
+ return _prismaPromise;
6470
+ }
6471
+ async function validateViaPostgres(apiKey) {
6472
+ const loader = loadPrismaForLicense();
6473
+ if (!loader) return null;
6474
+ try {
6475
+ const prisma = await loader;
6476
+ const rows = await prisma.$queryRawUnsafe(
6477
+ `SELECT plan, email, status, device_limit, employee_limit, memory_limit, expires_at
6478
+ FROM billing.licenses WHERE key = $1 LIMIT 1`,
6479
+ apiKey
6480
+ );
6481
+ if (!rows || rows.length === 0) return null;
6482
+ const row = rows[0];
6483
+ if (row.status !== "active") return null;
6484
+ if (row.expires_at && new Date(row.expires_at) < /* @__PURE__ */ new Date()) return null;
6485
+ const plan = row.plan;
6486
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
6487
+ return {
6488
+ valid: true,
6489
+ plan,
6490
+ email: row.email,
6491
+ expiresAt: row.expires_at ? new Date(row.expires_at).toISOString() : null,
6492
+ deviceLimit: row.device_limit ?? limits.devices,
6493
+ employeeLimit: row.employee_limit ?? limits.employees,
6494
+ memoryLimit: row.memory_limit ?? limits.memories
6495
+ };
6496
+ } catch {
6497
+ return null;
6498
+ }
6499
+ }
6500
+ async function validateViaCFWorker(apiKey, deviceId) {
6501
+ try {
6502
+ const res = await fetchRetry(`${API_BASE}/auth/activate`, {
6503
+ method: "POST",
6504
+ headers: { "Content-Type": "application/json" },
6505
+ body: JSON.stringify({ apiKey, deviceId }),
6506
+ signal: AbortSignal.timeout(1e4)
6507
+ });
6508
+ if (!res.ok) return null;
6509
+ const data = await res.json();
6510
+ if (data.error === "device_limit_exceeded") return null;
6511
+ if (!data.valid) return null;
6512
+ if (data.token) {
6513
+ cacheResponse(data.token);
6514
+ const verified = await verifyLicenseJwt(data.token);
6515
+ if (verified) return verified;
6516
+ }
6517
+ const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
6518
+ return {
6519
+ valid: data.valid,
6520
+ plan: data.plan,
6521
+ email: data.email,
6522
+ expiresAt: data.expiresAt,
6523
+ deviceLimit: limits.devices,
6524
+ employeeLimit: limits.employees,
6525
+ memoryLimit: limits.memories
6526
+ };
6527
+ } catch {
6528
+ return null;
6529
+ }
6530
+ }
6531
+ async function validateLicense(apiKey, deviceId) {
6532
+ const did = deviceId ?? loadDeviceId();
6533
+ const pgResult = await validateViaPostgres(apiKey);
6534
+ if (pgResult) {
6535
+ try {
6536
+ writeFileSync6(CACHE_PATH, JSON.stringify({ pgLicense: pgResult, ts: Date.now() }), "utf8");
6537
+ } catch {
6538
+ }
6539
+ return pgResult;
6540
+ }
6541
+ const cfResult = await validateViaCFWorker(apiKey, did);
6542
+ if (cfResult) return cfResult;
6543
+ const cached = await getCachedLicense();
6544
+ if (cached) return cached;
6545
+ try {
6546
+ if (existsSync12(CACHE_PATH)) {
6547
+ const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
6548
+ if (raw.pgLicense && raw.ts && Date.now() - raw.ts < 7 * 24 * 60 * 60 * 1e3) {
6549
+ return raw.pgLicense;
6550
+ }
6551
+ }
6552
+ } catch {
6553
+ }
6554
+ const rawFallback = getRawCachedPlan();
6555
+ if (rawFallback) return rawFallback;
6556
+ return { ...FREE_LICENSE, valid: false };
6557
+ }
6558
+ function getCacheAgeMs() {
6559
+ try {
6560
+ const { statSync: statSync5 } = __require("fs");
6561
+ const s = statSync5(CACHE_PATH);
6562
+ return Date.now() - s.mtimeMs;
6563
+ } catch {
6564
+ return Infinity;
6565
+ }
6566
+ }
6567
+ async function checkLicense() {
6568
+ let key = loadLicense();
6569
+ if (!key) {
6570
+ try {
6571
+ const configPath = path11.join(EXE_AI_DIR, "config.json");
6572
+ if (existsSync12(configPath)) {
6573
+ const raw = JSON.parse(readFileSync8(configPath, "utf8"));
6574
+ const cloud = raw.cloud;
6575
+ if (cloud?.apiKey) {
6576
+ key = cloud.apiKey;
6577
+ saveLicense(key);
6578
+ }
6579
+ }
6580
+ } catch {
6581
+ }
6582
+ }
6583
+ if (!key) return FREE_LICENSE;
6584
+ const cached = await getCachedLicense();
6585
+ if (cached && getCacheAgeMs() < CACHE_MAX_AGE_MS) return cached;
6586
+ const deviceId = loadDeviceId();
6587
+ return validateLicense(key, deviceId);
6588
+ }
6589
+ function isFeatureAllowed(license, feature) {
6590
+ switch (feature) {
6591
+ case "cloud_sync":
6592
+ case "external_agents":
6593
+ case "wiki":
6594
+ return license.plan !== "free";
6595
+ case "unlimited_employees":
6596
+ return license.plan === "team" || license.plan === "agency" || license.plan === "enterprise";
6597
+ }
6598
+ }
6599
+ function mirrorLicenseKey(apiKey) {
6600
+ const trimmed = apiKey.trim();
6601
+ if (!trimmed) return;
6602
+ saveLicense(trimmed);
6603
+ }
6604
+ async function assertVpsLicense(opts) {
6605
+ const env = opts?.env ?? process.env;
6606
+ const inProduction = env.NODE_ENV === "production";
6607
+ if (!opts?.force && !inProduction) {
6608
+ return { ...FREE_LICENSE, plan: "free" };
6609
+ }
6610
+ const envKey = env.EXE_LICENSE_KEY?.trim();
6611
+ if (envKey) {
6612
+ saveLicense(envKey);
6613
+ }
6614
+ const apiKey = envKey ?? loadLicense();
6615
+ if (!apiKey) {
6616
+ throw new Error(
6617
+ "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."
6618
+ );
6619
+ }
6620
+ const deviceId = loadDeviceId();
6621
+ let backendResponse = null;
6622
+ let explicitRejection = false;
6623
+ let transientFailure = false;
6624
+ try {
6625
+ const res = await fetchRetry(`${API_BASE}/auth/activate`, {
6626
+ method: "POST",
6627
+ headers: { "Content-Type": "application/json" },
6628
+ body: JSON.stringify({ apiKey, deviceId }),
6629
+ signal: AbortSignal.timeout(1e4)
6630
+ });
6631
+ if (res.ok) {
6632
+ backendResponse = await res.json();
6633
+ if (!backendResponse.valid) explicitRejection = true;
6634
+ } else if (res.status === 401 || res.status === 403) {
6635
+ explicitRejection = true;
6636
+ } else {
6637
+ transientFailure = true;
6638
+ }
6639
+ } catch {
6640
+ transientFailure = true;
6641
+ }
6642
+ if (backendResponse?.valid) {
6643
+ if (backendResponse.token) {
6644
+ cacheResponse(backendResponse.token);
6645
+ const verified = await verifyLicenseJwt(backendResponse.token);
6646
+ if (verified) return verified;
6647
+ }
6648
+ const limits = PLAN_LIMITS[backendResponse.plan] ?? PLAN_LIMITS.free;
6649
+ return {
6650
+ valid: true,
6651
+ plan: backendResponse.plan,
6652
+ email: backendResponse.email,
6653
+ expiresAt: backendResponse.expiresAt,
6654
+ deviceLimit: limits.devices,
6655
+ employeeLimit: limits.employees,
6656
+ memoryLimit: limits.memories
6657
+ };
6658
+ }
6659
+ if (explicitRejection) {
6660
+ throw new Error(
6661
+ `License invalid or expired. Renew at https://askexe.com, then restart. Backend rejected key ending in ...${apiKey.slice(-4)}.`
6662
+ );
6663
+ }
6664
+ if (!transientFailure) {
6665
+ throw new Error(
6666
+ "License validation failed: unknown backend state. Restore network connectivity to https://askexe.com/cloud and retry."
6667
+ );
6668
+ }
6669
+ const fresh = await getCachedLicense();
6670
+ if (fresh && fresh.valid) return fresh;
6671
+ const graceDays = opts?.offlineGraceDays ?? 7;
6672
+ const graceMs = graceDays * 24 * 60 * 60 * 1e3;
6673
+ try {
6674
+ const token = readCachedLicenseToken();
6675
+ if (token) {
6676
+ const payloadB64 = token.split(".")[1];
6677
+ if (payloadB64) {
6678
+ const payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString());
6679
+ const expMs = (payload.exp ?? 0) * 1e3;
6680
+ if (Date.now() < expMs + graceMs) {
6681
+ const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
6682
+ const { payload: verified } = await jwtVerify(token, key, {
6683
+ algorithms: [LICENSE_JWT_ALG],
6684
+ clockTolerance: graceDays * 24 * 60 * 60
6685
+ });
6686
+ const plan = verified.plan ?? "free";
6687
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
6688
+ return {
6689
+ valid: true,
6690
+ plan,
6691
+ email: verified.sub ?? "",
6692
+ expiresAt: verified.exp ? new Date(verified.exp * 1e3).toISOString() : null,
6693
+ deviceLimit: limits.devices,
6694
+ employeeLimit: limits.employees,
6695
+ memoryLimit: limits.memories
6696
+ };
6697
+ }
6698
+ }
6699
+ }
6700
+ } catch {
6701
+ }
6702
+ throw new Error(
6703
+ `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.`
6704
+ );
6705
+ }
6706
+ function startLicenseRevalidation(intervalMs = 36e5) {
6707
+ if (_revalTimer) return;
6708
+ _revalTimer = setInterval(async () => {
6709
+ try {
6710
+ const license = await checkLicense();
6711
+ if (!license.valid) {
6712
+ process.stderr.write("[exe-os] License expired or invalid \u2014 features may be restricted\n");
6713
+ }
6714
+ } catch {
6715
+ }
6716
+ }, intervalMs);
6717
+ if (_revalTimer && typeof _revalTimer === "object" && "unref" in _revalTimer) {
6718
+ _revalTimer.unref();
6719
+ }
6720
+ }
6721
+ function stopLicenseRevalidation() {
6722
+ if (_revalTimer) {
6723
+ clearInterval(_revalTimer);
6724
+ _revalTimer = null;
6725
+ }
6726
+ }
6727
+ 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;
6300
6728
  var init_license = __esm({
6301
6729
  "src/lib/license.ts"() {
6302
6730
  "use strict";
@@ -6305,6 +6733,12 @@ var init_license = __esm({
6305
6733
  CACHE_PATH = path11.join(EXE_AI_DIR, "license-cache.json");
6306
6734
  DEVICE_ID_PATH = path11.join(EXE_AI_DIR, "device-id");
6307
6735
  API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com/cloud";
6736
+ RETRY_DELAY_MS = 500;
6737
+ LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
6738
+ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
6739
+ 4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
6740
+ -----END PUBLIC KEY-----`;
6741
+ LICENSE_JWT_ALG = "ES256";
6308
6742
  PLAN_LIMITS = {
6309
6743
  free: { devices: 1, employees: 1, memories: 5e3 },
6310
6744
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -6312,6 +6746,19 @@ var init_license = __esm({
6312
6746
  agency: { devices: 50, employees: 100, memories: 1e7 },
6313
6747
  enterprise: { devices: -1, employees: -1, memories: -1 }
6314
6748
  };
6749
+ FREE_LICENSE = {
6750
+ valid: true,
6751
+ plan: "free",
6752
+ email: "",
6753
+ expiresAt: null,
6754
+ deviceLimit: 1,
6755
+ employeeLimit: 1,
6756
+ memoryLimit: 5e3
6757
+ };
6758
+ _prismaPromise = null;
6759
+ _prismaFailed = false;
6760
+ CACHE_MAX_AGE_MS = 36e5;
6761
+ _revalTimer = null;
6315
6762
  }
6316
6763
  });
6317
6764
 
@@ -7841,10 +8288,18 @@ async function storeBehavior(opts) {
7841
8288
  vector = new Float32Array(vec);
7842
8289
  } catch {
7843
8290
  }
8291
+ let createdByDevice = null;
8292
+ try {
8293
+ const { loadDeviceId: loadDeviceId2 } = await Promise.resolve().then(() => (init_license(), license_exports));
8294
+ createdByDevice = loadDeviceId2() ?? null;
8295
+ } catch {
8296
+ }
8297
+ const createdByAgent = process.env.AGENT_ID ?? null;
8298
+ const sourceSessionId = process.env.CLAUDE_SESSION_ID ?? process.env.SESSION_ID ?? null;
7844
8299
  await client.execute({
7845
- sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at, vector)
7846
- VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?)`,
7847
- args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now, vector ? vector.buffer : null]
8300
+ 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)
8301
+ VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?)`,
8302
+ args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now, vector ? vector.buffer : null, createdByAgent, createdByDevice, sourceSessionId]
7848
8303
  });
7849
8304
  return id;
7850
8305
  }
@@ -1898,6 +1898,13 @@ async function ensureSchema() {
1898
1898
  } catch (e) {
1899
1899
  logCatchDebug("migration", e);
1900
1900
  }
1901
+ for (const col of ["created_by_agent TEXT", "created_by_device TEXT", "source_session_id TEXT"]) {
1902
+ try {
1903
+ await client.execute({ sql: `ALTER TABLE behaviors ADD COLUMN ${col}`, args: [] });
1904
+ } catch (e) {
1905
+ logCatchDebug("migration", e);
1906
+ }
1907
+ }
1901
1908
  try {
1902
1909
  await client.execute({
1903
1910
  sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
@@ -3869,7 +3876,7 @@ var init_platform_procedures = __esm({
3869
3876
  title: "MCP tool dispatch \u2014 all tools use action parameter",
3870
3877
  domain: "tool-use",
3871
3878
  priority: "p0",
3872
- content: 'exe-os MCP tools come in two surfaces depending on EXE_MCP_TOOL_SURFACE config. Consolidated (19 tools): action-based dispatch \u2014 memory(action="recall"), task(action="create"), etc. Legacy (108 tools): one tool per operation \u2014 recall_my_memory, create_task, etc. Both surfaces have identical functionality. Use whichever tool names are available in your session. If you see domain tools (memory, task, config, etc.), use the action parameter. If you see specific tools (recall_my_memory, create_task, etc.), call them directly.'
3879
+ 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.'
3873
3880
  },
3874
3881
  {
3875
3882
  title: "MCP tools \u2014 memory, decision, and search",
@@ -6170,7 +6177,7 @@ import crypto2 from "crypto";
6170
6177
  async function listBehaviors(agentId, projectName, limit = 30) {
6171
6178
  const client = getClient();
6172
6179
  const result = await client.execute({
6173
- sql: `SELECT id, agent_id, project_name, domain, priority, content, active, created_at, updated_at, vector
6180
+ sql: `SELECT id, agent_id, project_name, domain, priority, content, active, created_at, updated_at, vector, created_by_agent, created_by_device, source_session_id
6174
6181
  FROM behaviors
6175
6182
  WHERE agent_id = ? AND active = 1
6176
6183
  AND (project_name IS NULL OR project_name = ?)
@@ -6199,7 +6206,10 @@ async function listBehaviors(agentId, projectName, limit = 30) {
6199
6206
  active: Number(r.active),
6200
6207
  created_at: String(r.created_at),
6201
6208
  updated_at: String(r.updated_at),
6202
- vector: r.vector ? Array.from(new Float32Array(r.vector)) : null
6209
+ vector: r.vector ? Array.from(new Float32Array(r.vector)) : null,
6210
+ created_by_agent: r.created_by_agent ? String(r.created_by_agent) : null,
6211
+ created_by_device: r.created_by_device ? String(r.created_by_device) : null,
6212
+ source_session_id: r.source_session_id ? String(r.source_session_id) : null
6203
6213
  }));
6204
6214
  }
6205
6215
 
@@ -1898,6 +1898,13 @@ async function ensureSchema() {
1898
1898
  } catch (e) {
1899
1899
  logCatchDebug("migration", e);
1900
1900
  }
1901
+ for (const col of ["created_by_agent TEXT", "created_by_device TEXT", "source_session_id TEXT"]) {
1902
+ try {
1903
+ await client.execute({ sql: `ALTER TABLE behaviors ADD COLUMN ${col}`, args: [] });
1904
+ } catch (e) {
1905
+ logCatchDebug("migration", e);
1906
+ }
1907
+ }
1901
1908
  try {
1902
1909
  await client.execute({
1903
1910
  sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
@@ -3869,7 +3876,7 @@ var init_platform_procedures = __esm({
3869
3876
  title: "MCP tool dispatch \u2014 all tools use action parameter",
3870
3877
  domain: "tool-use",
3871
3878
  priority: "p0",
3872
- content: 'exe-os MCP tools come in two surfaces depending on EXE_MCP_TOOL_SURFACE config. Consolidated (19 tools): action-based dispatch \u2014 memory(action="recall"), task(action="create"), etc. Legacy (108 tools): one tool per operation \u2014 recall_my_memory, create_task, etc. Both surfaces have identical functionality. Use whichever tool names are available in your session. If you see domain tools (memory, task, config, etc.), use the action parameter. If you see specific tools (recall_my_memory, create_task, etc.), call them directly.'
3879
+ 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.'
3873
3880
  },
3874
3881
  {
3875
3882
  title: "MCP tools \u2014 memory, decision, and search",
@@ -6033,7 +6040,7 @@ import crypto2 from "crypto";
6033
6040
  async function listBehaviors(agentId, projectName, limit = 30) {
6034
6041
  const client = getClient();
6035
6042
  const result = await client.execute({
6036
- sql: `SELECT id, agent_id, project_name, domain, priority, content, active, created_at, updated_at, vector
6043
+ sql: `SELECT id, agent_id, project_name, domain, priority, content, active, created_at, updated_at, vector, created_by_agent, created_by_device, source_session_id
6037
6044
  FROM behaviors
6038
6045
  WHERE agent_id = ? AND active = 1
6039
6046
  AND (project_name IS NULL OR project_name = ?)
@@ -6062,7 +6069,10 @@ async function listBehaviors(agentId, projectName, limit = 30) {
6062
6069
  active: Number(r.active),
6063
6070
  created_at: String(r.created_at),
6064
6071
  updated_at: String(r.updated_at),
6065
- vector: r.vector ? Array.from(new Float32Array(r.vector)) : null
6072
+ vector: r.vector ? Array.from(new Float32Array(r.vector)) : null,
6073
+ created_by_agent: r.created_by_agent ? String(r.created_by_agent) : null,
6074
+ created_by_device: r.created_by_device ? String(r.created_by_device) : null,
6075
+ source_session_id: r.source_session_id ? String(r.source_session_id) : null
6066
6076
  }));
6067
6077
  }
6068
6078
 
@@ -2048,6 +2048,13 @@ async function ensureSchema() {
2048
2048
  } catch (e) {
2049
2049
  logCatchDebug("migration", e);
2050
2050
  }
2051
+ for (const col of ["created_by_agent TEXT", "created_by_device TEXT", "source_session_id TEXT"]) {
2052
+ try {
2053
+ await client.execute({ sql: `ALTER TABLE behaviors ADD COLUMN ${col}`, args: [] });
2054
+ } catch (e) {
2055
+ logCatchDebug("migration", e);
2056
+ }
2057
+ }
2051
2058
  try {
2052
2059
  await client.execute({
2053
2060
  sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
@@ -4576,7 +4583,7 @@ var init_platform_procedures = __esm({
4576
4583
  title: "MCP tool dispatch \u2014 all tools use action parameter",
4577
4584
  domain: "tool-use",
4578
4585
  priority: "p0",
4579
- content: 'exe-os MCP tools come in two surfaces depending on EXE_MCP_TOOL_SURFACE config. Consolidated (19 tools): action-based dispatch \u2014 memory(action="recall"), task(action="create"), etc. Legacy (108 tools): one tool per operation \u2014 recall_my_memory, create_task, etc. Both surfaces have identical functionality. Use whichever tool names are available in your session. If you see domain tools (memory, task, config, etc.), use the action parameter. If you see specific tools (recall_my_memory, create_task, etc.), call them directly.'
4586
+ 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.'
4580
4587
  },
4581
4588
  {
4582
4589
  title: "MCP tools \u2014 memory, decision, and search",
@@ -2037,6 +2037,13 @@ async function ensureSchema() {
2037
2037
  } catch (e) {
2038
2038
  logCatchDebug("migration", e);
2039
2039
  }
2040
+ for (const col of ["created_by_agent TEXT", "created_by_device TEXT", "source_session_id TEXT"]) {
2041
+ try {
2042
+ await client.execute({ sql: `ALTER TABLE behaviors ADD COLUMN ${col}`, args: [] });
2043
+ } catch (e) {
2044
+ logCatchDebug("migration", e);
2045
+ }
2046
+ }
2040
2047
  try {
2041
2048
  await client.execute({
2042
2049
  sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
@@ -4565,7 +4572,7 @@ var init_platform_procedures = __esm({
4565
4572
  title: "MCP tool dispatch \u2014 all tools use action parameter",
4566
4573
  domain: "tool-use",
4567
4574
  priority: "p0",
4568
- content: 'exe-os MCP tools come in two surfaces depending on EXE_MCP_TOOL_SURFACE config. Consolidated (19 tools): action-based dispatch \u2014 memory(action="recall"), task(action="create"), etc. Legacy (108 tools): one tool per operation \u2014 recall_my_memory, create_task, etc. Both surfaces have identical functionality. Use whichever tool names are available in your session. If you see domain tools (memory, task, config, etc.), use the action parameter. If you see specific tools (recall_my_memory, create_task, etc.), call them directly.'
4575
+ 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.'
4569
4576
  },
4570
4577
  {
4571
4578
  title: "MCP tools \u2014 memory, decision, and search",