@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
@@ -337,11 +337,168 @@ var init_config = __esm({
337
337
  }
338
338
  });
339
339
 
340
+ // src/lib/runtime-table.ts
341
+ var RUNTIME_TABLE, DEFAULT_RUNTIME;
342
+ var init_runtime_table = __esm({
343
+ "src/lib/runtime-table.ts"() {
344
+ "use strict";
345
+ RUNTIME_TABLE = {
346
+ codex: {
347
+ binary: "codex",
348
+ launchMode: "interactive",
349
+ autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
350
+ inlineFlag: "--no-alt-screen",
351
+ apiKeyEnv: "OPENAI_API_KEY",
352
+ defaultModel: "gpt-5.5"
353
+ },
354
+ opencode: {
355
+ binary: "opencode",
356
+ launchMode: "exec",
357
+ autoApproveFlag: "--dangerously-skip-permissions",
358
+ inlineFlag: "",
359
+ apiKeyEnv: "ANTHROPIC_API_KEY",
360
+ defaultModel: "anthropic/claude-sonnet-4-6"
361
+ }
362
+ };
363
+ DEFAULT_RUNTIME = "claude";
364
+ }
365
+ });
366
+
367
+ // src/lib/agent-config.ts
368
+ var agent_config_exports = {};
369
+ __export(agent_config_exports, {
370
+ AGENT_CONFIG_PATH: () => AGENT_CONFIG_PATH,
371
+ DEFAULT_MODELS: () => DEFAULT_MODELS,
372
+ KNOWN_RUNTIMES: () => KNOWN_RUNTIMES,
373
+ RUNTIME_LABELS: () => RUNTIME_LABELS,
374
+ clearAgentRuntime: () => clearAgentRuntime,
375
+ getAgentRuntime: () => getAgentRuntime,
376
+ loadAgentConfig: () => loadAgentConfig,
377
+ saveAgentConfig: () => saveAgentConfig,
378
+ setAgentMcps: () => setAgentMcps,
379
+ setAgentRuntime: () => setAgentRuntime
380
+ });
381
+ import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
382
+ import path2 from "path";
383
+ function loadAgentConfig() {
384
+ if (!existsSync3(AGENT_CONFIG_PATH)) return {};
385
+ try {
386
+ return JSON.parse(readFileSync2(AGENT_CONFIG_PATH, "utf-8"));
387
+ } catch {
388
+ return {};
389
+ }
390
+ }
391
+ function saveAgentConfig(config) {
392
+ const dir = path2.dirname(AGENT_CONFIG_PATH);
393
+ ensurePrivateDirSync(dir);
394
+ writeFileSync(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
395
+ enforcePrivateFileSync(AGENT_CONFIG_PATH);
396
+ }
397
+ function getAgentRuntime(agentId) {
398
+ const config = loadAgentConfig();
399
+ const entry = config[agentId];
400
+ if (entry) return entry;
401
+ const orgDefault = config["default"];
402
+ if (orgDefault) return orgDefault;
403
+ return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
404
+ }
405
+ function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
406
+ const knownModels = KNOWN_RUNTIMES[runtime];
407
+ if (!knownModels) {
408
+ return {
409
+ ok: false,
410
+ error: `Unknown runtime "${runtime}". Valid: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`
411
+ };
412
+ }
413
+ if (!knownModels.includes(model)) {
414
+ return {
415
+ ok: false,
416
+ error: `Unknown model "${model}" for runtime "${runtime}". Valid: ${knownModels.join(", ")}`
417
+ };
418
+ }
419
+ const config = loadAgentConfig();
420
+ const existing = config[agentId];
421
+ const entry = { runtime, model };
422
+ if (reasoning_effort) entry.reasoning_effort = reasoning_effort;
423
+ if (mcps !== void 0) {
424
+ entry.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
425
+ } else if (existing?.mcps) {
426
+ entry.mcps = existing.mcps;
427
+ }
428
+ config[agentId] = entry;
429
+ saveAgentConfig(config);
430
+ return { ok: true };
431
+ }
432
+ function setAgentMcps(agentId, mcps) {
433
+ const config = loadAgentConfig();
434
+ const existing = config[agentId] ?? getAgentRuntime(agentId);
435
+ existing.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
436
+ config[agentId] = existing;
437
+ saveAgentConfig(config);
438
+ return { ok: true };
439
+ }
440
+ function clearAgentRuntime(agentId) {
441
+ const config = loadAgentConfig();
442
+ delete config[agentId];
443
+ saveAgentConfig(config);
444
+ }
445
+ var AGENT_CONFIG_PATH, KNOWN_RUNTIMES, RUNTIME_LABELS, DEFAULT_MODELS;
446
+ var init_agent_config = __esm({
447
+ "src/lib/agent-config.ts"() {
448
+ "use strict";
449
+ init_config();
450
+ init_runtime_table();
451
+ init_secure_files();
452
+ AGENT_CONFIG_PATH = path2.join(EXE_AI_DIR, "agent-config.json");
453
+ KNOWN_RUNTIMES = {
454
+ claude: ["claude-opus-4.6", "claude-opus-4", "claude-sonnet-4.6", "claude-sonnet-4", "claude-haiku-4.5"],
455
+ codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
456
+ opencode: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4", "google/gemini-2.5-pro", "deepseek/deepseek-r3", "minimax/minimax-m2.5"]
457
+ };
458
+ RUNTIME_LABELS = {
459
+ claude: "Claude Code (Anthropic)",
460
+ codex: "Codex (OpenAI)",
461
+ opencode: "OpenCode (open source)"
462
+ };
463
+ DEFAULT_MODELS = {
464
+ claude: "claude-opus-4.6",
465
+ codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
466
+ opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
467
+ };
468
+ }
469
+ });
470
+
340
471
  // src/lib/employees.ts
472
+ var employees_exports = {};
473
+ __export(employees_exports, {
474
+ COORDINATOR_ROLE: () => COORDINATOR_ROLE,
475
+ DEFAULT_COORDINATOR_TEMPLATE_NAME: () => DEFAULT_COORDINATOR_TEMPLATE_NAME,
476
+ EMPLOYEES_PATH: () => EMPLOYEES_PATH,
477
+ addEmployee: () => addEmployee,
478
+ baseAgentName: () => baseAgentName,
479
+ canCoordinate: () => canCoordinate,
480
+ getCoordinatorEmployee: () => getCoordinatorEmployee,
481
+ getCoordinatorName: () => getCoordinatorName,
482
+ getEmployee: () => getEmployee,
483
+ getEmployeeByRole: () => getEmployeeByRole,
484
+ getEmployeeNamesByRole: () => getEmployeeNamesByRole,
485
+ hasRole: () => hasRole,
486
+ hireEmployee: () => hireEmployee,
487
+ isCoordinatorName: () => isCoordinatorName,
488
+ isCoordinatorRole: () => isCoordinatorRole,
489
+ isMultiInstance: () => isMultiInstance,
490
+ loadEmployees: () => loadEmployees,
491
+ loadEmployeesSync: () => loadEmployeesSync,
492
+ normalizeRole: () => normalizeRole,
493
+ normalizeRosterCase: () => normalizeRosterCase,
494
+ registerBinSymlinks: () => registerBinSymlinks,
495
+ saveEmployees: () => saveEmployees,
496
+ validateEmployeeName: () => validateEmployeeName
497
+ });
341
498
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
342
- import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
499
+ import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
343
500
  import { execSync } from "child_process";
344
- import path2 from "path";
501
+ import path3 from "path";
345
502
  import os2 from "os";
346
503
  function normalizeRole(role) {
347
504
  return (role ?? "").trim().toLowerCase();
@@ -362,8 +519,23 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
362
519
  function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
363
520
  return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
364
521
  }
522
+ function validateEmployeeName(name) {
523
+ if (!name) {
524
+ return { valid: false, error: "Name is required" };
525
+ }
526
+ if (name.length > 32) {
527
+ return { valid: false, error: "Name must be 32 characters or fewer" };
528
+ }
529
+ if (!/^[a-z][a-z0-9]*$/.test(name)) {
530
+ return {
531
+ valid: false,
532
+ error: "Name must start with a letter and contain only lowercase alphanumeric characters"
533
+ };
534
+ }
535
+ return { valid: true };
536
+ }
365
537
  async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
366
- if (!existsSync3(employeesPath)) {
538
+ if (!existsSync4(employeesPath)) {
367
539
  return [];
368
540
  }
369
541
  const raw = await readFile2(employeesPath, "utf-8");
@@ -373,10 +545,14 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
373
545
  return [];
374
546
  }
375
547
  }
548
+ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
549
+ await mkdir2(path3.dirname(employeesPath), { recursive: true });
550
+ await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
551
+ }
376
552
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
377
- if (!existsSync3(employeesPath)) return [];
553
+ if (!existsSync4(employeesPath)) return [];
378
554
  try {
379
- return JSON.parse(readFileSync2(employeesPath, "utf-8"));
555
+ return JSON.parse(readFileSync3(employeesPath, "utf-8"));
380
556
  } catch {
381
557
  return [];
382
558
  }
@@ -384,6 +560,19 @@ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
384
560
  function getEmployee(employees, name) {
385
561
  return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
386
562
  }
563
+ function getEmployeeByRole(employees, role) {
564
+ const lower = role.toLowerCase();
565
+ return employees.find((e) => e.role.toLowerCase() === lower);
566
+ }
567
+ function getEmployeeNamesByRole(employees, role) {
568
+ const lower = role.toLowerCase();
569
+ return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
570
+ }
571
+ function hasRole(agentName, role) {
572
+ const employees = loadEmployeesSync();
573
+ const emp = getEmployee(employees, agentName);
574
+ return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
575
+ }
387
576
  function baseAgentName(name, employees) {
388
577
  const match = name.match(/^([a-zA-Z]+)\d+$/);
389
578
  if (!match) return name;
@@ -398,22 +587,147 @@ function isMultiInstance(agentName, employees) {
398
587
  if (!emp) return false;
399
588
  return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
400
589
  }
401
- var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR;
590
+ function addEmployee(employees, employee) {
591
+ const { systemPrompt: _legacyPrompt, ...rest } = employee;
592
+ const normalized = { ...rest, name: employee.name.toLowerCase() };
593
+ if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
594
+ throw new Error(`Employee '${normalized.name}' already exists`);
595
+ }
596
+ return [...employees, normalized];
597
+ }
598
+ function appendToCoordinatorTeam(employee) {
599
+ const coordinator = getCoordinatorEmployee(loadEmployeesSync());
600
+ if (!coordinator) return;
601
+ const idPath = path3.join(IDENTITY_DIR, `${coordinator.name}.md`);
602
+ if (!existsSync4(idPath)) return;
603
+ const content = readFileSync3(idPath, "utf-8");
604
+ if (content.includes(`**${capitalize(employee.name)}`)) return;
605
+ const teamMatch = content.match(TEAM_SECTION_RE);
606
+ if (!teamMatch || teamMatch.index === void 0) return;
607
+ const afterTeam = content.slice(teamMatch.index + teamMatch[0].length);
608
+ const nextHeading = afterTeam.match(/\n## /);
609
+ const entry = `
610
+ **${capitalize(employee.name)} (${employee.role}):** Newly hired. Update this description as the role develops.
611
+ `;
612
+ let updated;
613
+ if (nextHeading && nextHeading.index !== void 0) {
614
+ const insertAt = teamMatch.index + teamMatch[0].length + nextHeading.index;
615
+ updated = content.slice(0, insertAt) + entry + content.slice(insertAt);
616
+ } else {
617
+ updated = content.trimEnd() + "\n" + entry;
618
+ }
619
+ writeFileSync2(idPath, updated, "utf-8");
620
+ }
621
+ function capitalize(s) {
622
+ return s.charAt(0).toUpperCase() + s.slice(1);
623
+ }
624
+ async function hireEmployee(employee) {
625
+ const employees = await loadEmployees();
626
+ const updated = addEmployee(employees, employee);
627
+ await saveEmployees(updated);
628
+ try {
629
+ appendToCoordinatorTeam(employee);
630
+ } catch {
631
+ }
632
+ try {
633
+ const { loadAgentConfig: loadAgentConfig2, saveAgentConfig: saveAgentConfig2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
634
+ const config = loadAgentConfig2();
635
+ const name = employee.name.toLowerCase();
636
+ if (!config[name] && config["default"]) {
637
+ config[name] = { ...config["default"] };
638
+ saveAgentConfig2(config);
639
+ }
640
+ } catch {
641
+ }
642
+ return updated;
643
+ }
644
+ async function normalizeRosterCase(rosterPath) {
645
+ const employees = await loadEmployees(rosterPath);
646
+ let changed = false;
647
+ for (const emp of employees) {
648
+ if (emp.name !== emp.name.toLowerCase()) {
649
+ const oldName = emp.name;
650
+ emp.name = emp.name.toLowerCase();
651
+ changed = true;
652
+ try {
653
+ const identityDir = path3.join(os2.homedir(), ".exe-os", "identity");
654
+ const oldPath = path3.join(identityDir, `${oldName}.md`);
655
+ const newPath = path3.join(identityDir, `${emp.name}.md`);
656
+ if (existsSync4(oldPath) && !existsSync4(newPath)) {
657
+ renameSync2(oldPath, newPath);
658
+ } else if (existsSync4(oldPath) && oldPath !== newPath) {
659
+ const content = readFileSync3(oldPath, "utf-8");
660
+ writeFileSync2(newPath, content, "utf-8");
661
+ if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
662
+ unlinkSync(oldPath);
663
+ }
664
+ }
665
+ } catch {
666
+ }
667
+ }
668
+ }
669
+ if (changed) {
670
+ await saveEmployees(employees, rosterPath);
671
+ }
672
+ return changed;
673
+ }
674
+ function findExeBin() {
675
+ try {
676
+ return execSync(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
677
+ } catch {
678
+ return null;
679
+ }
680
+ }
681
+ function registerBinSymlinks(name) {
682
+ const created = [];
683
+ const skipped = [];
684
+ const errors = [];
685
+ const exeBinPath = findExeBin();
686
+ if (!exeBinPath) {
687
+ errors.push("Could not find 'exe-os' in PATH");
688
+ return { created, skipped, errors };
689
+ }
690
+ const binDir = path3.dirname(exeBinPath);
691
+ let target;
692
+ try {
693
+ target = readlinkSync(exeBinPath);
694
+ } catch {
695
+ errors.push("Could not read 'exe' symlink");
696
+ return { created, skipped, errors };
697
+ }
698
+ for (const suffix of ["", "-opencode"]) {
699
+ const linkName = `${name}${suffix}`;
700
+ const linkPath = path3.join(binDir, linkName);
701
+ if (existsSync4(linkPath)) {
702
+ skipped.push(linkName);
703
+ continue;
704
+ }
705
+ try {
706
+ symlinkSync(target, linkPath);
707
+ created.push(linkName);
708
+ } catch (err) {
709
+ errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
710
+ }
711
+ }
712
+ return { created, skipped, errors };
713
+ }
714
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR, TEAM_SECTION_RE;
402
715
  var init_employees = __esm({
403
716
  "src/lib/employees.ts"() {
404
717
  "use strict";
405
718
  init_config();
406
- EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
719
+ EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
407
720
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
408
721
  COORDINATOR_ROLE = "COO";
409
722
  MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
410
- IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
723
+ IDENTITY_DIR = path3.join(EXE_AI_DIR, "identity");
724
+ TEAM_SECTION_RE = /^## Team\b.*$/m;
411
725
  }
412
726
  });
413
727
 
414
728
  // src/lib/database-adapter.ts
415
729
  import os3 from "os";
416
- import path3 from "path";
730
+ import path4 from "path";
417
731
  import { createRequire } from "module";
418
732
  import { pathToFileURL } from "url";
419
733
  var BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES;
@@ -442,7 +756,7 @@ var init_memory = __esm({
442
756
  });
443
757
 
444
758
  // src/lib/database.ts
445
- import { chmodSync as chmodSync2, existsSync as existsSync4, statSync, copyFileSync, unlinkSync as unlinkSync2, openSync, closeSync, mkdirSync as mkdirSync2 } from "fs";
759
+ import { chmodSync as chmodSync2, existsSync as existsSync5, statSync, copyFileSync, unlinkSync as unlinkSync2, openSync, closeSync, mkdirSync as mkdirSync2 } from "fs";
446
760
  import { createClient } from "@libsql/client";
447
761
  import { homedir } from "os";
448
762
  import { join } from "path";
@@ -495,12 +809,12 @@ var init_database = __esm({
495
809
  });
496
810
 
497
811
  // src/lib/session-registry.ts
498
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, existsSync as existsSync5 } from "fs";
499
- import path4 from "path";
812
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync6 } from "fs";
813
+ import path5 from "path";
500
814
  import os4 from "os";
501
815
  function registerSession(entry) {
502
- const dir = path4.dirname(REGISTRY_PATH);
503
- if (!existsSync5(dir)) {
816
+ const dir = path5.dirname(REGISTRY_PATH);
817
+ if (!existsSync6(dir)) {
504
818
  mkdirSync3(dir, { recursive: true });
505
819
  }
506
820
  const sessions = listSessions();
@@ -510,11 +824,11 @@ function registerSession(entry) {
510
824
  } else {
511
825
  sessions.push(entry);
512
826
  }
513
- writeFileSync2(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
827
+ writeFileSync3(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
514
828
  }
515
829
  function listSessions() {
516
830
  try {
517
- const raw = readFileSync3(REGISTRY_PATH, "utf8");
831
+ const raw = readFileSync4(REGISTRY_PATH, "utf8");
518
832
  return JSON.parse(raw);
519
833
  } catch {
520
834
  return [];
@@ -524,7 +838,7 @@ var REGISTRY_PATH;
524
838
  var init_session_registry = __esm({
525
839
  "src/lib/session-registry.ts"() {
526
840
  "use strict";
527
- REGISTRY_PATH = path4.join(os4.homedir(), ".exe-os", "session-registry.json");
841
+ REGISTRY_PATH = path5.join(os4.homedir(), ".exe-os", "session-registry.json");
528
842
  }
529
843
  });
530
844
 
@@ -786,68 +1100,6 @@ var init_provider_table = __esm({
786
1100
  }
787
1101
  });
788
1102
 
789
- // src/lib/runtime-table.ts
790
- var RUNTIME_TABLE, DEFAULT_RUNTIME;
791
- var init_runtime_table = __esm({
792
- "src/lib/runtime-table.ts"() {
793
- "use strict";
794
- RUNTIME_TABLE = {
795
- codex: {
796
- binary: "codex",
797
- launchMode: "interactive",
798
- autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
799
- inlineFlag: "--no-alt-screen",
800
- apiKeyEnv: "OPENAI_API_KEY",
801
- defaultModel: "gpt-5.5"
802
- },
803
- opencode: {
804
- binary: "opencode",
805
- launchMode: "exec",
806
- autoApproveFlag: "--dangerously-skip-permissions",
807
- inlineFlag: "",
808
- apiKeyEnv: "ANTHROPIC_API_KEY",
809
- defaultModel: "anthropic/claude-sonnet-4-6"
810
- }
811
- };
812
- DEFAULT_RUNTIME = "claude";
813
- }
814
- });
815
-
816
- // src/lib/agent-config.ts
817
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync6 } from "fs";
818
- import path5 from "path";
819
- function loadAgentConfig() {
820
- if (!existsSync6(AGENT_CONFIG_PATH)) return {};
821
- try {
822
- return JSON.parse(readFileSync4(AGENT_CONFIG_PATH, "utf-8"));
823
- } catch {
824
- return {};
825
- }
826
- }
827
- function getAgentRuntime(agentId) {
828
- const config = loadAgentConfig();
829
- const entry = config[agentId];
830
- if (entry) return entry;
831
- const orgDefault = config["default"];
832
- if (orgDefault) return orgDefault;
833
- return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
834
- }
835
- var AGENT_CONFIG_PATH, DEFAULT_MODELS;
836
- var init_agent_config = __esm({
837
- "src/lib/agent-config.ts"() {
838
- "use strict";
839
- init_config();
840
- init_runtime_table();
841
- init_secure_files();
842
- AGENT_CONFIG_PATH = path5.join(EXE_AI_DIR, "agent-config.json");
843
- DEFAULT_MODELS = {
844
- claude: "claude-opus-4.6",
845
- codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
846
- opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
847
- };
848
- }
849
- });
850
-
851
1103
  // src/lib/intercom-queue.ts
852
1104
  var intercom_queue_exports = {};
853
1105
  __export(intercom_queue_exports, {
@@ -975,6 +1227,23 @@ var init_intercom_queue = __esm({
975
1227
  });
976
1228
 
977
1229
  // src/lib/license.ts
1230
+ var license_exports = {};
1231
+ __export(license_exports, {
1232
+ LICENSE_PUBLIC_KEY_PEM: () => LICENSE_PUBLIC_KEY_PEM,
1233
+ PLAN_LIMITS: () => PLAN_LIMITS,
1234
+ assertVpsLicense: () => assertVpsLicense,
1235
+ checkLicense: () => checkLicense,
1236
+ getCachedLicense: () => getCachedLicense,
1237
+ isFeatureAllowed: () => isFeatureAllowed,
1238
+ loadDeviceId: () => loadDeviceId,
1239
+ loadLicense: () => loadLicense,
1240
+ mirrorLicenseKey: () => mirrorLicenseKey,
1241
+ readCachedLicenseToken: () => readCachedLicenseToken,
1242
+ saveLicense: () => saveLicense,
1243
+ startLicenseRevalidation: () => startLicenseRevalidation,
1244
+ stopLicenseRevalidation: () => stopLicenseRevalidation,
1245
+ validateLicense: () => validateLicense
1246
+ });
978
1247
  import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync8, mkdirSync as mkdirSync5 } from "fs";
979
1248
  import { randomUUID } from "crypto";
980
1249
  import { createRequire as createRequire2 } from "module";
@@ -982,7 +1251,411 @@ import { pathToFileURL as pathToFileURL2 } from "url";
982
1251
  import os6 from "os";
983
1252
  import path7 from "path";
984
1253
  import { jwtVerify, importSPKI } from "jose";
985
- var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, PLAN_LIMITS;
1254
+ async function fetchRetry(url, init) {
1255
+ try {
1256
+ return await fetch(url, init);
1257
+ } catch {
1258
+ await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
1259
+ return fetch(url, { ...init, signal: AbortSignal.timeout(1e4) });
1260
+ }
1261
+ }
1262
+ function loadDeviceId() {
1263
+ const deviceJsonPath = path7.join(EXE_AI_DIR, "device.json");
1264
+ try {
1265
+ if (existsSync8(deviceJsonPath)) {
1266
+ const data = JSON.parse(readFileSync6(deviceJsonPath, "utf8"));
1267
+ if (data.deviceId) return data.deviceId;
1268
+ }
1269
+ } catch {
1270
+ }
1271
+ try {
1272
+ if (existsSync8(DEVICE_ID_PATH)) {
1273
+ const id2 = readFileSync6(DEVICE_ID_PATH, "utf8").trim();
1274
+ if (id2) return id2;
1275
+ }
1276
+ } catch {
1277
+ }
1278
+ const id = randomUUID();
1279
+ mkdirSync5(EXE_AI_DIR, { recursive: true });
1280
+ writeFileSync5(DEVICE_ID_PATH, id, "utf8");
1281
+ return id;
1282
+ }
1283
+ function loadLicense() {
1284
+ try {
1285
+ if (!existsSync8(LICENSE_PATH)) return null;
1286
+ return readFileSync6(LICENSE_PATH, "utf8").trim();
1287
+ } catch {
1288
+ return null;
1289
+ }
1290
+ }
1291
+ function saveLicense(apiKey) {
1292
+ mkdirSync5(EXE_AI_DIR, { recursive: true });
1293
+ writeFileSync5(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
1294
+ }
1295
+ async function verifyLicenseJwt(token) {
1296
+ try {
1297
+ const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
1298
+ const { payload } = await jwtVerify(token, key, {
1299
+ algorithms: [LICENSE_JWT_ALG]
1300
+ });
1301
+ const plan = payload.plan ?? "free";
1302
+ const email = payload.sub ?? "";
1303
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
1304
+ return {
1305
+ valid: true,
1306
+ plan,
1307
+ email,
1308
+ expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
1309
+ deviceLimit: limits.devices,
1310
+ employeeLimit: limits.employees,
1311
+ memoryLimit: limits.memories
1312
+ };
1313
+ } catch {
1314
+ return null;
1315
+ }
1316
+ }
1317
+ async function getCachedLicense() {
1318
+ try {
1319
+ if (!existsSync8(CACHE_PATH)) return null;
1320
+ const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
1321
+ if (!raw.token || typeof raw.token !== "string") return null;
1322
+ return await verifyLicenseJwt(raw.token);
1323
+ } catch {
1324
+ return null;
1325
+ }
1326
+ }
1327
+ function readCachedLicenseToken() {
1328
+ try {
1329
+ if (!existsSync8(CACHE_PATH)) return null;
1330
+ const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
1331
+ return typeof raw.token === "string" ? raw.token : null;
1332
+ } catch {
1333
+ return null;
1334
+ }
1335
+ }
1336
+ function getRawCachedPlan() {
1337
+ try {
1338
+ const token = readCachedLicenseToken();
1339
+ if (!token) return null;
1340
+ const parts = token.split(".");
1341
+ if (parts.length !== 3) return null;
1342
+ const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
1343
+ const plan = payload.plan ?? "free";
1344
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
1345
+ process.stderr.write(
1346
+ `[license] WARN: using unverified cached plan (API unreachable, JWT expired). Plan: ${plan}
1347
+ `
1348
+ );
1349
+ return {
1350
+ valid: true,
1351
+ plan,
1352
+ email: payload.sub ?? "",
1353
+ expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
1354
+ deviceLimit: limits.devices,
1355
+ employeeLimit: limits.employees,
1356
+ memoryLimit: limits.memories
1357
+ };
1358
+ } catch {
1359
+ return null;
1360
+ }
1361
+ }
1362
+ function cacheResponse(token) {
1363
+ try {
1364
+ writeFileSync5(CACHE_PATH, JSON.stringify({ token }), "utf8");
1365
+ } catch {
1366
+ }
1367
+ }
1368
+ function loadPrismaForLicense() {
1369
+ if (_prismaFailed) return null;
1370
+ const dbUrl = process.env.DATABASE_URL;
1371
+ if (!dbUrl) {
1372
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path7.join(os6.homedir(), "exe-db");
1373
+ if (!existsSync8(path7.join(exeDbRoot, "package.json"))) {
1374
+ _prismaFailed = true;
1375
+ return null;
1376
+ }
1377
+ }
1378
+ if (!_prismaPromise) {
1379
+ _prismaPromise = (async () => {
1380
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
1381
+ if (explicitPath) {
1382
+ const mod2 = await import(pathToFileURL2(explicitPath).href);
1383
+ const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
1384
+ if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
1385
+ return new Ctor2();
1386
+ }
1387
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path7.join(os6.homedir(), "exe-db");
1388
+ const req = createRequire2(path7.join(exeDbRoot, "package.json"));
1389
+ const entry = req.resolve("@prisma/client");
1390
+ const mod = await import(pathToFileURL2(entry).href);
1391
+ const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
1392
+ if (!Ctor) throw new Error(`No PrismaClient in ${entry}`);
1393
+ return new Ctor();
1394
+ })().catch((err) => {
1395
+ _prismaFailed = true;
1396
+ _prismaPromise = null;
1397
+ throw err;
1398
+ });
1399
+ }
1400
+ return _prismaPromise;
1401
+ }
1402
+ async function validateViaPostgres(apiKey) {
1403
+ const loader = loadPrismaForLicense();
1404
+ if (!loader) return null;
1405
+ try {
1406
+ const prisma = await loader;
1407
+ const rows = await prisma.$queryRawUnsafe(
1408
+ `SELECT plan, email, status, device_limit, employee_limit, memory_limit, expires_at
1409
+ FROM billing.licenses WHERE key = $1 LIMIT 1`,
1410
+ apiKey
1411
+ );
1412
+ if (!rows || rows.length === 0) return null;
1413
+ const row = rows[0];
1414
+ if (row.status !== "active") return null;
1415
+ if (row.expires_at && new Date(row.expires_at) < /* @__PURE__ */ new Date()) return null;
1416
+ const plan = row.plan;
1417
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
1418
+ return {
1419
+ valid: true,
1420
+ plan,
1421
+ email: row.email,
1422
+ expiresAt: row.expires_at ? new Date(row.expires_at).toISOString() : null,
1423
+ deviceLimit: row.device_limit ?? limits.devices,
1424
+ employeeLimit: row.employee_limit ?? limits.employees,
1425
+ memoryLimit: row.memory_limit ?? limits.memories
1426
+ };
1427
+ } catch {
1428
+ return null;
1429
+ }
1430
+ }
1431
+ async function validateViaCFWorker(apiKey, deviceId) {
1432
+ try {
1433
+ const res = await fetchRetry(`${API_BASE}/auth/activate`, {
1434
+ method: "POST",
1435
+ headers: { "Content-Type": "application/json" },
1436
+ body: JSON.stringify({ apiKey, deviceId }),
1437
+ signal: AbortSignal.timeout(1e4)
1438
+ });
1439
+ if (!res.ok) return null;
1440
+ const data = await res.json();
1441
+ if (data.error === "device_limit_exceeded") return null;
1442
+ if (!data.valid) return null;
1443
+ if (data.token) {
1444
+ cacheResponse(data.token);
1445
+ const verified = await verifyLicenseJwt(data.token);
1446
+ if (verified) return verified;
1447
+ }
1448
+ const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
1449
+ return {
1450
+ valid: data.valid,
1451
+ plan: data.plan,
1452
+ email: data.email,
1453
+ expiresAt: data.expiresAt,
1454
+ deviceLimit: limits.devices,
1455
+ employeeLimit: limits.employees,
1456
+ memoryLimit: limits.memories
1457
+ };
1458
+ } catch {
1459
+ return null;
1460
+ }
1461
+ }
1462
+ async function validateLicense(apiKey, deviceId) {
1463
+ const did = deviceId ?? loadDeviceId();
1464
+ const pgResult = await validateViaPostgres(apiKey);
1465
+ if (pgResult) {
1466
+ try {
1467
+ writeFileSync5(CACHE_PATH, JSON.stringify({ pgLicense: pgResult, ts: Date.now() }), "utf8");
1468
+ } catch {
1469
+ }
1470
+ return pgResult;
1471
+ }
1472
+ const cfResult = await validateViaCFWorker(apiKey, did);
1473
+ if (cfResult) return cfResult;
1474
+ const cached = await getCachedLicense();
1475
+ if (cached) return cached;
1476
+ try {
1477
+ if (existsSync8(CACHE_PATH)) {
1478
+ const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
1479
+ if (raw.pgLicense && raw.ts && Date.now() - raw.ts < 7 * 24 * 60 * 60 * 1e3) {
1480
+ return raw.pgLicense;
1481
+ }
1482
+ }
1483
+ } catch {
1484
+ }
1485
+ const rawFallback = getRawCachedPlan();
1486
+ if (rawFallback) return rawFallback;
1487
+ return { ...FREE_LICENSE, valid: false };
1488
+ }
1489
+ function getCacheAgeMs() {
1490
+ try {
1491
+ const { statSync: statSync3 } = __require("fs");
1492
+ const s = statSync3(CACHE_PATH);
1493
+ return Date.now() - s.mtimeMs;
1494
+ } catch {
1495
+ return Infinity;
1496
+ }
1497
+ }
1498
+ async function checkLicense() {
1499
+ let key = loadLicense();
1500
+ if (!key) {
1501
+ try {
1502
+ const configPath = path7.join(EXE_AI_DIR, "config.json");
1503
+ if (existsSync8(configPath)) {
1504
+ const raw = JSON.parse(readFileSync6(configPath, "utf8"));
1505
+ const cloud = raw.cloud;
1506
+ if (cloud?.apiKey) {
1507
+ key = cloud.apiKey;
1508
+ saveLicense(key);
1509
+ }
1510
+ }
1511
+ } catch {
1512
+ }
1513
+ }
1514
+ if (!key) return FREE_LICENSE;
1515
+ const cached = await getCachedLicense();
1516
+ if (cached && getCacheAgeMs() < CACHE_MAX_AGE_MS) return cached;
1517
+ const deviceId = loadDeviceId();
1518
+ return validateLicense(key, deviceId);
1519
+ }
1520
+ function isFeatureAllowed(license, feature) {
1521
+ switch (feature) {
1522
+ case "cloud_sync":
1523
+ case "external_agents":
1524
+ case "wiki":
1525
+ return license.plan !== "free";
1526
+ case "unlimited_employees":
1527
+ return license.plan === "team" || license.plan === "agency" || license.plan === "enterprise";
1528
+ }
1529
+ }
1530
+ function mirrorLicenseKey(apiKey) {
1531
+ const trimmed = apiKey.trim();
1532
+ if (!trimmed) return;
1533
+ saveLicense(trimmed);
1534
+ }
1535
+ async function assertVpsLicense(opts) {
1536
+ const env = opts?.env ?? process.env;
1537
+ const inProduction = env.NODE_ENV === "production";
1538
+ if (!opts?.force && !inProduction) {
1539
+ return { ...FREE_LICENSE, plan: "free" };
1540
+ }
1541
+ const envKey = env.EXE_LICENSE_KEY?.trim();
1542
+ if (envKey) {
1543
+ saveLicense(envKey);
1544
+ }
1545
+ const apiKey = envKey ?? loadLicense();
1546
+ if (!apiKey) {
1547
+ throw new Error(
1548
+ "License required: set EXE_LICENSE_KEY env var with your exe_sk_* key. Purchase at https://askexe.com. This VPS image refuses to boot without a valid license."
1549
+ );
1550
+ }
1551
+ const deviceId = loadDeviceId();
1552
+ let backendResponse = null;
1553
+ let explicitRejection = false;
1554
+ let transientFailure = false;
1555
+ try {
1556
+ const res = await fetchRetry(`${API_BASE}/auth/activate`, {
1557
+ method: "POST",
1558
+ headers: { "Content-Type": "application/json" },
1559
+ body: JSON.stringify({ apiKey, deviceId }),
1560
+ signal: AbortSignal.timeout(1e4)
1561
+ });
1562
+ if (res.ok) {
1563
+ backendResponse = await res.json();
1564
+ if (!backendResponse.valid) explicitRejection = true;
1565
+ } else if (res.status === 401 || res.status === 403) {
1566
+ explicitRejection = true;
1567
+ } else {
1568
+ transientFailure = true;
1569
+ }
1570
+ } catch {
1571
+ transientFailure = true;
1572
+ }
1573
+ if (backendResponse?.valid) {
1574
+ if (backendResponse.token) {
1575
+ cacheResponse(backendResponse.token);
1576
+ const verified = await verifyLicenseJwt(backendResponse.token);
1577
+ if (verified) return verified;
1578
+ }
1579
+ const limits = PLAN_LIMITS[backendResponse.plan] ?? PLAN_LIMITS.free;
1580
+ return {
1581
+ valid: true,
1582
+ plan: backendResponse.plan,
1583
+ email: backendResponse.email,
1584
+ expiresAt: backendResponse.expiresAt,
1585
+ deviceLimit: limits.devices,
1586
+ employeeLimit: limits.employees,
1587
+ memoryLimit: limits.memories
1588
+ };
1589
+ }
1590
+ if (explicitRejection) {
1591
+ throw new Error(
1592
+ `License invalid or expired. Renew at https://askexe.com, then restart. Backend rejected key ending in ...${apiKey.slice(-4)}.`
1593
+ );
1594
+ }
1595
+ if (!transientFailure) {
1596
+ throw new Error(
1597
+ "License validation failed: unknown backend state. Restore network connectivity to https://cloud.askexe.com and retry."
1598
+ );
1599
+ }
1600
+ const fresh = await getCachedLicense();
1601
+ if (fresh && fresh.valid) return fresh;
1602
+ const graceDays = opts?.offlineGraceDays ?? 7;
1603
+ const graceMs = graceDays * 24 * 60 * 60 * 1e3;
1604
+ try {
1605
+ const token = readCachedLicenseToken();
1606
+ if (token) {
1607
+ const payloadB64 = token.split(".")[1];
1608
+ if (payloadB64) {
1609
+ const payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString());
1610
+ const expMs = (payload.exp ?? 0) * 1e3;
1611
+ if (Date.now() < expMs + graceMs) {
1612
+ const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
1613
+ const { payload: verified } = await jwtVerify(token, key, {
1614
+ algorithms: [LICENSE_JWT_ALG],
1615
+ clockTolerance: graceDays * 24 * 60 * 60
1616
+ });
1617
+ const plan = verified.plan ?? "free";
1618
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
1619
+ return {
1620
+ valid: true,
1621
+ plan,
1622
+ email: verified.sub ?? "",
1623
+ expiresAt: verified.exp ? new Date(verified.exp * 1e3).toISOString() : null,
1624
+ deviceLimit: limits.devices,
1625
+ employeeLimit: limits.employees,
1626
+ memoryLimit: limits.memories
1627
+ };
1628
+ }
1629
+ }
1630
+ }
1631
+ } catch {
1632
+ }
1633
+ throw new Error(
1634
+ `License validation unreachable for more than ${graceDays} days. Restore network connectivity to https://cloud.askexe.com and retry. This VPS image refuses to boot after the offline grace window.`
1635
+ );
1636
+ }
1637
+ function startLicenseRevalidation(intervalMs = 36e5) {
1638
+ if (_revalTimer) return;
1639
+ _revalTimer = setInterval(async () => {
1640
+ try {
1641
+ const license = await checkLicense();
1642
+ if (!license.valid) {
1643
+ process.stderr.write("[exe-os] License expired or invalid \u2014 features may be restricted\n");
1644
+ }
1645
+ } catch {
1646
+ }
1647
+ }, intervalMs);
1648
+ if (_revalTimer && typeof _revalTimer === "object" && "unref" in _revalTimer) {
1649
+ _revalTimer.unref();
1650
+ }
1651
+ }
1652
+ function stopLicenseRevalidation() {
1653
+ if (_revalTimer) {
1654
+ clearInterval(_revalTimer);
1655
+ _revalTimer = null;
1656
+ }
1657
+ }
1658
+ var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, RETRY_DELAY_MS, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE, _prismaPromise, _prismaFailed, CACHE_MAX_AGE_MS, _revalTimer;
986
1659
  var init_license = __esm({
987
1660
  "src/lib/license.ts"() {
988
1661
  "use strict";
@@ -990,7 +1663,13 @@ var init_license = __esm({
990
1663
  LICENSE_PATH = path7.join(EXE_AI_DIR, "license.key");
991
1664
  CACHE_PATH = path7.join(EXE_AI_DIR, "license-cache.json");
992
1665
  DEVICE_ID_PATH = path7.join(EXE_AI_DIR, "device-id");
993
- API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com/cloud";
1666
+ API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://cloud.askexe.com";
1667
+ RETRY_DELAY_MS = 500;
1668
+ LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
1669
+ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
1670
+ 4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
1671
+ -----END PUBLIC KEY-----`;
1672
+ LICENSE_JWT_ALG = "ES256";
994
1673
  PLAN_LIMITS = {
995
1674
  free: { devices: 1, employees: 1, memories: 5e3 },
996
1675
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -998,6 +1677,19 @@ var init_license = __esm({
998
1677
  agency: { devices: 50, employees: 100, memories: 1e7 },
999
1678
  enterprise: { devices: -1, employees: -1, memories: -1 }
1000
1679
  };
1680
+ FREE_LICENSE = {
1681
+ valid: true,
1682
+ plan: "free",
1683
+ email: "",
1684
+ expiresAt: null,
1685
+ deviceLimit: 1,
1686
+ employeeLimit: 1,
1687
+ memoryLimit: 5e3
1688
+ };
1689
+ _prismaPromise = null;
1690
+ _prismaFailed = false;
1691
+ CACHE_MAX_AGE_MS = 36e5;
1692
+ _revalTimer = null;
1001
1693
  }
1002
1694
  });
1003
1695
 
@@ -1997,11 +2689,12 @@ function getDispatchedBy(sessionKey) {
1997
2689
  }
1998
2690
  }
1999
2691
  function resolveExeSession() {
2692
+ if (process.env.EXE_SESSION_NAME) {
2693
+ const fromEnv = extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
2694
+ if (fromEnv) return fromEnv;
2695
+ }
2000
2696
  const mySession = getMySession();
2001
2697
  if (!mySession) {
2002
- if (process.env.EXE_SESSION_NAME) {
2003
- return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
2004
- }
2005
2698
  return null;
2006
2699
  }
2007
2700
  const fromSessionName = extractRootExe(mySession);
@@ -2016,6 +2709,10 @@ function resolveExeSession() {
2016
2709
  `[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
2017
2710
  `
2018
2711
  );
2712
+ try {
2713
+ registerParentExe(key, fromSessionName);
2714
+ } catch {
2715
+ }
2019
2716
  candidate = fromSessionName;
2020
2717
  } else {
2021
2718
  candidate = fromCache;
@@ -2969,6 +3666,19 @@ async function resolveTask(client, identifier, scopeSession) {
2969
3666
  args: [identifier, ...scope.args]
2970
3667
  });
2971
3668
  if (result.rows.length === 1) return result.rows[0];
3669
+ if (/^[a-f0-9]{7,12}$/i.test(identifier)) {
3670
+ result = await client.execute({
3671
+ sql: `SELECT * FROM tasks WHERE id LIKE ?`,
3672
+ args: [`${identifier}%`]
3673
+ });
3674
+ if (result.rows.length === 1) return result.rows[0];
3675
+ if (result.rows.length > 1) {
3676
+ const matches = result.rows.map((r) => `${String(r.id)} "${String(r.title)}" (${String(r.status)})`).join(", ");
3677
+ throw new Error(
3678
+ `Multiple tasks match short-ID "${identifier}": ${matches}. Use a longer prefix to disambiguate.`
3679
+ );
3680
+ }
3681
+ }
2972
3682
  result = await client.execute({
2973
3683
  sql: `SELECT * FROM tasks WHERE task_file LIKE ?${scope.sql}`,
2974
3684
  args: [`%${identifier}%`, ...scope.args]
@@ -3515,12 +4225,13 @@ async function cascadeUnblock(taskId, baseDir, now) {
3515
4225
  WHERE blocked_by = ? AND status = 'blocked'`,
3516
4226
  args: [now, taskId]
3517
4227
  });
3518
- if (baseDir && unblocked.rowsAffected > 0) {
3519
- const ubScope = sessionScopeFilter();
3520
- const unblockedRows = await client.execute({
3521
- sql: `SELECT task_file FROM tasks WHERE blocked_by IS NULL AND updated_at = ?${ubScope.sql}`,
3522
- args: [now, ...ubScope.args]
3523
- });
4228
+ if (unblocked.rowsAffected === 0) return;
4229
+ const ubScope = sessionScopeFilter();
4230
+ const unblockedRows = await client.execute({
4231
+ sql: `SELECT id, title, assigned_to, priority, task_file FROM tasks WHERE blocked_by IS NULL AND updated_at = ?${ubScope.sql}`,
4232
+ args: [now, ...ubScope.args]
4233
+ });
4234
+ if (baseDir) {
3524
4235
  for (const ur of unblockedRows.rows) {
3525
4236
  try {
3526
4237
  const ubFile = path15.join(baseDir, String(ur.task_file));
@@ -3532,6 +4243,19 @@ async function cascadeUnblock(taskId, baseDir, now) {
3532
4243
  }
3533
4244
  }
3534
4245
  }
4246
+ if (unblockedRows.rows.length > 0 && !process.env.VITEST) {
4247
+ try {
4248
+ const { queueIntercom: queueIntercom2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
4249
+ const dispatched = /* @__PURE__ */ new Set();
4250
+ for (const ur of unblockedRows.rows) {
4251
+ const assignee = String(ur.assigned_to);
4252
+ if (dispatched.has(assignee)) continue;
4253
+ dispatched.add(assignee);
4254
+ queueIntercom2(`${assignee}`, `unblocked: "${String(ur.title)}" is now ready`);
4255
+ }
4256
+ } catch {
4257
+ }
4258
+ }
3535
4259
  }
3536
4260
  async function findNextTask(assignedTo) {
3537
4261
  const client = getClient();
@@ -4190,6 +4914,15 @@ var init_embedder = __esm({
4190
4914
  // src/lib/behaviors.ts
4191
4915
  import crypto5 from "crypto";
4192
4916
  async function storeBehavior(opts) {
4917
+ try {
4918
+ const { loadEmployeesSync: loadEmployeesSync2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
4919
+ const roster = loadEmployeesSync2();
4920
+ if (roster.length > 0 && !roster.some((e) => e.name === opts.agentId)) {
4921
+ throw new Error(`Agent "${opts.agentId}" not found in roster. Cannot store behavior for unregistered agent.`);
4922
+ }
4923
+ } catch (e) {
4924
+ if (e instanceof Error && e.message.includes("not found in roster")) throw e;
4925
+ }
4193
4926
  const client = getClient();
4194
4927
  const id = crypto5.randomUUID();
4195
4928
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -4200,10 +4933,18 @@ async function storeBehavior(opts) {
4200
4933
  vector = new Float32Array(vec);
4201
4934
  } catch {
4202
4935
  }
4936
+ let createdByDevice = null;
4937
+ try {
4938
+ const { loadDeviceId: loadDeviceId2 } = await Promise.resolve().then(() => (init_license(), license_exports));
4939
+ createdByDevice = loadDeviceId2() ?? null;
4940
+ } catch {
4941
+ }
4942
+ const createdByAgent = process.env.AGENT_ID ?? null;
4943
+ const sourceSessionId = process.env.CLAUDE_SESSION_ID ?? process.env.SESSION_ID ?? null;
4203
4944
  await client.execute({
4204
- sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at, vector)
4205
- VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?)`,
4206
- args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now, vector ? vector.buffer : null]
4945
+ sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at, vector, created_by_agent, created_by_device, source_session_id)
4946
+ VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?)`,
4947
+ args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now, vector ? vector.buffer : null, createdByAgent, createdByDevice, sourceSessionId]
4207
4948
  });
4208
4949
  return id;
4209
4950
  }
@@ -4635,6 +5376,12 @@ async function updateTask(input) {
4635
5376
  }
4636
5377
  }
4637
5378
  }
5379
+ if (input.status === "cancelled") {
5380
+ try {
5381
+ await cascadeUnblock(taskId, input.baseDir, now);
5382
+ } catch {
5383
+ }
5384
+ }
4638
5385
  if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
4639
5386
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
4640
5387
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({