@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.
Files changed (95) hide show
  1. package/README.md +9 -7
  2. package/dist/bin/agentic-ontology-backfill.js +62 -12
  3. package/dist/bin/agentic-reflection-backfill.js +37 -2
  4. package/dist/bin/agentic-semantic-label.js +37 -2
  5. package/dist/bin/backfill-conversations.js +61 -11
  6. package/dist/bin/backfill-responses.js +62 -12
  7. package/dist/bin/backfill-vectors.js +37 -2
  8. package/dist/bin/bulk-sync-postgres.js +63 -13
  9. package/dist/bin/cleanup-stale-review-tasks.js +83 -16
  10. package/dist/bin/cli.js +312 -80
  11. package/dist/bin/exe-agent-config.js +7 -1
  12. package/dist/bin/exe-agent.js +29 -3
  13. package/dist/bin/exe-assign.js +62 -12
  14. package/dist/bin/exe-boot.js +500 -151
  15. package/dist/bin/exe-call.js +46 -5
  16. package/dist/bin/exe-cloud.js +101 -16
  17. package/dist/bin/exe-dispatch.js +827 -27
  18. package/dist/bin/exe-doctor.js +61 -11
  19. package/dist/bin/exe-export-behaviors.js +67 -14
  20. package/dist/bin/exe-forget.js +62 -12
  21. package/dist/bin/exe-gateway.js +147 -27
  22. package/dist/bin/exe-heartbeat.js +83 -16
  23. package/dist/bin/exe-kill.js +62 -12
  24. package/dist/bin/exe-launch-agent.js +83 -15
  25. package/dist/bin/exe-new-employee.js +176 -8
  26. package/dist/bin/exe-pending-messages.js +83 -16
  27. package/dist/bin/exe-pending-notifications.js +83 -16
  28. package/dist/bin/exe-pending-reviews.js +83 -16
  29. package/dist/bin/exe-rename.js +62 -12
  30. package/dist/bin/exe-review.js +62 -12
  31. package/dist/bin/exe-search.js +62 -12
  32. package/dist/bin/exe-session-cleanup.js +949 -149
  33. package/dist/bin/exe-settings.js +10 -4
  34. package/dist/bin/exe-start-codex.js +537 -248
  35. package/dist/bin/exe-start-opencode.js +547 -168
  36. package/dist/bin/exe-status.js +83 -16
  37. package/dist/bin/exe-support.js +1 -1
  38. package/dist/bin/exe-team.js +62 -12
  39. package/dist/bin/git-sweep.js +827 -27
  40. package/dist/bin/graph-backfill.js +62 -12
  41. package/dist/bin/graph-export.js +62 -12
  42. package/dist/bin/install.js +62 -4
  43. package/dist/bin/intercom-check.js +949 -149
  44. package/dist/bin/pre-publish.js +14 -2
  45. package/dist/bin/scan-tasks.js +827 -27
  46. package/dist/bin/setup.js +99 -14
  47. package/dist/bin/shard-migrate.js +62 -12
  48. package/dist/bin/stack-update.js +1 -1
  49. package/dist/bin/update.js +3 -3
  50. package/dist/gateway/index.js +586 -26
  51. package/dist/hooks/bug-report-worker.js +586 -26
  52. package/dist/hooks/codex-stop-task-finalizer.js +977 -143
  53. package/dist/hooks/commit-complete.js +827 -27
  54. package/dist/hooks/error-recall.js +62 -12
  55. package/dist/hooks/ingest.js +4579 -249
  56. package/dist/hooks/instructions-loaded.js +62 -12
  57. package/dist/hooks/notification.js +62 -12
  58. package/dist/hooks/post-compact.js +83 -16
  59. package/dist/hooks/post-tool-combined.js +83 -16
  60. package/dist/hooks/pre-compact.js +907 -107
  61. package/dist/hooks/pre-tool-use.js +98 -16
  62. package/dist/hooks/prompt-submit.js +596 -30
  63. package/dist/hooks/session-end.js +909 -112
  64. package/dist/hooks/session-start.js +112 -17
  65. package/dist/hooks/stop.js +82 -15
  66. package/dist/hooks/subagent-stop.js +83 -16
  67. package/dist/hooks/summary-worker.js +81 -8
  68. package/dist/index.js +595 -29
  69. package/dist/lib/agent-config.js +16 -1
  70. package/dist/lib/cloud-sync.js +45 -1
  71. package/dist/lib/consolidation.js +16 -1
  72. package/dist/lib/database.js +23 -0
  73. package/dist/lib/db.js +23 -0
  74. package/dist/lib/device-registry.js +23 -0
  75. package/dist/lib/employee-templates.js +30 -4
  76. package/dist/lib/employees.js +16 -1
  77. package/dist/lib/exe-daemon.js +482 -52
  78. package/dist/lib/hybrid-search.js +62 -12
  79. package/dist/lib/license.js +3 -3
  80. package/dist/lib/messaging.js +21 -4
  81. package/dist/lib/schedules.js +37 -2
  82. package/dist/lib/skill-learning.js +910 -41
  83. package/dist/lib/status-brief.js +14 -1
  84. package/dist/lib/store.js +62 -12
  85. package/dist/lib/tasks.js +843 -93
  86. package/dist/lib/tmux-routing.js +766 -16
  87. package/dist/mcp/server.js +238 -41
  88. package/dist/mcp/tools/create-task.js +525 -15
  89. package/dist/mcp/tools/deactivate-behavior.js +33 -24
  90. package/dist/mcp/tools/list-tasks.js +21 -4
  91. package/dist/mcp/tools/send-message.js +21 -4
  92. package/dist/mcp/tools/update-task.js +840 -93
  93. package/dist/runtime/index.js +913 -107
  94. package/dist/tui/App.js +227 -58
  95. package/package.json +1 -1
@@ -1,5 +1,11 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
4
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
5
+ }) : x)(function(x) {
6
+ if (typeof require !== "undefined") return require.apply(this, arguments);
7
+ throw Error('Dynamic require of "' + x + '" is not supported');
8
+ });
3
9
  var __esm = (fn, res) => function __init() {
4
10
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
11
  };
@@ -313,6 +319,394 @@ var init_config = __esm({
313
319
  }
314
320
  });
315
321
 
322
+ // src/lib/runtime-table.ts
323
+ var RUNTIME_TABLE, DEFAULT_RUNTIME;
324
+ var init_runtime_table = __esm({
325
+ "src/lib/runtime-table.ts"() {
326
+ "use strict";
327
+ RUNTIME_TABLE = {
328
+ codex: {
329
+ binary: "codex",
330
+ launchMode: "interactive",
331
+ autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
332
+ inlineFlag: "--no-alt-screen",
333
+ apiKeyEnv: "OPENAI_API_KEY",
334
+ defaultModel: "gpt-5.5"
335
+ },
336
+ opencode: {
337
+ binary: "opencode",
338
+ launchMode: "exec",
339
+ autoApproveFlag: "--dangerously-skip-permissions",
340
+ inlineFlag: "",
341
+ apiKeyEnv: "ANTHROPIC_API_KEY",
342
+ defaultModel: "anthropic/claude-sonnet-4-6"
343
+ }
344
+ };
345
+ DEFAULT_RUNTIME = "claude";
346
+ }
347
+ });
348
+
349
+ // src/lib/agent-config.ts
350
+ var agent_config_exports = {};
351
+ __export(agent_config_exports, {
352
+ AGENT_CONFIG_PATH: () => AGENT_CONFIG_PATH,
353
+ DEFAULT_MODELS: () => DEFAULT_MODELS,
354
+ KNOWN_RUNTIMES: () => KNOWN_RUNTIMES,
355
+ RUNTIME_LABELS: () => RUNTIME_LABELS,
356
+ clearAgentRuntime: () => clearAgentRuntime,
357
+ getAgentRuntime: () => getAgentRuntime,
358
+ loadAgentConfig: () => loadAgentConfig,
359
+ saveAgentConfig: () => saveAgentConfig,
360
+ setAgentMcps: () => setAgentMcps,
361
+ setAgentRuntime: () => setAgentRuntime
362
+ });
363
+ import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
364
+ import path2 from "path";
365
+ function loadAgentConfig() {
366
+ if (!existsSync3(AGENT_CONFIG_PATH)) return {};
367
+ try {
368
+ return JSON.parse(readFileSync2(AGENT_CONFIG_PATH, "utf-8"));
369
+ } catch {
370
+ return {};
371
+ }
372
+ }
373
+ function saveAgentConfig(config) {
374
+ const dir = path2.dirname(AGENT_CONFIG_PATH);
375
+ ensurePrivateDirSync(dir);
376
+ writeFileSync(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
377
+ enforcePrivateFileSync(AGENT_CONFIG_PATH);
378
+ }
379
+ function getAgentRuntime(agentId) {
380
+ const config = loadAgentConfig();
381
+ const entry = config[agentId];
382
+ if (entry) return entry;
383
+ const orgDefault = config["default"];
384
+ if (orgDefault) return orgDefault;
385
+ return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
386
+ }
387
+ function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
388
+ const knownModels = KNOWN_RUNTIMES[runtime];
389
+ if (!knownModels) {
390
+ return {
391
+ ok: false,
392
+ error: `Unknown runtime "${runtime}". Valid: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`
393
+ };
394
+ }
395
+ if (!knownModels.includes(model)) {
396
+ return {
397
+ ok: false,
398
+ error: `Unknown model "${model}" for runtime "${runtime}". Valid: ${knownModels.join(", ")}`
399
+ };
400
+ }
401
+ const config = loadAgentConfig();
402
+ const existing = config[agentId];
403
+ const entry = { runtime, model };
404
+ if (reasoning_effort) entry.reasoning_effort = reasoning_effort;
405
+ if (mcps !== void 0) {
406
+ entry.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
407
+ } else if (existing?.mcps) {
408
+ entry.mcps = existing.mcps;
409
+ }
410
+ config[agentId] = entry;
411
+ saveAgentConfig(config);
412
+ return { ok: true };
413
+ }
414
+ function setAgentMcps(agentId, mcps) {
415
+ const config = loadAgentConfig();
416
+ const existing = config[agentId] ?? getAgentRuntime(agentId);
417
+ existing.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
418
+ config[agentId] = existing;
419
+ saveAgentConfig(config);
420
+ return { ok: true };
421
+ }
422
+ function clearAgentRuntime(agentId) {
423
+ const config = loadAgentConfig();
424
+ delete config[agentId];
425
+ saveAgentConfig(config);
426
+ }
427
+ var AGENT_CONFIG_PATH, KNOWN_RUNTIMES, RUNTIME_LABELS, DEFAULT_MODELS;
428
+ var init_agent_config = __esm({
429
+ "src/lib/agent-config.ts"() {
430
+ "use strict";
431
+ init_config();
432
+ init_runtime_table();
433
+ init_secure_files();
434
+ AGENT_CONFIG_PATH = path2.join(EXE_AI_DIR, "agent-config.json");
435
+ KNOWN_RUNTIMES = {
436
+ claude: ["claude-opus-4.6", "claude-opus-4", "claude-sonnet-4.6", "claude-sonnet-4", "claude-haiku-4.5"],
437
+ codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
438
+ opencode: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4", "google/gemini-2.5-pro", "deepseek/deepseek-r3", "minimax/minimax-m2.5"]
439
+ };
440
+ RUNTIME_LABELS = {
441
+ claude: "Claude Code (Anthropic)",
442
+ codex: "Codex (OpenAI)",
443
+ opencode: "OpenCode (open source)"
444
+ };
445
+ DEFAULT_MODELS = {
446
+ claude: "claude-opus-4.6",
447
+ codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
448
+ opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
449
+ };
450
+ }
451
+ });
452
+
453
+ // src/lib/employees.ts
454
+ var employees_exports = {};
455
+ __export(employees_exports, {
456
+ COORDINATOR_ROLE: () => COORDINATOR_ROLE,
457
+ DEFAULT_COORDINATOR_TEMPLATE_NAME: () => DEFAULT_COORDINATOR_TEMPLATE_NAME,
458
+ EMPLOYEES_PATH: () => EMPLOYEES_PATH,
459
+ addEmployee: () => addEmployee,
460
+ baseAgentName: () => baseAgentName,
461
+ canCoordinate: () => canCoordinate,
462
+ getCoordinatorEmployee: () => getCoordinatorEmployee,
463
+ getCoordinatorName: () => getCoordinatorName,
464
+ getEmployee: () => getEmployee,
465
+ getEmployeeByRole: () => getEmployeeByRole,
466
+ getEmployeeNamesByRole: () => getEmployeeNamesByRole,
467
+ hasRole: () => hasRole,
468
+ hireEmployee: () => hireEmployee,
469
+ isCoordinatorName: () => isCoordinatorName,
470
+ isCoordinatorRole: () => isCoordinatorRole,
471
+ isMultiInstance: () => isMultiInstance,
472
+ loadEmployees: () => loadEmployees,
473
+ loadEmployeesSync: () => loadEmployeesSync,
474
+ normalizeRole: () => normalizeRole,
475
+ normalizeRosterCase: () => normalizeRosterCase,
476
+ registerBinSymlinks: () => registerBinSymlinks,
477
+ saveEmployees: () => saveEmployees,
478
+ validateEmployeeName: () => validateEmployeeName
479
+ });
480
+ import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
481
+ import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
482
+ import { execSync } from "child_process";
483
+ import path3 from "path";
484
+ import os2 from "os";
485
+ function normalizeRole(role) {
486
+ return (role ?? "").trim().toLowerCase();
487
+ }
488
+ function isCoordinatorRole(role) {
489
+ return normalizeRole(role) === normalizeRole(COORDINATOR_ROLE);
490
+ }
491
+ function getCoordinatorEmployee(employees) {
492
+ return employees.find((e) => isCoordinatorRole(e.role));
493
+ }
494
+ function getCoordinatorName(employees = loadEmployeesSync()) {
495
+ return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
496
+ }
497
+ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
498
+ if (!agentName) return false;
499
+ return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
500
+ }
501
+ function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
502
+ return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
503
+ }
504
+ function validateEmployeeName(name) {
505
+ if (!name) {
506
+ return { valid: false, error: "Name is required" };
507
+ }
508
+ if (name.length > 32) {
509
+ return { valid: false, error: "Name must be 32 characters or fewer" };
510
+ }
511
+ if (!/^[a-z][a-z0-9]*$/.test(name)) {
512
+ return {
513
+ valid: false,
514
+ error: "Name must start with a letter and contain only lowercase alphanumeric characters"
515
+ };
516
+ }
517
+ return { valid: true };
518
+ }
519
+ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
520
+ if (!existsSync4(employeesPath)) {
521
+ return [];
522
+ }
523
+ const raw = await readFile2(employeesPath, "utf-8");
524
+ try {
525
+ return JSON.parse(raw);
526
+ } catch {
527
+ return [];
528
+ }
529
+ }
530
+ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
531
+ await mkdir2(path3.dirname(employeesPath), { recursive: true });
532
+ await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
533
+ }
534
+ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
535
+ if (!existsSync4(employeesPath)) return [];
536
+ try {
537
+ return JSON.parse(readFileSync3(employeesPath, "utf-8"));
538
+ } catch {
539
+ return [];
540
+ }
541
+ }
542
+ function getEmployee(employees, name) {
543
+ return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
544
+ }
545
+ function getEmployeeByRole(employees, role) {
546
+ const lower = role.toLowerCase();
547
+ return employees.find((e) => e.role.toLowerCase() === lower);
548
+ }
549
+ function getEmployeeNamesByRole(employees, role) {
550
+ const lower = role.toLowerCase();
551
+ return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
552
+ }
553
+ function hasRole(agentName, role) {
554
+ const employees = loadEmployeesSync();
555
+ const emp = getEmployee(employees, agentName);
556
+ return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
557
+ }
558
+ function baseAgentName(name, employees) {
559
+ const match = name.match(/^([a-zA-Z]+)\d+$/);
560
+ if (!match) return name;
561
+ const base = match[1];
562
+ const roster = employees ?? loadEmployeesSync();
563
+ if (getEmployee(roster, base)) return base;
564
+ return name;
565
+ }
566
+ function isMultiInstance(agentName, employees) {
567
+ const roster = employees ?? loadEmployeesSync();
568
+ const emp = getEmployee(roster, agentName);
569
+ if (!emp) return false;
570
+ return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
571
+ }
572
+ function addEmployee(employees, employee) {
573
+ const { systemPrompt: _legacyPrompt, ...rest } = employee;
574
+ const normalized = { ...rest, name: employee.name.toLowerCase() };
575
+ if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
576
+ throw new Error(`Employee '${normalized.name}' already exists`);
577
+ }
578
+ return [...employees, normalized];
579
+ }
580
+ function appendToCoordinatorTeam(employee) {
581
+ const coordinator = getCoordinatorEmployee(loadEmployeesSync());
582
+ if (!coordinator) return;
583
+ const idPath = path3.join(IDENTITY_DIR, `${coordinator.name}.md`);
584
+ if (!existsSync4(idPath)) return;
585
+ const content = readFileSync3(idPath, "utf-8");
586
+ if (content.includes(`**${capitalize(employee.name)}`)) return;
587
+ const teamMatch = content.match(TEAM_SECTION_RE);
588
+ if (!teamMatch || teamMatch.index === void 0) return;
589
+ const afterTeam = content.slice(teamMatch.index + teamMatch[0].length);
590
+ const nextHeading = afterTeam.match(/\n## /);
591
+ const entry = `
592
+ **${capitalize(employee.name)} (${employee.role}):** Newly hired. Update this description as the role develops.
593
+ `;
594
+ let updated;
595
+ if (nextHeading && nextHeading.index !== void 0) {
596
+ const insertAt = teamMatch.index + teamMatch[0].length + nextHeading.index;
597
+ updated = content.slice(0, insertAt) + entry + content.slice(insertAt);
598
+ } else {
599
+ updated = content.trimEnd() + "\n" + entry;
600
+ }
601
+ writeFileSync2(idPath, updated, "utf-8");
602
+ }
603
+ function capitalize(s) {
604
+ return s.charAt(0).toUpperCase() + s.slice(1);
605
+ }
606
+ async function hireEmployee(employee) {
607
+ const employees = await loadEmployees();
608
+ const updated = addEmployee(employees, employee);
609
+ await saveEmployees(updated);
610
+ try {
611
+ appendToCoordinatorTeam(employee);
612
+ } catch {
613
+ }
614
+ try {
615
+ const { loadAgentConfig: loadAgentConfig2, saveAgentConfig: saveAgentConfig2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
616
+ const config = loadAgentConfig2();
617
+ const name = employee.name.toLowerCase();
618
+ if (!config[name] && config["default"]) {
619
+ config[name] = { ...config["default"] };
620
+ saveAgentConfig2(config);
621
+ }
622
+ } catch {
623
+ }
624
+ return updated;
625
+ }
626
+ async function normalizeRosterCase(rosterPath) {
627
+ const employees = await loadEmployees(rosterPath);
628
+ let changed = false;
629
+ for (const emp of employees) {
630
+ if (emp.name !== emp.name.toLowerCase()) {
631
+ const oldName = emp.name;
632
+ emp.name = emp.name.toLowerCase();
633
+ changed = true;
634
+ try {
635
+ const identityDir = path3.join(os2.homedir(), ".exe-os", "identity");
636
+ const oldPath = path3.join(identityDir, `${oldName}.md`);
637
+ const newPath = path3.join(identityDir, `${emp.name}.md`);
638
+ if (existsSync4(oldPath) && !existsSync4(newPath)) {
639
+ renameSync2(oldPath, newPath);
640
+ } else if (existsSync4(oldPath) && oldPath !== newPath) {
641
+ const content = readFileSync3(oldPath, "utf-8");
642
+ writeFileSync2(newPath, content, "utf-8");
643
+ if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
644
+ unlinkSync(oldPath);
645
+ }
646
+ }
647
+ } catch {
648
+ }
649
+ }
650
+ }
651
+ if (changed) {
652
+ await saveEmployees(employees, rosterPath);
653
+ }
654
+ return changed;
655
+ }
656
+ function findExeBin() {
657
+ try {
658
+ return execSync(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
659
+ } catch {
660
+ return null;
661
+ }
662
+ }
663
+ function registerBinSymlinks(name) {
664
+ const created = [];
665
+ const skipped = [];
666
+ const errors = [];
667
+ const exeBinPath = findExeBin();
668
+ if (!exeBinPath) {
669
+ errors.push("Could not find 'exe-os' in PATH");
670
+ return { created, skipped, errors };
671
+ }
672
+ const binDir = path3.dirname(exeBinPath);
673
+ let target;
674
+ try {
675
+ target = readlinkSync(exeBinPath);
676
+ } catch {
677
+ errors.push("Could not read 'exe' symlink");
678
+ return { created, skipped, errors };
679
+ }
680
+ for (const suffix of ["", "-opencode"]) {
681
+ const linkName = `${name}${suffix}`;
682
+ const linkPath = path3.join(binDir, linkName);
683
+ if (existsSync4(linkPath)) {
684
+ skipped.push(linkName);
685
+ continue;
686
+ }
687
+ try {
688
+ symlinkSync(target, linkPath);
689
+ created.push(linkName);
690
+ } catch (err) {
691
+ errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
692
+ }
693
+ }
694
+ return { created, skipped, errors };
695
+ }
696
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR, TEAM_SECTION_RE;
697
+ var init_employees = __esm({
698
+ "src/lib/employees.ts"() {
699
+ "use strict";
700
+ init_config();
701
+ EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
702
+ DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
703
+ COORDINATOR_ROLE = "COO";
704
+ MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
705
+ IDENTITY_DIR = path3.join(EXE_AI_DIR, "identity");
706
+ TEAM_SECTION_RE = /^## Team\b.*$/m;
707
+ }
708
+ });
709
+
316
710
  // src/types/memory.ts
317
711
  var EMBEDDING_DIM;
318
712
  var init_memory = __esm({
@@ -324,8 +718,8 @@ var init_memory = __esm({
324
718
 
325
719
  // src/lib/daemon-auth.ts
326
720
  import crypto from "crypto";
327
- import path4 from "path";
328
- import { existsSync as existsSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
721
+ import path5 from "path";
722
+ import { existsSync as existsSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
329
723
  function normalizeToken(token) {
330
724
  if (!token) return null;
331
725
  const trimmed = token.trim();
@@ -333,8 +727,8 @@ function normalizeToken(token) {
333
727
  }
334
728
  function readDaemonToken() {
335
729
  try {
336
- if (!existsSync5(DAEMON_TOKEN_PATH)) return null;
337
- return normalizeToken(readFileSync3(DAEMON_TOKEN_PATH, "utf8"));
730
+ if (!existsSync6(DAEMON_TOKEN_PATH)) return null;
731
+ return normalizeToken(readFileSync4(DAEMON_TOKEN_PATH, "utf8"));
338
732
  } catch {
339
733
  return null;
340
734
  }
@@ -344,7 +738,7 @@ function ensureDaemonToken(seed) {
344
738
  if (existing) return existing;
345
739
  const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
346
740
  ensurePrivateDirSync(EXE_AI_DIR);
347
- writeFileSync2(DAEMON_TOKEN_PATH, `${token}
741
+ writeFileSync3(DAEMON_TOKEN_PATH, `${token}
348
742
  `, "utf8");
349
743
  enforcePrivateFileSync(DAEMON_TOKEN_PATH);
350
744
  return token;
@@ -355,7 +749,7 @@ var init_daemon_auth = __esm({
355
749
  "use strict";
356
750
  init_config();
357
751
  init_secure_files();
358
- DAEMON_TOKEN_PATH = path4.join(EXE_AI_DIR, "exed.token");
752
+ DAEMON_TOKEN_PATH = path5.join(EXE_AI_DIR, "exed.token");
359
753
  }
360
754
  });
361
755
 
@@ -364,8 +758,8 @@ import net from "net";
364
758
  import os4 from "os";
365
759
  import { spawn, execSync as execSync2 } from "child_process";
366
760
  import { randomUUID } from "crypto";
367
- import { existsSync as existsSync6, unlinkSync as unlinkSync3, readFileSync as readFileSync4, openSync as openSync2, closeSync as closeSync2, statSync as statSync2 } from "fs";
368
- import path5 from "path";
761
+ import { existsSync as existsSync7, unlinkSync as unlinkSync3, readFileSync as readFileSync5, openSync as openSync2, closeSync as closeSync2, statSync as statSync2 } from "fs";
762
+ import path6 from "path";
369
763
  import { fileURLToPath } from "url";
370
764
  function handleData(chunk) {
371
765
  _buffer += chunk.toString();
@@ -401,9 +795,9 @@ function isZombie(pid) {
401
795
  }
402
796
  }
403
797
  function cleanupStaleFiles() {
404
- if (existsSync6(PID_PATH)) {
798
+ if (existsSync7(PID_PATH)) {
405
799
  try {
406
- const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
800
+ const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
407
801
  if (pid > 0) {
408
802
  try {
409
803
  process.kill(pid, 0);
@@ -428,11 +822,11 @@ function cleanupStaleFiles() {
428
822
  }
429
823
  }
430
824
  function findPackageRoot() {
431
- let dir = path5.dirname(fileURLToPath(import.meta.url));
432
- const { root } = path5.parse(dir);
825
+ let dir = path6.dirname(fileURLToPath(import.meta.url));
826
+ const { root } = path6.parse(dir);
433
827
  while (dir !== root) {
434
- if (existsSync6(path5.join(dir, "package.json"))) return dir;
435
- dir = path5.dirname(dir);
828
+ if (existsSync7(path6.join(dir, "package.json"))) return dir;
829
+ dir = path6.dirname(dir);
436
830
  }
437
831
  return null;
438
832
  }
@@ -450,8 +844,8 @@ function spawnDaemon() {
450
844
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
451
845
  return;
452
846
  }
453
- const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
454
- if (!existsSync6(daemonPath)) {
847
+ const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
848
+ if (!existsSync7(daemonPath)) {
455
849
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
456
850
  `);
457
851
  return;
@@ -460,7 +854,7 @@ function spawnDaemon() {
460
854
  const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
461
855
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
462
856
  `);
463
- const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
857
+ const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
464
858
  let stderrFd = "ignore";
465
859
  try {
466
860
  stderrFd = openSync2(logPath, "a");
@@ -625,9 +1019,9 @@ function killAndRespawnDaemon() {
625
1019
  }
626
1020
  try {
627
1021
  process.stderr.write("[exed-client] Killing daemon for restart...\n");
628
- if (existsSync6(PID_PATH)) {
1022
+ if (existsSync7(PID_PATH)) {
629
1023
  try {
630
- const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
1024
+ const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
631
1025
  if (pid > 0) {
632
1026
  try {
633
1027
  process.kill(pid, "SIGKILL");
@@ -750,9 +1144,9 @@ var init_exe_daemon_client = __esm({
750
1144
  "use strict";
751
1145
  init_config();
752
1146
  init_daemon_auth();
753
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path5.join(EXE_AI_DIR, "exed.sock");
754
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path5.join(EXE_AI_DIR, "exed.pid");
755
- SPAWN_LOCK_PATH = path5.join(EXE_AI_DIR, "exed-spawn.lock");
1147
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
1148
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
1149
+ SPAWN_LOCK_PATH = path6.join(EXE_AI_DIR, "exed-spawn.lock");
756
1150
  SPAWN_LOCK_STALE_MS = 3e4;
757
1151
  CONNECT_TIMEOUT_MS = 15e3;
758
1152
  REQUEST_TIMEOUT_MS = 3e4;
@@ -808,10 +1202,10 @@ async function disposeEmbedder() {
808
1202
  async function embedDirect(text) {
809
1203
  const llamaCpp = await import("node-llama-cpp");
810
1204
  const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
811
- const { existsSync: existsSync7 } = await import("fs");
812
- const path6 = await import("path");
813
- const modelPath = path6.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
814
- if (!existsSync7(modelPath)) {
1205
+ const { existsSync: existsSync9 } = await import("fs");
1206
+ const path8 = await import("path");
1207
+ const modelPath = path8.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
1208
+ if (!existsSync9(modelPath)) {
815
1209
  throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
816
1210
  }
817
1211
  const llama = await llamaCpp.getLlama();
@@ -839,28 +1233,486 @@ var init_embedder = __esm({
839
1233
  }
840
1234
  });
841
1235
 
1236
+ // src/lib/license.ts
1237
+ var license_exports = {};
1238
+ __export(license_exports, {
1239
+ LICENSE_PUBLIC_KEY_PEM: () => LICENSE_PUBLIC_KEY_PEM,
1240
+ PLAN_LIMITS: () => PLAN_LIMITS,
1241
+ assertVpsLicense: () => assertVpsLicense,
1242
+ checkLicense: () => checkLicense,
1243
+ getCachedLicense: () => getCachedLicense,
1244
+ isFeatureAllowed: () => isFeatureAllowed,
1245
+ loadDeviceId: () => loadDeviceId,
1246
+ loadLicense: () => loadLicense,
1247
+ mirrorLicenseKey: () => mirrorLicenseKey,
1248
+ readCachedLicenseToken: () => readCachedLicenseToken,
1249
+ saveLicense: () => saveLicense,
1250
+ startLicenseRevalidation: () => startLicenseRevalidation,
1251
+ stopLicenseRevalidation: () => stopLicenseRevalidation,
1252
+ validateLicense: () => validateLicense
1253
+ });
1254
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync8, mkdirSync as mkdirSync3 } from "fs";
1255
+ import { randomUUID as randomUUID2 } from "crypto";
1256
+ import { createRequire as createRequire2 } from "module";
1257
+ import { pathToFileURL as pathToFileURL2 } from "url";
1258
+ import os5 from "os";
1259
+ import path7 from "path";
1260
+ import { jwtVerify, importSPKI } from "jose";
1261
+ async function fetchRetry(url, init) {
1262
+ try {
1263
+ return await fetch(url, init);
1264
+ } catch {
1265
+ await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
1266
+ return fetch(url, { ...init, signal: AbortSignal.timeout(1e4) });
1267
+ }
1268
+ }
1269
+ function loadDeviceId() {
1270
+ const deviceJsonPath = path7.join(EXE_AI_DIR, "device.json");
1271
+ try {
1272
+ if (existsSync8(deviceJsonPath)) {
1273
+ const data = JSON.parse(readFileSync6(deviceJsonPath, "utf8"));
1274
+ if (data.deviceId) return data.deviceId;
1275
+ }
1276
+ } catch {
1277
+ }
1278
+ try {
1279
+ if (existsSync8(DEVICE_ID_PATH)) {
1280
+ const id2 = readFileSync6(DEVICE_ID_PATH, "utf8").trim();
1281
+ if (id2) return id2;
1282
+ }
1283
+ } catch {
1284
+ }
1285
+ const id = randomUUID2();
1286
+ mkdirSync3(EXE_AI_DIR, { recursive: true });
1287
+ writeFileSync4(DEVICE_ID_PATH, id, "utf8");
1288
+ return id;
1289
+ }
1290
+ function loadLicense() {
1291
+ try {
1292
+ if (!existsSync8(LICENSE_PATH)) return null;
1293
+ return readFileSync6(LICENSE_PATH, "utf8").trim();
1294
+ } catch {
1295
+ return null;
1296
+ }
1297
+ }
1298
+ function saveLicense(apiKey) {
1299
+ mkdirSync3(EXE_AI_DIR, { recursive: true });
1300
+ writeFileSync4(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
1301
+ }
1302
+ async function verifyLicenseJwt(token) {
1303
+ try {
1304
+ const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
1305
+ const { payload } = await jwtVerify(token, key, {
1306
+ algorithms: [LICENSE_JWT_ALG]
1307
+ });
1308
+ const plan = payload.plan ?? "free";
1309
+ const email = payload.sub ?? "";
1310
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
1311
+ return {
1312
+ valid: true,
1313
+ plan,
1314
+ email,
1315
+ expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
1316
+ deviceLimit: limits.devices,
1317
+ employeeLimit: limits.employees,
1318
+ memoryLimit: limits.memories
1319
+ };
1320
+ } catch {
1321
+ return null;
1322
+ }
1323
+ }
1324
+ async function getCachedLicense() {
1325
+ try {
1326
+ if (!existsSync8(CACHE_PATH)) return null;
1327
+ const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
1328
+ if (!raw.token || typeof raw.token !== "string") return null;
1329
+ return await verifyLicenseJwt(raw.token);
1330
+ } catch {
1331
+ return null;
1332
+ }
1333
+ }
1334
+ function readCachedLicenseToken() {
1335
+ try {
1336
+ if (!existsSync8(CACHE_PATH)) return null;
1337
+ const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
1338
+ return typeof raw.token === "string" ? raw.token : null;
1339
+ } catch {
1340
+ return null;
1341
+ }
1342
+ }
1343
+ function getRawCachedPlan() {
1344
+ try {
1345
+ const token = readCachedLicenseToken();
1346
+ if (!token) return null;
1347
+ const parts = token.split(".");
1348
+ if (parts.length !== 3) return null;
1349
+ const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
1350
+ const plan = payload.plan ?? "free";
1351
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
1352
+ process.stderr.write(
1353
+ `[license] WARN: using unverified cached plan (API unreachable, JWT expired). Plan: ${plan}
1354
+ `
1355
+ );
1356
+ return {
1357
+ valid: true,
1358
+ plan,
1359
+ email: payload.sub ?? "",
1360
+ expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
1361
+ deviceLimit: limits.devices,
1362
+ employeeLimit: limits.employees,
1363
+ memoryLimit: limits.memories
1364
+ };
1365
+ } catch {
1366
+ return null;
1367
+ }
1368
+ }
1369
+ function cacheResponse(token) {
1370
+ try {
1371
+ writeFileSync4(CACHE_PATH, JSON.stringify({ token }), "utf8");
1372
+ } catch {
1373
+ }
1374
+ }
1375
+ function loadPrismaForLicense() {
1376
+ if (_prismaFailed) return null;
1377
+ const dbUrl = process.env.DATABASE_URL;
1378
+ if (!dbUrl) {
1379
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path7.join(os5.homedir(), "exe-db");
1380
+ if (!existsSync8(path7.join(exeDbRoot, "package.json"))) {
1381
+ _prismaFailed = true;
1382
+ return null;
1383
+ }
1384
+ }
1385
+ if (!_prismaPromise) {
1386
+ _prismaPromise = (async () => {
1387
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
1388
+ if (explicitPath) {
1389
+ const mod2 = await import(pathToFileURL2(explicitPath).href);
1390
+ const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
1391
+ if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
1392
+ return new Ctor2();
1393
+ }
1394
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path7.join(os5.homedir(), "exe-db");
1395
+ const req = createRequire2(path7.join(exeDbRoot, "package.json"));
1396
+ const entry = req.resolve("@prisma/client");
1397
+ const mod = await import(pathToFileURL2(entry).href);
1398
+ const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
1399
+ if (!Ctor) throw new Error(`No PrismaClient in ${entry}`);
1400
+ return new Ctor();
1401
+ })().catch((err) => {
1402
+ _prismaFailed = true;
1403
+ _prismaPromise = null;
1404
+ throw err;
1405
+ });
1406
+ }
1407
+ return _prismaPromise;
1408
+ }
1409
+ async function validateViaPostgres(apiKey) {
1410
+ const loader = loadPrismaForLicense();
1411
+ if (!loader) return null;
1412
+ try {
1413
+ const prisma = await loader;
1414
+ const rows = await prisma.$queryRawUnsafe(
1415
+ `SELECT plan, email, status, device_limit, employee_limit, memory_limit, expires_at
1416
+ FROM billing.licenses WHERE key = $1 LIMIT 1`,
1417
+ apiKey
1418
+ );
1419
+ if (!rows || rows.length === 0) return null;
1420
+ const row = rows[0];
1421
+ if (row.status !== "active") return null;
1422
+ if (row.expires_at && new Date(row.expires_at) < /* @__PURE__ */ new Date()) return null;
1423
+ const plan = row.plan;
1424
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
1425
+ return {
1426
+ valid: true,
1427
+ plan,
1428
+ email: row.email,
1429
+ expiresAt: row.expires_at ? new Date(row.expires_at).toISOString() : null,
1430
+ deviceLimit: row.device_limit ?? limits.devices,
1431
+ employeeLimit: row.employee_limit ?? limits.employees,
1432
+ memoryLimit: row.memory_limit ?? limits.memories
1433
+ };
1434
+ } catch {
1435
+ return null;
1436
+ }
1437
+ }
1438
+ async function validateViaCFWorker(apiKey, deviceId) {
1439
+ try {
1440
+ const res = await fetchRetry(`${API_BASE}/auth/activate`, {
1441
+ method: "POST",
1442
+ headers: { "Content-Type": "application/json" },
1443
+ body: JSON.stringify({ apiKey, deviceId }),
1444
+ signal: AbortSignal.timeout(1e4)
1445
+ });
1446
+ if (!res.ok) return null;
1447
+ const data = await res.json();
1448
+ if (data.error === "device_limit_exceeded") return null;
1449
+ if (!data.valid) return null;
1450
+ if (data.token) {
1451
+ cacheResponse(data.token);
1452
+ const verified = await verifyLicenseJwt(data.token);
1453
+ if (verified) return verified;
1454
+ }
1455
+ const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
1456
+ return {
1457
+ valid: data.valid,
1458
+ plan: data.plan,
1459
+ email: data.email,
1460
+ expiresAt: data.expiresAt,
1461
+ deviceLimit: limits.devices,
1462
+ employeeLimit: limits.employees,
1463
+ memoryLimit: limits.memories
1464
+ };
1465
+ } catch {
1466
+ return null;
1467
+ }
1468
+ }
1469
+ async function validateLicense(apiKey, deviceId) {
1470
+ const did = deviceId ?? loadDeviceId();
1471
+ const pgResult = await validateViaPostgres(apiKey);
1472
+ if (pgResult) {
1473
+ try {
1474
+ writeFileSync4(CACHE_PATH, JSON.stringify({ pgLicense: pgResult, ts: Date.now() }), "utf8");
1475
+ } catch {
1476
+ }
1477
+ return pgResult;
1478
+ }
1479
+ const cfResult = await validateViaCFWorker(apiKey, did);
1480
+ if (cfResult) return cfResult;
1481
+ const cached = await getCachedLicense();
1482
+ if (cached) return cached;
1483
+ try {
1484
+ if (existsSync8(CACHE_PATH)) {
1485
+ const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
1486
+ if (raw.pgLicense && raw.ts && Date.now() - raw.ts < 7 * 24 * 60 * 60 * 1e3) {
1487
+ return raw.pgLicense;
1488
+ }
1489
+ }
1490
+ } catch {
1491
+ }
1492
+ const rawFallback = getRawCachedPlan();
1493
+ if (rawFallback) return rawFallback;
1494
+ return { ...FREE_LICENSE, valid: false };
1495
+ }
1496
+ function getCacheAgeMs() {
1497
+ try {
1498
+ const { statSync: statSync3 } = __require("fs");
1499
+ const s = statSync3(CACHE_PATH);
1500
+ return Date.now() - s.mtimeMs;
1501
+ } catch {
1502
+ return Infinity;
1503
+ }
1504
+ }
1505
+ async function checkLicense() {
1506
+ let key = loadLicense();
1507
+ if (!key) {
1508
+ try {
1509
+ const configPath = path7.join(EXE_AI_DIR, "config.json");
1510
+ if (existsSync8(configPath)) {
1511
+ const raw = JSON.parse(readFileSync6(configPath, "utf8"));
1512
+ const cloud = raw.cloud;
1513
+ if (cloud?.apiKey) {
1514
+ key = cloud.apiKey;
1515
+ saveLicense(key);
1516
+ }
1517
+ }
1518
+ } catch {
1519
+ }
1520
+ }
1521
+ if (!key) return FREE_LICENSE;
1522
+ const cached = await getCachedLicense();
1523
+ if (cached && getCacheAgeMs() < CACHE_MAX_AGE_MS) return cached;
1524
+ const deviceId = loadDeviceId();
1525
+ return validateLicense(key, deviceId);
1526
+ }
1527
+ function isFeatureAllowed(license, feature) {
1528
+ switch (feature) {
1529
+ case "cloud_sync":
1530
+ case "external_agents":
1531
+ case "wiki":
1532
+ return license.plan !== "free";
1533
+ case "unlimited_employees":
1534
+ return license.plan === "team" || license.plan === "agency" || license.plan === "enterprise";
1535
+ }
1536
+ }
1537
+ function mirrorLicenseKey(apiKey) {
1538
+ const trimmed = apiKey.trim();
1539
+ if (!trimmed) return;
1540
+ saveLicense(trimmed);
1541
+ }
1542
+ async function assertVpsLicense(opts) {
1543
+ const env = opts?.env ?? process.env;
1544
+ const inProduction = env.NODE_ENV === "production";
1545
+ if (!opts?.force && !inProduction) {
1546
+ return { ...FREE_LICENSE, plan: "free" };
1547
+ }
1548
+ const envKey = env.EXE_LICENSE_KEY?.trim();
1549
+ if (envKey) {
1550
+ saveLicense(envKey);
1551
+ }
1552
+ const apiKey = envKey ?? loadLicense();
1553
+ if (!apiKey) {
1554
+ throw new Error(
1555
+ "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."
1556
+ );
1557
+ }
1558
+ const deviceId = loadDeviceId();
1559
+ let backendResponse = null;
1560
+ let explicitRejection = false;
1561
+ let transientFailure = false;
1562
+ try {
1563
+ const res = await fetchRetry(`${API_BASE}/auth/activate`, {
1564
+ method: "POST",
1565
+ headers: { "Content-Type": "application/json" },
1566
+ body: JSON.stringify({ apiKey, deviceId }),
1567
+ signal: AbortSignal.timeout(1e4)
1568
+ });
1569
+ if (res.ok) {
1570
+ backendResponse = await res.json();
1571
+ if (!backendResponse.valid) explicitRejection = true;
1572
+ } else if (res.status === 401 || res.status === 403) {
1573
+ explicitRejection = true;
1574
+ } else {
1575
+ transientFailure = true;
1576
+ }
1577
+ } catch {
1578
+ transientFailure = true;
1579
+ }
1580
+ if (backendResponse?.valid) {
1581
+ if (backendResponse.token) {
1582
+ cacheResponse(backendResponse.token);
1583
+ const verified = await verifyLicenseJwt(backendResponse.token);
1584
+ if (verified) return verified;
1585
+ }
1586
+ const limits = PLAN_LIMITS[backendResponse.plan] ?? PLAN_LIMITS.free;
1587
+ return {
1588
+ valid: true,
1589
+ plan: backendResponse.plan,
1590
+ email: backendResponse.email,
1591
+ expiresAt: backendResponse.expiresAt,
1592
+ deviceLimit: limits.devices,
1593
+ employeeLimit: limits.employees,
1594
+ memoryLimit: limits.memories
1595
+ };
1596
+ }
1597
+ if (explicitRejection) {
1598
+ throw new Error(
1599
+ `License invalid or expired. Renew at https://askexe.com, then restart. Backend rejected key ending in ...${apiKey.slice(-4)}.`
1600
+ );
1601
+ }
1602
+ if (!transientFailure) {
1603
+ throw new Error(
1604
+ "License validation failed: unknown backend state. Restore network connectivity to https://cloud.askexe.com and retry."
1605
+ );
1606
+ }
1607
+ const fresh = await getCachedLicense();
1608
+ if (fresh && fresh.valid) return fresh;
1609
+ const graceDays = opts?.offlineGraceDays ?? 7;
1610
+ const graceMs = graceDays * 24 * 60 * 60 * 1e3;
1611
+ try {
1612
+ const token = readCachedLicenseToken();
1613
+ if (token) {
1614
+ const payloadB64 = token.split(".")[1];
1615
+ if (payloadB64) {
1616
+ const payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString());
1617
+ const expMs = (payload.exp ?? 0) * 1e3;
1618
+ if (Date.now() < expMs + graceMs) {
1619
+ const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
1620
+ const { payload: verified } = await jwtVerify(token, key, {
1621
+ algorithms: [LICENSE_JWT_ALG],
1622
+ clockTolerance: graceDays * 24 * 60 * 60
1623
+ });
1624
+ const plan = verified.plan ?? "free";
1625
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
1626
+ return {
1627
+ valid: true,
1628
+ plan,
1629
+ email: verified.sub ?? "",
1630
+ expiresAt: verified.exp ? new Date(verified.exp * 1e3).toISOString() : null,
1631
+ deviceLimit: limits.devices,
1632
+ employeeLimit: limits.employees,
1633
+ memoryLimit: limits.memories
1634
+ };
1635
+ }
1636
+ }
1637
+ }
1638
+ } catch {
1639
+ }
1640
+ throw new Error(
1641
+ `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.`
1642
+ );
1643
+ }
1644
+ function startLicenseRevalidation(intervalMs = 36e5) {
1645
+ if (_revalTimer) return;
1646
+ _revalTimer = setInterval(async () => {
1647
+ try {
1648
+ const license = await checkLicense();
1649
+ if (!license.valid) {
1650
+ process.stderr.write("[exe-os] License expired or invalid \u2014 features may be restricted\n");
1651
+ }
1652
+ } catch {
1653
+ }
1654
+ }, intervalMs);
1655
+ if (_revalTimer && typeof _revalTimer === "object" && "unref" in _revalTimer) {
1656
+ _revalTimer.unref();
1657
+ }
1658
+ }
1659
+ function stopLicenseRevalidation() {
1660
+ if (_revalTimer) {
1661
+ clearInterval(_revalTimer);
1662
+ _revalTimer = null;
1663
+ }
1664
+ }
1665
+ 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;
1666
+ var init_license = __esm({
1667
+ "src/lib/license.ts"() {
1668
+ "use strict";
1669
+ init_config();
1670
+ LICENSE_PATH = path7.join(EXE_AI_DIR, "license.key");
1671
+ CACHE_PATH = path7.join(EXE_AI_DIR, "license-cache.json");
1672
+ DEVICE_ID_PATH = path7.join(EXE_AI_DIR, "device-id");
1673
+ API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://cloud.askexe.com";
1674
+ RETRY_DELAY_MS = 500;
1675
+ LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
1676
+ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
1677
+ 4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
1678
+ -----END PUBLIC KEY-----`;
1679
+ LICENSE_JWT_ALG = "ES256";
1680
+ PLAN_LIMITS = {
1681
+ free: { devices: 1, employees: 1, memories: 5e3 },
1682
+ pro: { devices: 3, employees: 5, memories: 1e5 },
1683
+ team: { devices: 10, employees: 20, memories: 1e6 },
1684
+ agency: { devices: 50, employees: 100, memories: 1e7 },
1685
+ enterprise: { devices: -1, employees: -1, memories: -1 }
1686
+ };
1687
+ FREE_LICENSE = {
1688
+ valid: true,
1689
+ plan: "free",
1690
+ email: "",
1691
+ expiresAt: null,
1692
+ deviceLimit: 1,
1693
+ employeeLimit: 1,
1694
+ memoryLimit: 5e3
1695
+ };
1696
+ _prismaPromise = null;
1697
+ _prismaFailed = false;
1698
+ CACHE_MAX_AGE_MS = 36e5;
1699
+ _revalTimer = null;
1700
+ }
1701
+ });
1702
+
842
1703
  // src/lib/skill-learning.ts
843
1704
  import crypto3 from "crypto";
844
1705
 
845
1706
  // src/lib/database.ts
846
- import { chmodSync as chmodSync2, existsSync as existsSync4, statSync, copyFileSync, unlinkSync as unlinkSync2, openSync, closeSync, mkdirSync as mkdirSync2 } from "fs";
1707
+ import { chmodSync as chmodSync2, existsSync as existsSync5, statSync, copyFileSync, unlinkSync as unlinkSync2, openSync, closeSync, mkdirSync as mkdirSync2 } from "fs";
847
1708
  import { createClient } from "@libsql/client";
848
1709
  import { homedir } from "os";
849
1710
  import { join } from "path";
850
-
851
- // src/lib/employees.ts
852
- init_config();
853
- import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
854
- import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
855
- import { execSync } from "child_process";
856
- import path2 from "path";
857
- import os2 from "os";
858
- var EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
859
- var IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
1711
+ init_employees();
860
1712
 
861
1713
  // src/lib/database-adapter.ts
862
1714
  import os3 from "os";
863
- import path3 from "path";
1715
+ import path4 from "path";
864
1716
  import { createRequire } from "module";
865
1717
  import { pathToFileURL } from "url";
866
1718
  var BOOLEAN_COLUMNS_BY_TABLE = {
@@ -916,6 +1768,15 @@ function getClient() {
916
1768
  // src/lib/behaviors.ts
917
1769
  import crypto2 from "crypto";
918
1770
  async function storeBehavior(opts) {
1771
+ try {
1772
+ const { loadEmployeesSync: loadEmployeesSync2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
1773
+ const roster = loadEmployeesSync2();
1774
+ if (roster.length > 0 && !roster.some((e) => e.name === opts.agentId)) {
1775
+ throw new Error(`Agent "${opts.agentId}" not found in roster. Cannot store behavior for unregistered agent.`);
1776
+ }
1777
+ } catch (e) {
1778
+ if (e instanceof Error && e.message.includes("not found in roster")) throw e;
1779
+ }
919
1780
  const client = getClient();
920
1781
  const id = crypto2.randomUUID();
921
1782
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -926,10 +1787,18 @@ async function storeBehavior(opts) {
926
1787
  vector = new Float32Array(vec);
927
1788
  } catch {
928
1789
  }
1790
+ let createdByDevice = null;
1791
+ try {
1792
+ const { loadDeviceId: loadDeviceId2 } = await Promise.resolve().then(() => (init_license(), license_exports));
1793
+ createdByDevice = loadDeviceId2() ?? null;
1794
+ } catch {
1795
+ }
1796
+ const createdByAgent = process.env.AGENT_ID ?? null;
1797
+ const sourceSessionId = process.env.CLAUDE_SESSION_ID ?? process.env.SESSION_ID ?? null;
929
1798
  await client.execute({
930
- sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at, vector)
931
- VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?)`,
932
- args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now, vector ? vector.buffer : null]
1799
+ 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)
1800
+ VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?)`,
1801
+ args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now, vector ? vector.buffer : null, createdByAgent, createdByDevice, sourceSessionId]
933
1802
  });
934
1803
  return id;
935
1804
  }