@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
@@ -398,11 +398,168 @@ var init_session_key = __esm({
398
398
  }
399
399
  });
400
400
 
401
+ // src/lib/runtime-table.ts
402
+ var RUNTIME_TABLE, DEFAULT_RUNTIME;
403
+ var init_runtime_table = __esm({
404
+ "src/lib/runtime-table.ts"() {
405
+ "use strict";
406
+ RUNTIME_TABLE = {
407
+ codex: {
408
+ binary: "codex",
409
+ launchMode: "interactive",
410
+ autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
411
+ inlineFlag: "--no-alt-screen",
412
+ apiKeyEnv: "OPENAI_API_KEY",
413
+ defaultModel: "gpt-5.5"
414
+ },
415
+ opencode: {
416
+ binary: "opencode",
417
+ launchMode: "exec",
418
+ autoApproveFlag: "--dangerously-skip-permissions",
419
+ inlineFlag: "",
420
+ apiKeyEnv: "ANTHROPIC_API_KEY",
421
+ defaultModel: "anthropic/claude-sonnet-4-6"
422
+ }
423
+ };
424
+ DEFAULT_RUNTIME = "claude";
425
+ }
426
+ });
427
+
428
+ // src/lib/agent-config.ts
429
+ var agent_config_exports = {};
430
+ __export(agent_config_exports, {
431
+ AGENT_CONFIG_PATH: () => AGENT_CONFIG_PATH,
432
+ DEFAULT_MODELS: () => DEFAULT_MODELS,
433
+ KNOWN_RUNTIMES: () => KNOWN_RUNTIMES,
434
+ RUNTIME_LABELS: () => RUNTIME_LABELS,
435
+ clearAgentRuntime: () => clearAgentRuntime,
436
+ getAgentRuntime: () => getAgentRuntime,
437
+ loadAgentConfig: () => loadAgentConfig,
438
+ saveAgentConfig: () => saveAgentConfig,
439
+ setAgentMcps: () => setAgentMcps,
440
+ setAgentRuntime: () => setAgentRuntime
441
+ });
442
+ import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
443
+ import path2 from "path";
444
+ function loadAgentConfig() {
445
+ if (!existsSync3(AGENT_CONFIG_PATH)) return {};
446
+ try {
447
+ return JSON.parse(readFileSync2(AGENT_CONFIG_PATH, "utf-8"));
448
+ } catch {
449
+ return {};
450
+ }
451
+ }
452
+ function saveAgentConfig(config) {
453
+ const dir = path2.dirname(AGENT_CONFIG_PATH);
454
+ ensurePrivateDirSync(dir);
455
+ writeFileSync(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
456
+ enforcePrivateFileSync(AGENT_CONFIG_PATH);
457
+ }
458
+ function getAgentRuntime(agentId) {
459
+ const config = loadAgentConfig();
460
+ const entry = config[agentId];
461
+ if (entry) return entry;
462
+ const orgDefault = config["default"];
463
+ if (orgDefault) return orgDefault;
464
+ return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
465
+ }
466
+ function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
467
+ const knownModels = KNOWN_RUNTIMES[runtime];
468
+ if (!knownModels) {
469
+ return {
470
+ ok: false,
471
+ error: `Unknown runtime "${runtime}". Valid: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`
472
+ };
473
+ }
474
+ if (!knownModels.includes(model)) {
475
+ return {
476
+ ok: false,
477
+ error: `Unknown model "${model}" for runtime "${runtime}". Valid: ${knownModels.join(", ")}`
478
+ };
479
+ }
480
+ const config = loadAgentConfig();
481
+ const existing = config[agentId];
482
+ const entry = { runtime, model };
483
+ if (reasoning_effort) entry.reasoning_effort = reasoning_effort;
484
+ if (mcps !== void 0) {
485
+ entry.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
486
+ } else if (existing?.mcps) {
487
+ entry.mcps = existing.mcps;
488
+ }
489
+ config[agentId] = entry;
490
+ saveAgentConfig(config);
491
+ return { ok: true };
492
+ }
493
+ function setAgentMcps(agentId, mcps) {
494
+ const config = loadAgentConfig();
495
+ const existing = config[agentId] ?? getAgentRuntime(agentId);
496
+ existing.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
497
+ config[agentId] = existing;
498
+ saveAgentConfig(config);
499
+ return { ok: true };
500
+ }
501
+ function clearAgentRuntime(agentId) {
502
+ const config = loadAgentConfig();
503
+ delete config[agentId];
504
+ saveAgentConfig(config);
505
+ }
506
+ var AGENT_CONFIG_PATH, KNOWN_RUNTIMES, RUNTIME_LABELS, DEFAULT_MODELS;
507
+ var init_agent_config = __esm({
508
+ "src/lib/agent-config.ts"() {
509
+ "use strict";
510
+ init_config();
511
+ init_runtime_table();
512
+ init_secure_files();
513
+ AGENT_CONFIG_PATH = path2.join(EXE_AI_DIR, "agent-config.json");
514
+ KNOWN_RUNTIMES = {
515
+ claude: ["claude-opus-4.6", "claude-opus-4", "claude-sonnet-4.6", "claude-sonnet-4", "claude-haiku-4.5"],
516
+ codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
517
+ opencode: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4", "google/gemini-2.5-pro", "deepseek/deepseek-r3", "minimax/minimax-m2.5"]
518
+ };
519
+ RUNTIME_LABELS = {
520
+ claude: "Claude Code (Anthropic)",
521
+ codex: "Codex (OpenAI)",
522
+ opencode: "OpenCode (open source)"
523
+ };
524
+ DEFAULT_MODELS = {
525
+ claude: "claude-opus-4.6",
526
+ codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
527
+ opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
528
+ };
529
+ }
530
+ });
531
+
401
532
  // src/lib/employees.ts
533
+ var employees_exports = {};
534
+ __export(employees_exports, {
535
+ COORDINATOR_ROLE: () => COORDINATOR_ROLE,
536
+ DEFAULT_COORDINATOR_TEMPLATE_NAME: () => DEFAULT_COORDINATOR_TEMPLATE_NAME,
537
+ EMPLOYEES_PATH: () => EMPLOYEES_PATH,
538
+ addEmployee: () => addEmployee,
539
+ baseAgentName: () => baseAgentName,
540
+ canCoordinate: () => canCoordinate,
541
+ getCoordinatorEmployee: () => getCoordinatorEmployee,
542
+ getCoordinatorName: () => getCoordinatorName,
543
+ getEmployee: () => getEmployee,
544
+ getEmployeeByRole: () => getEmployeeByRole,
545
+ getEmployeeNamesByRole: () => getEmployeeNamesByRole,
546
+ hasRole: () => hasRole,
547
+ hireEmployee: () => hireEmployee,
548
+ isCoordinatorName: () => isCoordinatorName,
549
+ isCoordinatorRole: () => isCoordinatorRole,
550
+ isMultiInstance: () => isMultiInstance,
551
+ loadEmployees: () => loadEmployees,
552
+ loadEmployeesSync: () => loadEmployeesSync,
553
+ normalizeRole: () => normalizeRole,
554
+ normalizeRosterCase: () => normalizeRosterCase,
555
+ registerBinSymlinks: () => registerBinSymlinks,
556
+ saveEmployees: () => saveEmployees,
557
+ validateEmployeeName: () => validateEmployeeName
558
+ });
402
559
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
403
- import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
560
+ import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
404
561
  import { execSync as execSync2 } from "child_process";
405
- import path2 from "path";
562
+ import path3 from "path";
406
563
  import os2 from "os";
407
564
  function normalizeRole(role) {
408
565
  return (role ?? "").trim().toLowerCase();
@@ -423,8 +580,23 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
423
580
  function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
424
581
  return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
425
582
  }
583
+ function validateEmployeeName(name) {
584
+ if (!name) {
585
+ return { valid: false, error: "Name is required" };
586
+ }
587
+ if (name.length > 32) {
588
+ return { valid: false, error: "Name must be 32 characters or fewer" };
589
+ }
590
+ if (!/^[a-z][a-z0-9]*$/.test(name)) {
591
+ return {
592
+ valid: false,
593
+ error: "Name must start with a letter and contain only lowercase alphanumeric characters"
594
+ };
595
+ }
596
+ return { valid: true };
597
+ }
426
598
  async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
427
- if (!existsSync3(employeesPath)) {
599
+ if (!existsSync4(employeesPath)) {
428
600
  return [];
429
601
  }
430
602
  const raw = await readFile2(employeesPath, "utf-8");
@@ -434,10 +606,14 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
434
606
  return [];
435
607
  }
436
608
  }
609
+ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
610
+ await mkdir2(path3.dirname(employeesPath), { recursive: true });
611
+ await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
612
+ }
437
613
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
438
- if (!existsSync3(employeesPath)) return [];
614
+ if (!existsSync4(employeesPath)) return [];
439
615
  try {
440
- return JSON.parse(readFileSync2(employeesPath, "utf-8"));
616
+ return JSON.parse(readFileSync3(employeesPath, "utf-8"));
441
617
  } catch {
442
618
  return [];
443
619
  }
@@ -445,6 +621,19 @@ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
445
621
  function getEmployee(employees, name) {
446
622
  return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
447
623
  }
624
+ function getEmployeeByRole(employees, role) {
625
+ const lower = role.toLowerCase();
626
+ return employees.find((e) => e.role.toLowerCase() === lower);
627
+ }
628
+ function getEmployeeNamesByRole(employees, role) {
629
+ const lower = role.toLowerCase();
630
+ return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
631
+ }
632
+ function hasRole(agentName, role) {
633
+ const employees = loadEmployeesSync();
634
+ const emp = getEmployee(employees, agentName);
635
+ return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
636
+ }
448
637
  function baseAgentName(name, employees) {
449
638
  const match = name.match(/^([a-zA-Z]+)\d+$/);
450
639
  if (!match) return name;
@@ -459,26 +648,151 @@ function isMultiInstance(agentName, employees) {
459
648
  if (!emp) return false;
460
649
  return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
461
650
  }
462
- var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR;
651
+ function addEmployee(employees, employee) {
652
+ const { systemPrompt: _legacyPrompt, ...rest } = employee;
653
+ const normalized = { ...rest, name: employee.name.toLowerCase() };
654
+ if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
655
+ throw new Error(`Employee '${normalized.name}' already exists`);
656
+ }
657
+ return [...employees, normalized];
658
+ }
659
+ function appendToCoordinatorTeam(employee) {
660
+ const coordinator = getCoordinatorEmployee(loadEmployeesSync());
661
+ if (!coordinator) return;
662
+ const idPath = path3.join(IDENTITY_DIR, `${coordinator.name}.md`);
663
+ if (!existsSync4(idPath)) return;
664
+ const content = readFileSync3(idPath, "utf-8");
665
+ if (content.includes(`**${capitalize(employee.name)}`)) return;
666
+ const teamMatch = content.match(TEAM_SECTION_RE);
667
+ if (!teamMatch || teamMatch.index === void 0) return;
668
+ const afterTeam = content.slice(teamMatch.index + teamMatch[0].length);
669
+ const nextHeading = afterTeam.match(/\n## /);
670
+ const entry = `
671
+ **${capitalize(employee.name)} (${employee.role}):** Newly hired. Update this description as the role develops.
672
+ `;
673
+ let updated;
674
+ if (nextHeading && nextHeading.index !== void 0) {
675
+ const insertAt = teamMatch.index + teamMatch[0].length + nextHeading.index;
676
+ updated = content.slice(0, insertAt) + entry + content.slice(insertAt);
677
+ } else {
678
+ updated = content.trimEnd() + "\n" + entry;
679
+ }
680
+ writeFileSync2(idPath, updated, "utf-8");
681
+ }
682
+ function capitalize(s) {
683
+ return s.charAt(0).toUpperCase() + s.slice(1);
684
+ }
685
+ async function hireEmployee(employee) {
686
+ const employees = await loadEmployees();
687
+ const updated = addEmployee(employees, employee);
688
+ await saveEmployees(updated);
689
+ try {
690
+ appendToCoordinatorTeam(employee);
691
+ } catch {
692
+ }
693
+ try {
694
+ const { loadAgentConfig: loadAgentConfig2, saveAgentConfig: saveAgentConfig2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
695
+ const config = loadAgentConfig2();
696
+ const name = employee.name.toLowerCase();
697
+ if (!config[name] && config["default"]) {
698
+ config[name] = { ...config["default"] };
699
+ saveAgentConfig2(config);
700
+ }
701
+ } catch {
702
+ }
703
+ return updated;
704
+ }
705
+ async function normalizeRosterCase(rosterPath) {
706
+ const employees = await loadEmployees(rosterPath);
707
+ let changed = false;
708
+ for (const emp of employees) {
709
+ if (emp.name !== emp.name.toLowerCase()) {
710
+ const oldName = emp.name;
711
+ emp.name = emp.name.toLowerCase();
712
+ changed = true;
713
+ try {
714
+ const identityDir = path3.join(os2.homedir(), ".exe-os", "identity");
715
+ const oldPath = path3.join(identityDir, `${oldName}.md`);
716
+ const newPath = path3.join(identityDir, `${emp.name}.md`);
717
+ if (existsSync4(oldPath) && !existsSync4(newPath)) {
718
+ renameSync2(oldPath, newPath);
719
+ } else if (existsSync4(oldPath) && oldPath !== newPath) {
720
+ const content = readFileSync3(oldPath, "utf-8");
721
+ writeFileSync2(newPath, content, "utf-8");
722
+ if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
723
+ unlinkSync(oldPath);
724
+ }
725
+ }
726
+ } catch {
727
+ }
728
+ }
729
+ }
730
+ if (changed) {
731
+ await saveEmployees(employees, rosterPath);
732
+ }
733
+ return changed;
734
+ }
735
+ function findExeBin() {
736
+ try {
737
+ return execSync2(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
738
+ } catch {
739
+ return null;
740
+ }
741
+ }
742
+ function registerBinSymlinks(name) {
743
+ const created = [];
744
+ const skipped = [];
745
+ const errors = [];
746
+ const exeBinPath = findExeBin();
747
+ if (!exeBinPath) {
748
+ errors.push("Could not find 'exe-os' in PATH");
749
+ return { created, skipped, errors };
750
+ }
751
+ const binDir = path3.dirname(exeBinPath);
752
+ let target;
753
+ try {
754
+ target = readlinkSync(exeBinPath);
755
+ } catch {
756
+ errors.push("Could not read 'exe' symlink");
757
+ return { created, skipped, errors };
758
+ }
759
+ for (const suffix of ["", "-opencode"]) {
760
+ const linkName = `${name}${suffix}`;
761
+ const linkPath = path3.join(binDir, linkName);
762
+ if (existsSync4(linkPath)) {
763
+ skipped.push(linkName);
764
+ continue;
765
+ }
766
+ try {
767
+ symlinkSync(target, linkPath);
768
+ created.push(linkName);
769
+ } catch (err) {
770
+ errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
771
+ }
772
+ }
773
+ return { created, skipped, errors };
774
+ }
775
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR, TEAM_SECTION_RE;
463
776
  var init_employees = __esm({
464
777
  "src/lib/employees.ts"() {
465
778
  "use strict";
466
779
  init_config();
467
- EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
780
+ EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
468
781
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
469
782
  COORDINATOR_ROLE = "COO";
470
783
  MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
471
- IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
784
+ IDENTITY_DIR = path3.join(EXE_AI_DIR, "identity");
785
+ TEAM_SECTION_RE = /^## Team\b.*$/m;
472
786
  }
473
787
  });
474
788
 
475
789
  // src/lib/session-registry.ts
476
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, existsSync as existsSync5 } from "fs";
477
- import path5 from "path";
790
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, existsSync as existsSync6 } from "fs";
791
+ import path6 from "path";
478
792
  import os4 from "os";
479
793
  function registerSession(entry) {
480
- const dir = path5.dirname(REGISTRY_PATH);
481
- if (!existsSync5(dir)) {
794
+ const dir = path6.dirname(REGISTRY_PATH);
795
+ if (!existsSync6(dir)) {
482
796
  mkdirSync4(dir, { recursive: true });
483
797
  }
484
798
  const sessions = listSessions();
@@ -488,11 +802,11 @@ function registerSession(entry) {
488
802
  } else {
489
803
  sessions.push(entry);
490
804
  }
491
- writeFileSync4(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
805
+ writeFileSync5(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
492
806
  }
493
807
  function listSessions() {
494
808
  try {
495
- const raw = readFileSync5(REGISTRY_PATH, "utf8");
809
+ const raw = readFileSync6(REGISTRY_PATH, "utf8");
496
810
  return JSON.parse(raw);
497
811
  } catch {
498
812
  return [];
@@ -502,7 +816,7 @@ var REGISTRY_PATH;
502
816
  var init_session_registry = __esm({
503
817
  "src/lib/session-registry.ts"() {
504
818
  "use strict";
505
- REGISTRY_PATH = path5.join(os4.homedir(), ".exe-os", "session-registry.json");
819
+ REGISTRY_PATH = path6.join(os4.homedir(), ".exe-os", "session-registry.json");
506
820
  }
507
821
  });
508
822
 
@@ -696,68 +1010,6 @@ var init_provider_table = __esm({
696
1010
  }
697
1011
  });
698
1012
 
699
- // src/lib/runtime-table.ts
700
- var RUNTIME_TABLE, DEFAULT_RUNTIME;
701
- var init_runtime_table = __esm({
702
- "src/lib/runtime-table.ts"() {
703
- "use strict";
704
- RUNTIME_TABLE = {
705
- codex: {
706
- binary: "codex",
707
- launchMode: "interactive",
708
- autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
709
- inlineFlag: "--no-alt-screen",
710
- apiKeyEnv: "OPENAI_API_KEY",
711
- defaultModel: "gpt-5.5"
712
- },
713
- opencode: {
714
- binary: "opencode",
715
- launchMode: "exec",
716
- autoApproveFlag: "--dangerously-skip-permissions",
717
- inlineFlag: "",
718
- apiKeyEnv: "ANTHROPIC_API_KEY",
719
- defaultModel: "anthropic/claude-sonnet-4-6"
720
- }
721
- };
722
- DEFAULT_RUNTIME = "claude";
723
- }
724
- });
725
-
726
- // src/lib/agent-config.ts
727
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync6 } from "fs";
728
- import path6 from "path";
729
- function loadAgentConfig() {
730
- if (!existsSync6(AGENT_CONFIG_PATH)) return {};
731
- try {
732
- return JSON.parse(readFileSync6(AGENT_CONFIG_PATH, "utf-8"));
733
- } catch {
734
- return {};
735
- }
736
- }
737
- function getAgentRuntime(agentId) {
738
- const config = loadAgentConfig();
739
- const entry = config[agentId];
740
- if (entry) return entry;
741
- const orgDefault = config["default"];
742
- if (orgDefault) return orgDefault;
743
- return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
744
- }
745
- var AGENT_CONFIG_PATH, DEFAULT_MODELS;
746
- var init_agent_config = __esm({
747
- "src/lib/agent-config.ts"() {
748
- "use strict";
749
- init_config();
750
- init_runtime_table();
751
- init_secure_files();
752
- AGENT_CONFIG_PATH = path6.join(EXE_AI_DIR, "agent-config.json");
753
- DEFAULT_MODELS = {
754
- claude: "claude-opus-4.6",
755
- codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
756
- opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
757
- };
758
- }
759
- });
760
-
761
1013
  // src/lib/intercom-queue.ts
762
1014
  var intercom_queue_exports = {};
763
1015
  __export(intercom_queue_exports, {
@@ -2617,6 +2869,13 @@ async function ensureSchema() {
2617
2869
  } catch (e) {
2618
2870
  logCatchDebug("migration", e);
2619
2871
  }
2872
+ for (const col of ["created_by_agent TEXT", "created_by_device TEXT", "source_session_id TEXT"]) {
2873
+ try {
2874
+ await client.execute({ sql: `ALTER TABLE behaviors ADD COLUMN ${col}`, args: [] });
2875
+ } catch (e) {
2876
+ logCatchDebug("migration", e);
2877
+ }
2878
+ }
2620
2879
  try {
2621
2880
  await client.execute({
2622
2881
  sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
@@ -3833,6 +4092,22 @@ async function ensureSchema() {
3833
4092
  } catch (e) {
3834
4093
  logCatchDebug("migration", e);
3835
4094
  }
4095
+ try {
4096
+ await client.execute({
4097
+ sql: `ALTER TABLE memories ADD COLUMN visibility TEXT DEFAULT 'private'`,
4098
+ args: []
4099
+ });
4100
+ } catch (e) {
4101
+ logCatchDebug("migration", e);
4102
+ }
4103
+ try {
4104
+ await client.execute({
4105
+ sql: `ALTER TABLE memories ADD COLUMN strength REAL DEFAULT 1.0`,
4106
+ args: []
4107
+ });
4108
+ } catch (e) {
4109
+ logCatchDebug("migration", e);
4110
+ }
3836
4111
  }
3837
4112
  async function disposeDatabase() {
3838
4113
  if (_walCheckpointTimer) {
@@ -3884,6 +4159,23 @@ var init_database = __esm({
3884
4159
  });
3885
4160
 
3886
4161
  // src/lib/license.ts
4162
+ var license_exports = {};
4163
+ __export(license_exports, {
4164
+ LICENSE_PUBLIC_KEY_PEM: () => LICENSE_PUBLIC_KEY_PEM,
4165
+ PLAN_LIMITS: () => PLAN_LIMITS,
4166
+ assertVpsLicense: () => assertVpsLicense,
4167
+ checkLicense: () => checkLicense,
4168
+ getCachedLicense: () => getCachedLicense,
4169
+ isFeatureAllowed: () => isFeatureAllowed,
4170
+ loadDeviceId: () => loadDeviceId,
4171
+ loadLicense: () => loadLicense,
4172
+ mirrorLicenseKey: () => mirrorLicenseKey,
4173
+ readCachedLicenseToken: () => readCachedLicenseToken,
4174
+ saveLicense: () => saveLicense,
4175
+ startLicenseRevalidation: () => startLicenseRevalidation,
4176
+ stopLicenseRevalidation: () => stopLicenseRevalidation,
4177
+ validateLicense: () => validateLicense
4178
+ });
3887
4179
  import { readFileSync as readFileSync10, writeFileSync as writeFileSync8, existsSync as existsSync11, mkdirSync as mkdirSync7 } from "fs";
3888
4180
  import { randomUUID as randomUUID2 } from "crypto";
3889
4181
  import { createRequire as createRequire2 } from "module";
@@ -3891,7 +4183,411 @@ import { pathToFileURL as pathToFileURL2 } from "url";
3891
4183
  import os8 from "os";
3892
4184
  import path11 from "path";
3893
4185
  import { jwtVerify, importSPKI } from "jose";
3894
- var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, PLAN_LIMITS;
4186
+ async function fetchRetry(url, init) {
4187
+ try {
4188
+ return await fetch(url, init);
4189
+ } catch {
4190
+ await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
4191
+ return fetch(url, { ...init, signal: AbortSignal.timeout(1e4) });
4192
+ }
4193
+ }
4194
+ function loadDeviceId() {
4195
+ const deviceJsonPath = path11.join(EXE_AI_DIR, "device.json");
4196
+ try {
4197
+ if (existsSync11(deviceJsonPath)) {
4198
+ const data = JSON.parse(readFileSync10(deviceJsonPath, "utf8"));
4199
+ if (data.deviceId) return data.deviceId;
4200
+ }
4201
+ } catch {
4202
+ }
4203
+ try {
4204
+ if (existsSync11(DEVICE_ID_PATH)) {
4205
+ const id2 = readFileSync10(DEVICE_ID_PATH, "utf8").trim();
4206
+ if (id2) return id2;
4207
+ }
4208
+ } catch {
4209
+ }
4210
+ const id = randomUUID2();
4211
+ mkdirSync7(EXE_AI_DIR, { recursive: true });
4212
+ writeFileSync8(DEVICE_ID_PATH, id, "utf8");
4213
+ return id;
4214
+ }
4215
+ function loadLicense() {
4216
+ try {
4217
+ if (!existsSync11(LICENSE_PATH)) return null;
4218
+ return readFileSync10(LICENSE_PATH, "utf8").trim();
4219
+ } catch {
4220
+ return null;
4221
+ }
4222
+ }
4223
+ function saveLicense(apiKey) {
4224
+ mkdirSync7(EXE_AI_DIR, { recursive: true });
4225
+ writeFileSync8(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
4226
+ }
4227
+ async function verifyLicenseJwt(token) {
4228
+ try {
4229
+ const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
4230
+ const { payload } = await jwtVerify(token, key, {
4231
+ algorithms: [LICENSE_JWT_ALG]
4232
+ });
4233
+ const plan = payload.plan ?? "free";
4234
+ const email = payload.sub ?? "";
4235
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
4236
+ return {
4237
+ valid: true,
4238
+ plan,
4239
+ email,
4240
+ expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
4241
+ deviceLimit: limits.devices,
4242
+ employeeLimit: limits.employees,
4243
+ memoryLimit: limits.memories
4244
+ };
4245
+ } catch {
4246
+ return null;
4247
+ }
4248
+ }
4249
+ async function getCachedLicense() {
4250
+ try {
4251
+ if (!existsSync11(CACHE_PATH)) return null;
4252
+ const raw = JSON.parse(readFileSync10(CACHE_PATH, "utf8"));
4253
+ if (!raw.token || typeof raw.token !== "string") return null;
4254
+ return await verifyLicenseJwt(raw.token);
4255
+ } catch {
4256
+ return null;
4257
+ }
4258
+ }
4259
+ function readCachedLicenseToken() {
4260
+ try {
4261
+ if (!existsSync11(CACHE_PATH)) return null;
4262
+ const raw = JSON.parse(readFileSync10(CACHE_PATH, "utf8"));
4263
+ return typeof raw.token === "string" ? raw.token : null;
4264
+ } catch {
4265
+ return null;
4266
+ }
4267
+ }
4268
+ function getRawCachedPlan() {
4269
+ try {
4270
+ const token = readCachedLicenseToken();
4271
+ if (!token) return null;
4272
+ const parts = token.split(".");
4273
+ if (parts.length !== 3) return null;
4274
+ const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
4275
+ const plan = payload.plan ?? "free";
4276
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
4277
+ process.stderr.write(
4278
+ `[license] WARN: using unverified cached plan (API unreachable, JWT expired). Plan: ${plan}
4279
+ `
4280
+ );
4281
+ return {
4282
+ valid: true,
4283
+ plan,
4284
+ email: payload.sub ?? "",
4285
+ expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
4286
+ deviceLimit: limits.devices,
4287
+ employeeLimit: limits.employees,
4288
+ memoryLimit: limits.memories
4289
+ };
4290
+ } catch {
4291
+ return null;
4292
+ }
4293
+ }
4294
+ function cacheResponse(token) {
4295
+ try {
4296
+ writeFileSync8(CACHE_PATH, JSON.stringify({ token }), "utf8");
4297
+ } catch {
4298
+ }
4299
+ }
4300
+ function loadPrismaForLicense() {
4301
+ if (_prismaFailed) return null;
4302
+ const dbUrl = process.env.DATABASE_URL;
4303
+ if (!dbUrl) {
4304
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path11.join(os8.homedir(), "exe-db");
4305
+ if (!existsSync11(path11.join(exeDbRoot, "package.json"))) {
4306
+ _prismaFailed = true;
4307
+ return null;
4308
+ }
4309
+ }
4310
+ if (!_prismaPromise) {
4311
+ _prismaPromise = (async () => {
4312
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
4313
+ if (explicitPath) {
4314
+ const mod2 = await import(pathToFileURL2(explicitPath).href);
4315
+ const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
4316
+ if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
4317
+ return new Ctor2();
4318
+ }
4319
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path11.join(os8.homedir(), "exe-db");
4320
+ const req = createRequire2(path11.join(exeDbRoot, "package.json"));
4321
+ const entry = req.resolve("@prisma/client");
4322
+ const mod = await import(pathToFileURL2(entry).href);
4323
+ const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
4324
+ if (!Ctor) throw new Error(`No PrismaClient in ${entry}`);
4325
+ return new Ctor();
4326
+ })().catch((err) => {
4327
+ _prismaFailed = true;
4328
+ _prismaPromise = null;
4329
+ throw err;
4330
+ });
4331
+ }
4332
+ return _prismaPromise;
4333
+ }
4334
+ async function validateViaPostgres(apiKey) {
4335
+ const loader = loadPrismaForLicense();
4336
+ if (!loader) return null;
4337
+ try {
4338
+ const prisma = await loader;
4339
+ const rows = await prisma.$queryRawUnsafe(
4340
+ `SELECT plan, email, status, device_limit, employee_limit, memory_limit, expires_at
4341
+ FROM billing.licenses WHERE key = $1 LIMIT 1`,
4342
+ apiKey
4343
+ );
4344
+ if (!rows || rows.length === 0) return null;
4345
+ const row = rows[0];
4346
+ if (row.status !== "active") return null;
4347
+ if (row.expires_at && new Date(row.expires_at) < /* @__PURE__ */ new Date()) return null;
4348
+ const plan = row.plan;
4349
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
4350
+ return {
4351
+ valid: true,
4352
+ plan,
4353
+ email: row.email,
4354
+ expiresAt: row.expires_at ? new Date(row.expires_at).toISOString() : null,
4355
+ deviceLimit: row.device_limit ?? limits.devices,
4356
+ employeeLimit: row.employee_limit ?? limits.employees,
4357
+ memoryLimit: row.memory_limit ?? limits.memories
4358
+ };
4359
+ } catch {
4360
+ return null;
4361
+ }
4362
+ }
4363
+ async function validateViaCFWorker(apiKey, deviceId) {
4364
+ try {
4365
+ const res = await fetchRetry(`${API_BASE}/auth/activate`, {
4366
+ method: "POST",
4367
+ headers: { "Content-Type": "application/json" },
4368
+ body: JSON.stringify({ apiKey, deviceId }),
4369
+ signal: AbortSignal.timeout(1e4)
4370
+ });
4371
+ if (!res.ok) return null;
4372
+ const data = await res.json();
4373
+ if (data.error === "device_limit_exceeded") return null;
4374
+ if (!data.valid) return null;
4375
+ if (data.token) {
4376
+ cacheResponse(data.token);
4377
+ const verified = await verifyLicenseJwt(data.token);
4378
+ if (verified) return verified;
4379
+ }
4380
+ const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
4381
+ return {
4382
+ valid: data.valid,
4383
+ plan: data.plan,
4384
+ email: data.email,
4385
+ expiresAt: data.expiresAt,
4386
+ deviceLimit: limits.devices,
4387
+ employeeLimit: limits.employees,
4388
+ memoryLimit: limits.memories
4389
+ };
4390
+ } catch {
4391
+ return null;
4392
+ }
4393
+ }
4394
+ async function validateLicense(apiKey, deviceId) {
4395
+ const did = deviceId ?? loadDeviceId();
4396
+ const pgResult = await validateViaPostgres(apiKey);
4397
+ if (pgResult) {
4398
+ try {
4399
+ writeFileSync8(CACHE_PATH, JSON.stringify({ pgLicense: pgResult, ts: Date.now() }), "utf8");
4400
+ } catch {
4401
+ }
4402
+ return pgResult;
4403
+ }
4404
+ const cfResult = await validateViaCFWorker(apiKey, did);
4405
+ if (cfResult) return cfResult;
4406
+ const cached = await getCachedLicense();
4407
+ if (cached) return cached;
4408
+ try {
4409
+ if (existsSync11(CACHE_PATH)) {
4410
+ const raw = JSON.parse(readFileSync10(CACHE_PATH, "utf8"));
4411
+ if (raw.pgLicense && raw.ts && Date.now() - raw.ts < 7 * 24 * 60 * 60 * 1e3) {
4412
+ return raw.pgLicense;
4413
+ }
4414
+ }
4415
+ } catch {
4416
+ }
4417
+ const rawFallback = getRawCachedPlan();
4418
+ if (rawFallback) return rawFallback;
4419
+ return { ...FREE_LICENSE, valid: false };
4420
+ }
4421
+ function getCacheAgeMs() {
4422
+ try {
4423
+ const { statSync: statSync6 } = __require("fs");
4424
+ const s = statSync6(CACHE_PATH);
4425
+ return Date.now() - s.mtimeMs;
4426
+ } catch {
4427
+ return Infinity;
4428
+ }
4429
+ }
4430
+ async function checkLicense() {
4431
+ let key = loadLicense();
4432
+ if (!key) {
4433
+ try {
4434
+ const configPath = path11.join(EXE_AI_DIR, "config.json");
4435
+ if (existsSync11(configPath)) {
4436
+ const raw = JSON.parse(readFileSync10(configPath, "utf8"));
4437
+ const cloud = raw.cloud;
4438
+ if (cloud?.apiKey) {
4439
+ key = cloud.apiKey;
4440
+ saveLicense(key);
4441
+ }
4442
+ }
4443
+ } catch {
4444
+ }
4445
+ }
4446
+ if (!key) return FREE_LICENSE;
4447
+ const cached = await getCachedLicense();
4448
+ if (cached && getCacheAgeMs() < CACHE_MAX_AGE_MS) return cached;
4449
+ const deviceId = loadDeviceId();
4450
+ return validateLicense(key, deviceId);
4451
+ }
4452
+ function isFeatureAllowed(license, feature) {
4453
+ switch (feature) {
4454
+ case "cloud_sync":
4455
+ case "external_agents":
4456
+ case "wiki":
4457
+ return license.plan !== "free";
4458
+ case "unlimited_employees":
4459
+ return license.plan === "team" || license.plan === "agency" || license.plan === "enterprise";
4460
+ }
4461
+ }
4462
+ function mirrorLicenseKey(apiKey) {
4463
+ const trimmed = apiKey.trim();
4464
+ if (!trimmed) return;
4465
+ saveLicense(trimmed);
4466
+ }
4467
+ async function assertVpsLicense(opts) {
4468
+ const env = opts?.env ?? process.env;
4469
+ const inProduction = env.NODE_ENV === "production";
4470
+ if (!opts?.force && !inProduction) {
4471
+ return { ...FREE_LICENSE, plan: "free" };
4472
+ }
4473
+ const envKey = env.EXE_LICENSE_KEY?.trim();
4474
+ if (envKey) {
4475
+ saveLicense(envKey);
4476
+ }
4477
+ const apiKey = envKey ?? loadLicense();
4478
+ if (!apiKey) {
4479
+ throw new Error(
4480
+ "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."
4481
+ );
4482
+ }
4483
+ const deviceId = loadDeviceId();
4484
+ let backendResponse = null;
4485
+ let explicitRejection = false;
4486
+ let transientFailure = false;
4487
+ try {
4488
+ const res = await fetchRetry(`${API_BASE}/auth/activate`, {
4489
+ method: "POST",
4490
+ headers: { "Content-Type": "application/json" },
4491
+ body: JSON.stringify({ apiKey, deviceId }),
4492
+ signal: AbortSignal.timeout(1e4)
4493
+ });
4494
+ if (res.ok) {
4495
+ backendResponse = await res.json();
4496
+ if (!backendResponse.valid) explicitRejection = true;
4497
+ } else if (res.status === 401 || res.status === 403) {
4498
+ explicitRejection = true;
4499
+ } else {
4500
+ transientFailure = true;
4501
+ }
4502
+ } catch {
4503
+ transientFailure = true;
4504
+ }
4505
+ if (backendResponse?.valid) {
4506
+ if (backendResponse.token) {
4507
+ cacheResponse(backendResponse.token);
4508
+ const verified = await verifyLicenseJwt(backendResponse.token);
4509
+ if (verified) return verified;
4510
+ }
4511
+ const limits = PLAN_LIMITS[backendResponse.plan] ?? PLAN_LIMITS.free;
4512
+ return {
4513
+ valid: true,
4514
+ plan: backendResponse.plan,
4515
+ email: backendResponse.email,
4516
+ expiresAt: backendResponse.expiresAt,
4517
+ deviceLimit: limits.devices,
4518
+ employeeLimit: limits.employees,
4519
+ memoryLimit: limits.memories
4520
+ };
4521
+ }
4522
+ if (explicitRejection) {
4523
+ throw new Error(
4524
+ `License invalid or expired. Renew at https://askexe.com, then restart. Backend rejected key ending in ...${apiKey.slice(-4)}.`
4525
+ );
4526
+ }
4527
+ if (!transientFailure) {
4528
+ throw new Error(
4529
+ "License validation failed: unknown backend state. Restore network connectivity to https://cloud.askexe.com and retry."
4530
+ );
4531
+ }
4532
+ const fresh = await getCachedLicense();
4533
+ if (fresh && fresh.valid) return fresh;
4534
+ const graceDays = opts?.offlineGraceDays ?? 7;
4535
+ const graceMs = graceDays * 24 * 60 * 60 * 1e3;
4536
+ try {
4537
+ const token = readCachedLicenseToken();
4538
+ if (token) {
4539
+ const payloadB64 = token.split(".")[1];
4540
+ if (payloadB64) {
4541
+ const payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString());
4542
+ const expMs = (payload.exp ?? 0) * 1e3;
4543
+ if (Date.now() < expMs + graceMs) {
4544
+ const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
4545
+ const { payload: verified } = await jwtVerify(token, key, {
4546
+ algorithms: [LICENSE_JWT_ALG],
4547
+ clockTolerance: graceDays * 24 * 60 * 60
4548
+ });
4549
+ const plan = verified.plan ?? "free";
4550
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
4551
+ return {
4552
+ valid: true,
4553
+ plan,
4554
+ email: verified.sub ?? "",
4555
+ expiresAt: verified.exp ? new Date(verified.exp * 1e3).toISOString() : null,
4556
+ deviceLimit: limits.devices,
4557
+ employeeLimit: limits.employees,
4558
+ memoryLimit: limits.memories
4559
+ };
4560
+ }
4561
+ }
4562
+ }
4563
+ } catch {
4564
+ }
4565
+ throw new Error(
4566
+ `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.`
4567
+ );
4568
+ }
4569
+ function startLicenseRevalidation(intervalMs = 36e5) {
4570
+ if (_revalTimer) return;
4571
+ _revalTimer = setInterval(async () => {
4572
+ try {
4573
+ const license = await checkLicense();
4574
+ if (!license.valid) {
4575
+ process.stderr.write("[exe-os] License expired or invalid \u2014 features may be restricted\n");
4576
+ }
4577
+ } catch {
4578
+ }
4579
+ }, intervalMs);
4580
+ if (_revalTimer && typeof _revalTimer === "object" && "unref" in _revalTimer) {
4581
+ _revalTimer.unref();
4582
+ }
4583
+ }
4584
+ function stopLicenseRevalidation() {
4585
+ if (_revalTimer) {
4586
+ clearInterval(_revalTimer);
4587
+ _revalTimer = null;
4588
+ }
4589
+ }
4590
+ 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;
3895
4591
  var init_license = __esm({
3896
4592
  "src/lib/license.ts"() {
3897
4593
  "use strict";
@@ -3899,7 +4595,13 @@ var init_license = __esm({
3899
4595
  LICENSE_PATH = path11.join(EXE_AI_DIR, "license.key");
3900
4596
  CACHE_PATH = path11.join(EXE_AI_DIR, "license-cache.json");
3901
4597
  DEVICE_ID_PATH = path11.join(EXE_AI_DIR, "device-id");
3902
- API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com/cloud";
4598
+ API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://cloud.askexe.com";
4599
+ RETRY_DELAY_MS = 500;
4600
+ LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
4601
+ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
4602
+ 4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
4603
+ -----END PUBLIC KEY-----`;
4604
+ LICENSE_JWT_ALG = "ES256";
3903
4605
  PLAN_LIMITS = {
3904
4606
  free: { devices: 1, employees: 1, memories: 5e3 },
3905
4607
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -3907,6 +4609,19 @@ var init_license = __esm({
3907
4609
  agency: { devices: 50, employees: 100, memories: 1e7 },
3908
4610
  enterprise: { devices: -1, employees: -1, memories: -1 }
3909
4611
  };
4612
+ FREE_LICENSE = {
4613
+ valid: true,
4614
+ plan: "free",
4615
+ email: "",
4616
+ expiresAt: null,
4617
+ deviceLimit: 1,
4618
+ employeeLimit: 1,
4619
+ memoryLimit: 5e3
4620
+ };
4621
+ _prismaPromise = null;
4622
+ _prismaFailed = false;
4623
+ CACHE_MAX_AGE_MS = 36e5;
4624
+ _revalTimer = null;
3910
4625
  }
3911
4626
  });
3912
4627
 
@@ -4591,6 +5306,19 @@ async function resolveTask(client, identifier, scopeSession) {
4591
5306
  args: [identifier, ...scope.args]
4592
5307
  });
4593
5308
  if (result.rows.length === 1) return result.rows[0];
5309
+ if (/^[a-f0-9]{7,12}$/i.test(identifier)) {
5310
+ result = await client.execute({
5311
+ sql: `SELECT * FROM tasks WHERE id LIKE ?`,
5312
+ args: [`${identifier}%`]
5313
+ });
5314
+ if (result.rows.length === 1) return result.rows[0];
5315
+ if (result.rows.length > 1) {
5316
+ const matches = result.rows.map((r) => `${String(r.id)} "${String(r.title)}" (${String(r.status)})`).join(", ");
5317
+ throw new Error(
5318
+ `Multiple tasks match short-ID "${identifier}": ${matches}. Use a longer prefix to disambiguate.`
5319
+ );
5320
+ }
5321
+ }
4594
5322
  result = await client.execute({
4595
5323
  sql: `SELECT * FROM tasks WHERE task_file LIKE ?${scope.sql}`,
4596
5324
  args: [`%${identifier}%`, ...scope.args]
@@ -5445,12 +6173,13 @@ async function cascadeUnblock(taskId, baseDir, now) {
5445
6173
  WHERE blocked_by = ? AND status = 'blocked'`,
5446
6174
  args: [now, taskId]
5447
6175
  });
5448
- if (baseDir && unblocked.rowsAffected > 0) {
5449
- const ubScope = sessionScopeFilter();
5450
- const unblockedRows = await client.execute({
5451
- sql: `SELECT task_file FROM tasks WHERE blocked_by IS NULL AND updated_at = ?${ubScope.sql}`,
5452
- args: [now, ...ubScope.args]
5453
- });
6176
+ if (unblocked.rowsAffected === 0) return;
6177
+ const ubScope = sessionScopeFilter();
6178
+ const unblockedRows = await client.execute({
6179
+ sql: `SELECT id, title, assigned_to, priority, task_file FROM tasks WHERE blocked_by IS NULL AND updated_at = ?${ubScope.sql}`,
6180
+ args: [now, ...ubScope.args]
6181
+ });
6182
+ if (baseDir) {
5454
6183
  for (const ur of unblockedRows.rows) {
5455
6184
  try {
5456
6185
  const ubFile = path18.join(baseDir, String(ur.task_file));
@@ -5462,6 +6191,19 @@ async function cascadeUnblock(taskId, baseDir, now) {
5462
6191
  }
5463
6192
  }
5464
6193
  }
6194
+ if (unblockedRows.rows.length > 0 && !process.env.VITEST) {
6195
+ try {
6196
+ const { queueIntercom: queueIntercom2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
6197
+ const dispatched = /* @__PURE__ */ new Set();
6198
+ for (const ur of unblockedRows.rows) {
6199
+ const assignee = String(ur.assigned_to);
6200
+ if (dispatched.has(assignee)) continue;
6201
+ dispatched.add(assignee);
6202
+ queueIntercom2(`${assignee}`, `unblocked: "${String(ur.title)}" is now ready`);
6203
+ }
6204
+ } catch {
6205
+ }
6206
+ }
5465
6207
  }
5466
6208
  async function findNextTask(assignedTo) {
5467
6209
  const client = getClient();
@@ -5671,6 +6413,15 @@ var init_embedder = __esm({
5671
6413
  // src/lib/behaviors.ts
5672
6414
  import crypto5 from "crypto";
5673
6415
  async function storeBehavior(opts) {
6416
+ try {
6417
+ const { loadEmployeesSync: loadEmployeesSync2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
6418
+ const roster = loadEmployeesSync2();
6419
+ if (roster.length > 0 && !roster.some((e) => e.name === opts.agentId)) {
6420
+ throw new Error(`Agent "${opts.agentId}" not found in roster. Cannot store behavior for unregistered agent.`);
6421
+ }
6422
+ } catch (e) {
6423
+ if (e instanceof Error && e.message.includes("not found in roster")) throw e;
6424
+ }
5674
6425
  const client = getClient();
5675
6426
  const id = crypto5.randomUUID();
5676
6427
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -5681,10 +6432,18 @@ async function storeBehavior(opts) {
5681
6432
  vector = new Float32Array(vec);
5682
6433
  } catch {
5683
6434
  }
6435
+ let createdByDevice = null;
6436
+ try {
6437
+ const { loadDeviceId: loadDeviceId2 } = await Promise.resolve().then(() => (init_license(), license_exports));
6438
+ createdByDevice = loadDeviceId2() ?? null;
6439
+ } catch {
6440
+ }
6441
+ const createdByAgent = process.env.AGENT_ID ?? null;
6442
+ const sourceSessionId = process.env.CLAUDE_SESSION_ID ?? process.env.SESSION_ID ?? null;
5684
6443
  await client.execute({
5685
- sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at, vector)
5686
- VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?)`,
5687
- args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now, vector ? vector.buffer : null]
6444
+ 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)
6445
+ VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?)`,
6446
+ args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now, vector ? vector.buffer : null, createdByAgent, createdByDevice, sourceSessionId]
5688
6447
  });
5689
6448
  return id;
5690
6449
  }
@@ -6116,6 +6875,12 @@ async function updateTask(input2) {
6116
6875
  }
6117
6876
  }
6118
6877
  }
6878
+ if (input2.status === "cancelled") {
6879
+ try {
6880
+ await cascadeUnblock(taskId, input2.baseDir, now);
6881
+ } catch {
6882
+ }
6883
+ }
6119
6884
  if ((input2.status === "done" || input2.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
6120
6885
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
6121
6886
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
@@ -6647,11 +7412,12 @@ function getDispatchedBy(sessionKey) {
6647
7412
  }
6648
7413
  }
6649
7414
  function resolveExeSession() {
7415
+ if (process.env.EXE_SESSION_NAME) {
7416
+ const fromEnv = extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
7417
+ if (fromEnv) return fromEnv;
7418
+ }
6650
7419
  const mySession = getMySession();
6651
7420
  if (!mySession) {
6652
- if (process.env.EXE_SESSION_NAME) {
6653
- return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
6654
- }
6655
7421
  return null;
6656
7422
  }
6657
7423
  const fromSessionName = extractRootExe(mySession);
@@ -6666,6 +7432,10 @@ function resolveExeSession() {
6666
7432
  `[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
6667
7433
  `
6668
7434
  );
7435
+ try {
7436
+ registerParentExe(key, fromSessionName);
7437
+ } catch {
7438
+ }
6669
7439
  candidate = fromSessionName;
6670
7440
  } else {
6671
7441
  candidate = fromCache;
@@ -8393,11 +9163,17 @@ var init_platform_procedures = __esm({
8393
9163
  content: "Founder -> coordinator (the executive agent, internally routed as 'COO') -> CTO/CMO. CTO -> engineers. CMO -> content production. Never skip levels: the coordinator does not bypass managers for specialist work. Specialists report to their manager. If you need cross-team info, use ask_team_memory \u2014 don't read other agents' task folders. Each level owns dispatch downward and review upward."
8394
9164
  },
8395
9165
  {
8396
- title: "Customer orchestration maturity \u2014 recommend, never trap",
9166
+ title: "Orchestration phase guidance \u2014 recommend, never trap",
8397
9167
  domain: "workflow",
8398
9168
  priority: "p1",
8399
9169
  content: "New customers start best in Phase 1: founder \u2194 coordinator/Chief of Staff, building company context. Suggest Phase 2 executives when domain work repeats; suggest Phase 3 parallel execution only when review/permission gates are ready. This is guidance, not a blocker: users may jump phases anytime. Never overwrite their phase, role titles, identities, or custom org design."
8400
9170
  },
9171
+ {
9172
+ title: "Routing slot vs display title \u2014 internal 'coo' is plumbing, not your name",
9173
+ domain: "identity",
9174
+ priority: "p0",
9175
+ content: "These procedures reference 'COO' as a shorthand for the coordinator role. This is an INTERNAL routing slot used by exe-os code (chain-of-command checks, dispatch logic, session detection). It is NOT your display title. Your actual title comes from your identity file's `title:` field \u2014 that is what you use externally: introductions, sign-offs, team comms, and any user-facing text. If your identity says `title: AI Chief of Staff`, you are the AI Chief of Staff. The routing slot stays `role: coo` for code compatibility \u2014 never rename it, but also never introduce yourself as 'COO' unless your identity file explicitly says so. The founder chose your title; respect it."
9176
+ },
8401
9177
  {
8402
9178
  title: "Single dispatch path \u2014 create_task only",
8403
9179
  domain: "workflow",
@@ -8431,6 +9207,12 @@ var init_platform_procedures = __esm({
8431
9207
  priority: "p0",
8432
9208
  content: "NEVER: (1) Access the database directly \u2014 it's SQLCipher encrypted, always fails. Use MCP tools only. (2) Manually spawn tmux sessions \u2014 create_task handles it. (3) Run git checkout main \u2014 agents work in worktrees. (4) Modify another agent's in-progress task. (5) Push to remote \u2014 the COO reviews and pushes. (6) Skip update_task(done) \u2014 it's the ONLY way your work gets reviewed. (7) Run git init."
8433
9209
  },
9210
+ {
9211
+ title: "Destructive operations \u2014 mandatory reviewer gate",
9212
+ domain: "security",
9213
+ priority: "p0",
9214
+ content: "Before ANY destructive operation (delete, remove, overwrite, drop, reset, force-push, truncate), you MUST: (1) Have your full task spec accessible \u2014 if you cannot read it, STOP and report to your reviewer. Never improvise destructive actions. (2) Confirm with your reviewer (assigned_by or COO) before executing. (3) If the task spec explicitly authorizes the operation, proceed \u2014 but log it. Violation = immediate task failure. This applies to ALL agents regardless of role."
9215
+ },
8434
9216
  {
8435
9217
  title: "Customer patch triage \u2014 upstream bug vs customization",
8436
9218
  domain: "support",
@@ -8582,7 +9364,7 @@ var init_platform_procedures = __esm({
8582
9364
  title: "MCP tool dispatch \u2014 all tools use action parameter",
8583
9365
  domain: "tool-use",
8584
9366
  priority: "p0",
8585
- 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.'
9367
+ 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.'
8586
9368
  },
8587
9369
  {
8588
9370
  title: "MCP tools \u2014 memory, decision, and search",
@@ -8716,10 +9498,24 @@ function stableId(memoryId, type, content) {
8716
9498
  return createHash2("sha256").update(`${memoryId}:${type}:${content}`).digest("hex").slice(0, 32);
8717
9499
  }
8718
9500
  function cleanText(text) {
8719
- return text.replace(/```[\s\S]*?```/g, " ").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
9501
+ let cleaned = text.replace(
9502
+ /```(\w*)\n(.*?)(?:\n[\s\S]*?)```/g,
9503
+ (_m, lang, firstLine) => `[code${lang ? `:${lang}` : ""}] ${firstLine.trim()}`
9504
+ );
9505
+ cleaned = cleaned.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
9506
+ return cleaned;
8720
9507
  }
8721
- function splitSentences(text) {
8722
- return cleanText(text).split(/(?<=[.!?])\s+|\n+/).map((s) => s.trim()).filter((s) => s.length >= 24 && s.length <= MAX_SENTENCE_CHARS);
9508
+ function splitSegments(text) {
9509
+ const cleaned = cleanText(text);
9510
+ const segments = cleaned.split(/(?<=[.!?:;])\s+|\n{2,}|(?<=\))\s+(?=[A-Z])|\s*[|│]\s*/).map((s) => s.trim()).filter((s) => s.length >= MIN_SEGMENT_CHARS && s.length <= MAX_SEGMENT_CHARS);
9511
+ if (segments.length === 0 && cleaned.length >= MIN_SEGMENT_CHARS) {
9512
+ const lines = cleaned.split(/\n+/).map((l) => l.trim()).filter((l) => l.length >= MIN_SEGMENT_CHARS && l.length <= MAX_SEGMENT_CHARS);
9513
+ if (lines.length > 0) return lines;
9514
+ if (cleaned.length >= MIN_SEGMENT_CHARS) {
9515
+ return [cleaned.slice(0, MAX_SEGMENT_CHARS)];
9516
+ }
9517
+ }
9518
+ return segments;
8723
9519
  }
8724
9520
  function inferCardType(sentence, toolName) {
8725
9521
  const lower = sentence.toLowerCase();
@@ -8751,12 +9547,12 @@ function predicateFor(type) {
8751
9547
  }
8752
9548
  }
8753
9549
  function extractMemoryCards(row) {
8754
- const sentences = splitSentences(row.raw_text);
9550
+ const segments = splitSegments(row.raw_text);
8755
9551
  const cards = [];
8756
- for (const sentence of sentences) {
9552
+ for (const sentence of segments) {
8757
9553
  const type = inferCardType(sentence, row.tool_name);
8758
9554
  const subject = extractSubject(sentence, row.agent_id);
8759
- const content = sentence.length > MAX_SENTENCE_CHARS ? `${sentence.slice(0, MAX_SENTENCE_CHARS - 1)}\u2026` : sentence;
9555
+ const content = sentence.length > MAX_SEGMENT_CHARS ? `${sentence.slice(0, MAX_SEGMENT_CHARS - 1)}\u2026` : sentence;
8760
9556
  cards.push({
8761
9557
  id: stableId(row.id, type, content),
8762
9558
  memory_id: row.id,
@@ -8852,13 +9648,14 @@ Source memory: ${String(row.source_ref ?? row.memory_id)}`,
8852
9648
  last_accessed: String(row.timestamp)
8853
9649
  }));
8854
9650
  }
8855
- var MAX_CARDS_PER_MEMORY, MAX_SENTENCE_CHARS;
9651
+ var MAX_CARDS_PER_MEMORY, MAX_SEGMENT_CHARS, MIN_SEGMENT_CHARS;
8856
9652
  var init_memory_cards = __esm({
8857
9653
  "src/lib/memory-cards.ts"() {
8858
9654
  "use strict";
8859
9655
  init_database();
8860
- MAX_CARDS_PER_MEMORY = 6;
8861
- MAX_SENTENCE_CHARS = 360;
9656
+ MAX_CARDS_PER_MEMORY = 8;
9657
+ MAX_SEGMENT_CHARS = 500;
9658
+ MIN_SEGMENT_CHARS = 20;
8862
9659
  }
8863
9660
  });
8864
9661
 
@@ -10195,9 +10992,9 @@ var init_git_task_sweep = __esm({
10195
10992
  // src/lib/active-agent.ts
10196
10993
  init_config();
10197
10994
  init_session_key();
10198
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2, readdirSync } from "fs";
10995
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2, readdirSync } from "fs";
10199
10996
  import { execSync as execSync3 } from "child_process";
10200
- import path3 from "path";
10997
+ import path4 from "path";
10201
10998
 
10202
10999
  // src/mcp/agent-context.ts
10203
11000
  import { AsyncLocalStorage } from "async_hooks";
@@ -10208,7 +11005,7 @@ function getAgentContext() {
10208
11005
 
10209
11006
  // src/lib/active-agent.ts
10210
11007
  init_employees();
10211
- var CACHE_DIR = path3.join(EXE_AI_DIR, "session-cache");
11008
+ var CACHE_DIR = path4.join(EXE_AI_DIR, "session-cache");
10212
11009
  var STALE_MS = 24 * 60 * 60 * 1e3;
10213
11010
  function isNameWithOptionalInstance(candidate, baseName) {
10214
11011
  if (candidate === baseName) return true;
@@ -10253,7 +11050,7 @@ function resolveActiveAgentFromTmuxSession(sessionName) {
10253
11050
  return null;
10254
11051
  }
10255
11052
  function getMarkerPath() {
10256
- return path3.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
11053
+ return path4.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
10257
11054
  }
10258
11055
  function clearActiveAgent() {
10259
11056
  try {
@@ -10266,7 +11063,7 @@ function getActiveAgent() {
10266
11063
  if (httpCtx) return httpCtx;
10267
11064
  try {
10268
11065
  const markerPath = getMarkerPath();
10269
- const raw = readFileSync3(markerPath, "utf8");
11066
+ const raw = readFileSync4(markerPath, "utf8");
10270
11067
  const data = JSON.parse(raw);
10271
11068
  if (data.agentId) {
10272
11069
  if (data.startedAt) {
@@ -10311,15 +11108,15 @@ init_session_key();
10311
11108
 
10312
11109
  // src/lib/cache-warmth.ts
10313
11110
  import os3 from "os";
10314
- import path4 from "path";
10315
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync4, unlinkSync as unlinkSync3, writeFileSync as writeFileSync3 } from "fs";
11111
+ import path5 from "path";
11112
+ import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync5, unlinkSync as unlinkSync3, writeFileSync as writeFileSync4 } from "fs";
10316
11113
  var CACHE_TTL_MS = 5 * 60 * 1e3;
10317
- var CACHE_DIR2 = path4.join(
10318
- process.env.EXE_OS_DIR ?? path4.join(os3.homedir(), ".exe-os"),
11114
+ var CACHE_DIR2 = path5.join(
11115
+ process.env.EXE_OS_DIR ?? path5.join(os3.homedir(), ".exe-os"),
10319
11116
  "session-cache"
10320
11117
  );
10321
11118
  function getStatePath(sessionKey) {
10322
- return path4.join(CACHE_DIR2, `cache-warmth-${sessionKey}.json`);
11119
+ return path5.join(CACHE_DIR2, `cache-warmth-${sessionKey}.json`);
10323
11120
  }
10324
11121
  function clearCacheState(sessionKey) {
10325
11122
  try {