@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
@@ -395,11 +395,168 @@ var init_config = __esm({
395
395
  }
396
396
  });
397
397
 
398
+ // src/lib/runtime-table.ts
399
+ var RUNTIME_TABLE, DEFAULT_RUNTIME;
400
+ var init_runtime_table = __esm({
401
+ "src/lib/runtime-table.ts"() {
402
+ "use strict";
403
+ RUNTIME_TABLE = {
404
+ codex: {
405
+ binary: "codex",
406
+ launchMode: "interactive",
407
+ autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
408
+ inlineFlag: "--no-alt-screen",
409
+ apiKeyEnv: "OPENAI_API_KEY",
410
+ defaultModel: "gpt-5.5"
411
+ },
412
+ opencode: {
413
+ binary: "opencode",
414
+ launchMode: "exec",
415
+ autoApproveFlag: "--dangerously-skip-permissions",
416
+ inlineFlag: "",
417
+ apiKeyEnv: "ANTHROPIC_API_KEY",
418
+ defaultModel: "anthropic/claude-sonnet-4-6"
419
+ }
420
+ };
421
+ DEFAULT_RUNTIME = "claude";
422
+ }
423
+ });
424
+
425
+ // src/lib/agent-config.ts
426
+ var agent_config_exports = {};
427
+ __export(agent_config_exports, {
428
+ AGENT_CONFIG_PATH: () => AGENT_CONFIG_PATH,
429
+ DEFAULT_MODELS: () => DEFAULT_MODELS,
430
+ KNOWN_RUNTIMES: () => KNOWN_RUNTIMES,
431
+ RUNTIME_LABELS: () => RUNTIME_LABELS,
432
+ clearAgentRuntime: () => clearAgentRuntime,
433
+ getAgentRuntime: () => getAgentRuntime,
434
+ loadAgentConfig: () => loadAgentConfig,
435
+ saveAgentConfig: () => saveAgentConfig,
436
+ setAgentMcps: () => setAgentMcps,
437
+ setAgentRuntime: () => setAgentRuntime
438
+ });
439
+ import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
440
+ import path2 from "path";
441
+ function loadAgentConfig() {
442
+ if (!existsSync3(AGENT_CONFIG_PATH)) return {};
443
+ try {
444
+ return JSON.parse(readFileSync2(AGENT_CONFIG_PATH, "utf-8"));
445
+ } catch {
446
+ return {};
447
+ }
448
+ }
449
+ function saveAgentConfig(config) {
450
+ const dir = path2.dirname(AGENT_CONFIG_PATH);
451
+ ensurePrivateDirSync(dir);
452
+ writeFileSync(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
453
+ enforcePrivateFileSync(AGENT_CONFIG_PATH);
454
+ }
455
+ function getAgentRuntime(agentId) {
456
+ const config = loadAgentConfig();
457
+ const entry = config[agentId];
458
+ if (entry) return entry;
459
+ const orgDefault = config["default"];
460
+ if (orgDefault) return orgDefault;
461
+ return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
462
+ }
463
+ function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
464
+ const knownModels = KNOWN_RUNTIMES[runtime];
465
+ if (!knownModels) {
466
+ return {
467
+ ok: false,
468
+ error: `Unknown runtime "${runtime}". Valid: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`
469
+ };
470
+ }
471
+ if (!knownModels.includes(model)) {
472
+ return {
473
+ ok: false,
474
+ error: `Unknown model "${model}" for runtime "${runtime}". Valid: ${knownModels.join(", ")}`
475
+ };
476
+ }
477
+ const config = loadAgentConfig();
478
+ const existing = config[agentId];
479
+ const entry = { runtime, model };
480
+ if (reasoning_effort) entry.reasoning_effort = reasoning_effort;
481
+ if (mcps !== void 0) {
482
+ entry.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
483
+ } else if (existing?.mcps) {
484
+ entry.mcps = existing.mcps;
485
+ }
486
+ config[agentId] = entry;
487
+ saveAgentConfig(config);
488
+ return { ok: true };
489
+ }
490
+ function setAgentMcps(agentId, mcps) {
491
+ const config = loadAgentConfig();
492
+ const existing = config[agentId] ?? getAgentRuntime(agentId);
493
+ existing.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
494
+ config[agentId] = existing;
495
+ saveAgentConfig(config);
496
+ return { ok: true };
497
+ }
498
+ function clearAgentRuntime(agentId) {
499
+ const config = loadAgentConfig();
500
+ delete config[agentId];
501
+ saveAgentConfig(config);
502
+ }
503
+ var AGENT_CONFIG_PATH, KNOWN_RUNTIMES, RUNTIME_LABELS, DEFAULT_MODELS;
504
+ var init_agent_config = __esm({
505
+ "src/lib/agent-config.ts"() {
506
+ "use strict";
507
+ init_config();
508
+ init_runtime_table();
509
+ init_secure_files();
510
+ AGENT_CONFIG_PATH = path2.join(EXE_AI_DIR, "agent-config.json");
511
+ KNOWN_RUNTIMES = {
512
+ claude: ["claude-opus-4.6", "claude-opus-4", "claude-sonnet-4.6", "claude-sonnet-4", "claude-haiku-4.5"],
513
+ codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
514
+ opencode: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4", "google/gemini-2.5-pro", "deepseek/deepseek-r3", "minimax/minimax-m2.5"]
515
+ };
516
+ RUNTIME_LABELS = {
517
+ claude: "Claude Code (Anthropic)",
518
+ codex: "Codex (OpenAI)",
519
+ opencode: "OpenCode (open source)"
520
+ };
521
+ DEFAULT_MODELS = {
522
+ claude: "claude-opus-4.6",
523
+ codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
524
+ opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
525
+ };
526
+ }
527
+ });
528
+
398
529
  // src/lib/employees.ts
530
+ var employees_exports = {};
531
+ __export(employees_exports, {
532
+ COORDINATOR_ROLE: () => COORDINATOR_ROLE,
533
+ DEFAULT_COORDINATOR_TEMPLATE_NAME: () => DEFAULT_COORDINATOR_TEMPLATE_NAME,
534
+ EMPLOYEES_PATH: () => EMPLOYEES_PATH,
535
+ addEmployee: () => addEmployee,
536
+ baseAgentName: () => baseAgentName,
537
+ canCoordinate: () => canCoordinate,
538
+ getCoordinatorEmployee: () => getCoordinatorEmployee,
539
+ getCoordinatorName: () => getCoordinatorName,
540
+ getEmployee: () => getEmployee,
541
+ getEmployeeByRole: () => getEmployeeByRole,
542
+ getEmployeeNamesByRole: () => getEmployeeNamesByRole,
543
+ hasRole: () => hasRole,
544
+ hireEmployee: () => hireEmployee,
545
+ isCoordinatorName: () => isCoordinatorName,
546
+ isCoordinatorRole: () => isCoordinatorRole,
547
+ isMultiInstance: () => isMultiInstance,
548
+ loadEmployees: () => loadEmployees,
549
+ loadEmployeesSync: () => loadEmployeesSync,
550
+ normalizeRole: () => normalizeRole,
551
+ normalizeRosterCase: () => normalizeRosterCase,
552
+ registerBinSymlinks: () => registerBinSymlinks,
553
+ saveEmployees: () => saveEmployees,
554
+ validateEmployeeName: () => validateEmployeeName
555
+ });
399
556
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
400
- import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
557
+ import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
401
558
  import { execSync } from "child_process";
402
- import path2 from "path";
559
+ import path3 from "path";
403
560
  import os2 from "os";
404
561
  function normalizeRole(role) {
405
562
  return (role ?? "").trim().toLowerCase();
@@ -417,8 +574,26 @@ function isCoordinatorName(agentName2, employees = loadEmployeesSync()) {
417
574
  if (!agentName2) return false;
418
575
  return agentName2.toLowerCase() === getCoordinatorName(employees).toLowerCase();
419
576
  }
577
+ function canCoordinate(agentName2, agentRole, employees = loadEmployeesSync()) {
578
+ return agentName2 === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName2, employees);
579
+ }
580
+ function validateEmployeeName(name) {
581
+ if (!name) {
582
+ return { valid: false, error: "Name is required" };
583
+ }
584
+ if (name.length > 32) {
585
+ return { valid: false, error: "Name must be 32 characters or fewer" };
586
+ }
587
+ if (!/^[a-z][a-z0-9]*$/.test(name)) {
588
+ return {
589
+ valid: false,
590
+ error: "Name must start with a letter and contain only lowercase alphanumeric characters"
591
+ };
592
+ }
593
+ return { valid: true };
594
+ }
420
595
  async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
421
- if (!existsSync3(employeesPath)) {
596
+ if (!existsSync4(employeesPath)) {
422
597
  return [];
423
598
  }
424
599
  const raw = await readFile2(employeesPath, "utf-8");
@@ -428,10 +603,14 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
428
603
  return [];
429
604
  }
430
605
  }
606
+ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
607
+ await mkdir2(path3.dirname(employeesPath), { recursive: true });
608
+ await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
609
+ }
431
610
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
432
- if (!existsSync3(employeesPath)) return [];
611
+ if (!existsSync4(employeesPath)) return [];
433
612
  try {
434
- return JSON.parse(readFileSync2(employeesPath, "utf-8"));
613
+ return JSON.parse(readFileSync3(employeesPath, "utf-8"));
435
614
  } catch {
436
615
  return [];
437
616
  }
@@ -439,6 +618,19 @@ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
439
618
  function getEmployee(employees, name) {
440
619
  return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
441
620
  }
621
+ function getEmployeeByRole(employees, role) {
622
+ const lower = role.toLowerCase();
623
+ return employees.find((e) => e.role.toLowerCase() === lower);
624
+ }
625
+ function getEmployeeNamesByRole(employees, role) {
626
+ const lower = role.toLowerCase();
627
+ return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
628
+ }
629
+ function hasRole(agentName2, role) {
630
+ const employees = loadEmployeesSync();
631
+ const emp = getEmployee(employees, agentName2);
632
+ return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
633
+ }
442
634
  function baseAgentName(name, employees) {
443
635
  const match = name.match(/^([a-zA-Z]+)\d+$/);
444
636
  if (!match) return name;
@@ -453,22 +645,147 @@ function isMultiInstance(agentName2, employees) {
453
645
  if (!emp) return false;
454
646
  return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
455
647
  }
456
- var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR;
648
+ function addEmployee(employees, employee) {
649
+ const { systemPrompt: _legacyPrompt, ...rest } = employee;
650
+ const normalized = { ...rest, name: employee.name.toLowerCase() };
651
+ if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
652
+ throw new Error(`Employee '${normalized.name}' already exists`);
653
+ }
654
+ return [...employees, normalized];
655
+ }
656
+ function appendToCoordinatorTeam(employee) {
657
+ const coordinator = getCoordinatorEmployee(loadEmployeesSync());
658
+ if (!coordinator) return;
659
+ const idPath = path3.join(IDENTITY_DIR, `${coordinator.name}.md`);
660
+ if (!existsSync4(idPath)) return;
661
+ const content = readFileSync3(idPath, "utf-8");
662
+ if (content.includes(`**${capitalize(employee.name)}`)) return;
663
+ const teamMatch = content.match(TEAM_SECTION_RE);
664
+ if (!teamMatch || teamMatch.index === void 0) return;
665
+ const afterTeam = content.slice(teamMatch.index + teamMatch[0].length);
666
+ const nextHeading = afterTeam.match(/\n## /);
667
+ const entry = `
668
+ **${capitalize(employee.name)} (${employee.role}):** Newly hired. Update this description as the role develops.
669
+ `;
670
+ let updated;
671
+ if (nextHeading && nextHeading.index !== void 0) {
672
+ const insertAt = teamMatch.index + teamMatch[0].length + nextHeading.index;
673
+ updated = content.slice(0, insertAt) + entry + content.slice(insertAt);
674
+ } else {
675
+ updated = content.trimEnd() + "\n" + entry;
676
+ }
677
+ writeFileSync2(idPath, updated, "utf-8");
678
+ }
679
+ function capitalize(s) {
680
+ return s.charAt(0).toUpperCase() + s.slice(1);
681
+ }
682
+ async function hireEmployee(employee) {
683
+ const employees = await loadEmployees();
684
+ const updated = addEmployee(employees, employee);
685
+ await saveEmployees(updated);
686
+ try {
687
+ appendToCoordinatorTeam(employee);
688
+ } catch {
689
+ }
690
+ try {
691
+ const { loadAgentConfig: loadAgentConfig2, saveAgentConfig: saveAgentConfig2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
692
+ const config = loadAgentConfig2();
693
+ const name = employee.name.toLowerCase();
694
+ if (!config[name] && config["default"]) {
695
+ config[name] = { ...config["default"] };
696
+ saveAgentConfig2(config);
697
+ }
698
+ } catch {
699
+ }
700
+ return updated;
701
+ }
702
+ async function normalizeRosterCase(rosterPath) {
703
+ const employees = await loadEmployees(rosterPath);
704
+ let changed = false;
705
+ for (const emp of employees) {
706
+ if (emp.name !== emp.name.toLowerCase()) {
707
+ const oldName = emp.name;
708
+ emp.name = emp.name.toLowerCase();
709
+ changed = true;
710
+ try {
711
+ const identityDir = path3.join(os2.homedir(), ".exe-os", "identity");
712
+ const oldPath = path3.join(identityDir, `${oldName}.md`);
713
+ const newPath = path3.join(identityDir, `${emp.name}.md`);
714
+ if (existsSync4(oldPath) && !existsSync4(newPath)) {
715
+ renameSync2(oldPath, newPath);
716
+ } else if (existsSync4(oldPath) && oldPath !== newPath) {
717
+ const content = readFileSync3(oldPath, "utf-8");
718
+ writeFileSync2(newPath, content, "utf-8");
719
+ if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
720
+ unlinkSync(oldPath);
721
+ }
722
+ }
723
+ } catch {
724
+ }
725
+ }
726
+ }
727
+ if (changed) {
728
+ await saveEmployees(employees, rosterPath);
729
+ }
730
+ return changed;
731
+ }
732
+ function findExeBin() {
733
+ try {
734
+ return execSync(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
735
+ } catch {
736
+ return null;
737
+ }
738
+ }
739
+ function registerBinSymlinks(name) {
740
+ const created = [];
741
+ const skipped = [];
742
+ const errors = [];
743
+ const exeBinPath = findExeBin();
744
+ if (!exeBinPath) {
745
+ errors.push("Could not find 'exe-os' in PATH");
746
+ return { created, skipped, errors };
747
+ }
748
+ const binDir = path3.dirname(exeBinPath);
749
+ let target;
750
+ try {
751
+ target = readlinkSync(exeBinPath);
752
+ } catch {
753
+ errors.push("Could not read 'exe' symlink");
754
+ return { created, skipped, errors };
755
+ }
756
+ for (const suffix of ["", "-opencode"]) {
757
+ const linkName = `${name}${suffix}`;
758
+ const linkPath = path3.join(binDir, linkName);
759
+ if (existsSync4(linkPath)) {
760
+ skipped.push(linkName);
761
+ continue;
762
+ }
763
+ try {
764
+ symlinkSync(target, linkPath);
765
+ created.push(linkName);
766
+ } catch (err) {
767
+ errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
768
+ }
769
+ }
770
+ return { created, skipped, errors };
771
+ }
772
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR, TEAM_SECTION_RE;
457
773
  var init_employees = __esm({
458
774
  "src/lib/employees.ts"() {
459
775
  "use strict";
460
776
  init_config();
461
- EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
777
+ EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
462
778
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
463
779
  COORDINATOR_ROLE = "COO";
464
780
  MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
465
- IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
781
+ IDENTITY_DIR = path3.join(EXE_AI_DIR, "identity");
782
+ TEAM_SECTION_RE = /^## Team\b.*$/m;
466
783
  }
467
784
  });
468
785
 
469
786
  // src/lib/database-adapter.ts
470
787
  import os3 from "os";
471
- import path3 from "path";
788
+ import path4 from "path";
472
789
  import { createRequire } from "module";
473
790
  import { pathToFileURL } from "url";
474
791
  function quotedIdentifier(identifier) {
@@ -779,8 +1096,8 @@ async function loadPrismaClient() {
779
1096
  }
780
1097
  return new PrismaClient2();
781
1098
  }
782
- const exeDbRoot = process.env.EXE_DB_ROOT ?? path3.join(os3.homedir(), "exe-db");
783
- const requireFromExeDb = createRequire(path3.join(exeDbRoot, "package.json"));
1099
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path4.join(os3.homedir(), "exe-db");
1100
+ const requireFromExeDb = createRequire(path4.join(exeDbRoot, "package.json"));
784
1101
  const prismaEntry = requireFromExeDb.resolve("@prisma/client");
785
1102
  const module = await import(pathToFileURL(prismaEntry).href);
786
1103
  const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
@@ -1052,8 +1369,8 @@ var init_database_adapter = __esm({
1052
1369
 
1053
1370
  // src/lib/daemon-auth.ts
1054
1371
  import crypto from "crypto";
1055
- import path4 from "path";
1056
- import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
1372
+ import path5 from "path";
1373
+ import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
1057
1374
  function normalizeToken(token) {
1058
1375
  if (!token) return null;
1059
1376
  const trimmed = token.trim();
@@ -1061,8 +1378,8 @@ function normalizeToken(token) {
1061
1378
  }
1062
1379
  function readDaemonToken() {
1063
1380
  try {
1064
- if (!existsSync4(DAEMON_TOKEN_PATH)) return null;
1065
- return normalizeToken(readFileSync3(DAEMON_TOKEN_PATH, "utf8"));
1381
+ if (!existsSync5(DAEMON_TOKEN_PATH)) return null;
1382
+ return normalizeToken(readFileSync4(DAEMON_TOKEN_PATH, "utf8"));
1066
1383
  } catch {
1067
1384
  return null;
1068
1385
  }
@@ -1072,7 +1389,7 @@ function ensureDaemonToken(seed) {
1072
1389
  if (existing) return existing;
1073
1390
  const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
1074
1391
  ensurePrivateDirSync(EXE_AI_DIR);
1075
- writeFileSync2(DAEMON_TOKEN_PATH, `${token}
1392
+ writeFileSync3(DAEMON_TOKEN_PATH, `${token}
1076
1393
  `, "utf8");
1077
1394
  enforcePrivateFileSync(DAEMON_TOKEN_PATH);
1078
1395
  return token;
@@ -1083,7 +1400,7 @@ var init_daemon_auth = __esm({
1083
1400
  "use strict";
1084
1401
  init_config();
1085
1402
  init_secure_files();
1086
- DAEMON_TOKEN_PATH = path4.join(EXE_AI_DIR, "exed.token");
1403
+ DAEMON_TOKEN_PATH = path5.join(EXE_AI_DIR, "exed.token");
1087
1404
  }
1088
1405
  });
1089
1406
 
@@ -1092,8 +1409,8 @@ import net from "net";
1092
1409
  import os4 from "os";
1093
1410
  import { spawn, execSync as execSync2 } from "child_process";
1094
1411
  import { randomUUID } from "crypto";
1095
- import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
1096
- import path5 from "path";
1412
+ import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync5, openSync, closeSync, statSync } from "fs";
1413
+ import path6 from "path";
1097
1414
  import { fileURLToPath } from "url";
1098
1415
  function handleData(chunk) {
1099
1416
  _buffer += chunk.toString();
@@ -1129,9 +1446,9 @@ function isZombie(pid) {
1129
1446
  }
1130
1447
  }
1131
1448
  function cleanupStaleFiles() {
1132
- if (existsSync5(PID_PATH)) {
1449
+ if (existsSync6(PID_PATH)) {
1133
1450
  try {
1134
- const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
1451
+ const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
1135
1452
  if (pid > 0) {
1136
1453
  try {
1137
1454
  process.kill(pid, 0);
@@ -1156,11 +1473,11 @@ function cleanupStaleFiles() {
1156
1473
  }
1157
1474
  }
1158
1475
  function findPackageRoot() {
1159
- let dir = path5.dirname(fileURLToPath(import.meta.url));
1160
- const { root } = path5.parse(dir);
1476
+ let dir = path6.dirname(fileURLToPath(import.meta.url));
1477
+ const { root } = path6.parse(dir);
1161
1478
  while (dir !== root) {
1162
- if (existsSync5(path5.join(dir, "package.json"))) return dir;
1163
- dir = path5.dirname(dir);
1479
+ if (existsSync6(path6.join(dir, "package.json"))) return dir;
1480
+ dir = path6.dirname(dir);
1164
1481
  }
1165
1482
  return null;
1166
1483
  }
@@ -1178,8 +1495,8 @@ function spawnDaemon() {
1178
1495
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
1179
1496
  return;
1180
1497
  }
1181
- const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1182
- if (!existsSync5(daemonPath)) {
1498
+ const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1499
+ if (!existsSync6(daemonPath)) {
1183
1500
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
1184
1501
  `);
1185
1502
  return;
@@ -1188,7 +1505,7 @@ function spawnDaemon() {
1188
1505
  const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
1189
1506
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
1190
1507
  `);
1191
- const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
1508
+ const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
1192
1509
  let stderrFd = "ignore";
1193
1510
  try {
1194
1511
  stderrFd = openSync(logPath, "a");
@@ -1353,9 +1670,9 @@ function killAndRespawnDaemon() {
1353
1670
  }
1354
1671
  try {
1355
1672
  process.stderr.write("[exed-client] Killing daemon for restart...\n");
1356
- if (existsSync5(PID_PATH)) {
1673
+ if (existsSync6(PID_PATH)) {
1357
1674
  try {
1358
- const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
1675
+ const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
1359
1676
  if (pid > 0) {
1360
1677
  try {
1361
1678
  process.kill(pid, "SIGKILL");
@@ -1481,9 +1798,9 @@ var init_exe_daemon_client = __esm({
1481
1798
  "use strict";
1482
1799
  init_config();
1483
1800
  init_daemon_auth();
1484
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path5.join(EXE_AI_DIR, "exed.sock");
1485
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path5.join(EXE_AI_DIR, "exed.pid");
1486
- SPAWN_LOCK_PATH = path5.join(EXE_AI_DIR, "exed-spawn.lock");
1801
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
1802
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
1803
+ SPAWN_LOCK_PATH = path6.join(EXE_AI_DIR, "exed-spawn.lock");
1487
1804
  SPAWN_LOCK_STALE_MS = 3e4;
1488
1805
  CONNECT_TIMEOUT_MS = 15e3;
1489
1806
  REQUEST_TIMEOUT_MS = 3e4;
@@ -1716,7 +2033,7 @@ __export(database_exports, {
1716
2033
  isInitialized: () => isInitialized,
1717
2034
  setExternalClient: () => setExternalClient
1718
2035
  });
1719
- import { chmodSync as chmodSync2, existsSync as existsSync6, statSync as statSync2, copyFileSync, unlinkSync as unlinkSync3, openSync as openSync2, closeSync as closeSync2, mkdirSync as mkdirSync2 } from "fs";
2036
+ import { chmodSync as chmodSync2, existsSync as existsSync7, statSync as statSync2, copyFileSync, unlinkSync as unlinkSync3, openSync as openSync2, closeSync as closeSync2, mkdirSync as mkdirSync2 } from "fs";
1720
2037
  import { createClient } from "@libsql/client";
1721
2038
  import { homedir } from "os";
1722
2039
  import { join } from "path";
@@ -1769,11 +2086,11 @@ function releaseDbLock() {
1769
2086
  }
1770
2087
  async function initDatabase(config) {
1771
2088
  acquireDbLock();
1772
- if (existsSync6(config.dbPath)) {
2089
+ if (existsSync7(config.dbPath)) {
1773
2090
  const dbStat = statSync2(config.dbPath);
1774
2091
  if (dbStat.size === 0) {
1775
2092
  const walPath = config.dbPath + "-wal";
1776
- if (existsSync6(walPath) && statSync2(walPath).size > 0) {
2093
+ if (existsSync7(walPath) && statSync2(walPath).size > 0) {
1777
2094
  const backupPath = config.dbPath + ".zeroed-" + Date.now();
1778
2095
  copyFileSync(config.dbPath, backupPath);
1779
2096
  unlinkSync3(config.dbPath);
@@ -2069,6 +2386,13 @@ async function ensureSchema() {
2069
2386
  } catch (e) {
2070
2387
  logCatchDebug("migration", e);
2071
2388
  }
2389
+ for (const col of ["created_by_agent TEXT", "created_by_device TEXT", "source_session_id TEXT"]) {
2390
+ try {
2391
+ await client.execute({ sql: `ALTER TABLE behaviors ADD COLUMN ${col}`, args: [] });
2392
+ } catch (e) {
2393
+ logCatchDebug("migration", e);
2394
+ }
2395
+ }
2072
2396
  try {
2073
2397
  await client.execute({
2074
2398
  sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
@@ -3285,6 +3609,22 @@ async function ensureSchema() {
3285
3609
  } catch (e) {
3286
3610
  logCatchDebug("migration", e);
3287
3611
  }
3612
+ try {
3613
+ await client.execute({
3614
+ sql: `ALTER TABLE memories ADD COLUMN visibility TEXT DEFAULT 'private'`,
3615
+ args: []
3616
+ });
3617
+ } catch (e) {
3618
+ logCatchDebug("migration", e);
3619
+ }
3620
+ try {
3621
+ await client.execute({
3622
+ sql: `ALTER TABLE memories ADD COLUMN strength REAL DEFAULT 1.0`,
3623
+ args: []
3624
+ });
3625
+ } catch (e) {
3626
+ logCatchDebug("migration", e);
3627
+ }
3288
3628
  }
3289
3629
  async function disposeDatabase() {
3290
3630
  if (_walCheckpointTimer) {
@@ -3337,15 +3677,15 @@ var init_database = __esm({
3337
3677
 
3338
3678
  // src/lib/keychain.ts
3339
3679
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
3340
- import { existsSync as existsSync7, statSync as statSync3 } from "fs";
3680
+ import { existsSync as existsSync8, statSync as statSync3 } from "fs";
3341
3681
  import { execSync as execSync3 } from "child_process";
3342
- import path6 from "path";
3682
+ import path7 from "path";
3343
3683
  import os5 from "os";
3344
3684
  function getKeyDir() {
3345
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path6.join(os5.homedir(), ".exe-os");
3685
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path7.join(os5.homedir(), ".exe-os");
3346
3686
  }
3347
3687
  function getKeyPath() {
3348
- return path6.join(getKeyDir(), "master.key");
3688
+ return path7.join(getKeyDir(), "master.key");
3349
3689
  }
3350
3690
  function nativeKeychainAllowed() {
3351
3691
  return process.env.EXE_OS_DISABLE_NATIVE_KEYCHAIN !== "1";
@@ -3376,7 +3716,7 @@ function isRootOnlyTrustedServerKeyFile(keyPath) {
3376
3716
  if (!st.isFile() || (st.mode & 63) !== 0) return false;
3377
3717
  if (uid === 0) return true;
3378
3718
  const exeOsDir = process.env.EXE_OS_DIR;
3379
- return Boolean(exeOsDir && path6.resolve(keyPath).startsWith(path6.resolve(exeOsDir) + path6.sep));
3719
+ return Boolean(exeOsDir && path7.resolve(keyPath).startsWith(path7.resolve(exeOsDir) + path7.sep));
3380
3720
  } catch {
3381
3721
  return false;
3382
3722
  }
@@ -3573,7 +3913,7 @@ async function getMasterKey() {
3573
3913
  }
3574
3914
  }
3575
3915
  const keyPath = getKeyPath();
3576
- if (!existsSync7(keyPath)) {
3916
+ if (!existsSync8(keyPath)) {
3577
3917
  process.stderr.write(
3578
3918
  `[keychain] Key not found at ${keyPath} (HOME=${os5.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
3579
3919
  `
@@ -3907,14 +4247,14 @@ __export(shard_manager_exports, {
3907
4247
  listShards: () => listShards,
3908
4248
  shardExists: () => shardExists
3909
4249
  });
3910
- import path7 from "path";
3911
- import { existsSync as existsSync8, mkdirSync as mkdirSync3, readdirSync, renameSync as renameSync3, statSync as statSync4 } from "fs";
4250
+ import path8 from "path";
4251
+ import { existsSync as existsSync9, mkdirSync as mkdirSync3, readdirSync, renameSync as renameSync3, statSync as statSync4 } from "fs";
3912
4252
  import { createClient as createClient2 } from "@libsql/client";
3913
4253
  function initShardManager(encryptionKey) {
3914
4254
  _encryptionKey = encryptionKey;
3915
4255
  _keyValidated = false;
3916
4256
  _keyValidationPromise = null;
3917
- if (!existsSync8(SHARDS_DIR)) {
4257
+ if (!existsSync9(SHARDS_DIR)) {
3918
4258
  mkdirSync3(SHARDS_DIR, { recursive: true });
3919
4259
  }
3920
4260
  const existingShards = readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db"));
@@ -3935,7 +4275,7 @@ async function validateEncryptionKey() {
3935
4275
  return true;
3936
4276
  }
3937
4277
  for (const shardFile of existingShards.slice(0, 3)) {
3938
- const dbPath = path7.join(SHARDS_DIR, shardFile);
4278
+ const dbPath = path8.join(SHARDS_DIR, shardFile);
3939
4279
  const testClient = createClient2({ url: `file:${dbPath}`, encryptionKey: _encryptionKey });
3940
4280
  try {
3941
4281
  await testClient.execute("SELECT COUNT(*) FROM sqlite_schema");
@@ -3978,7 +4318,7 @@ function getShardClient(projectName) {
3978
4318
  while (_shards.size >= MAX_OPEN_SHARDS) {
3979
4319
  evictLRU();
3980
4320
  }
3981
- const dbPath = path7.join(SHARDS_DIR, `${safeName}.db`);
4321
+ const dbPath = path8.join(SHARDS_DIR, `${safeName}.db`);
3982
4322
  const client = createClient2({
3983
4323
  url: `file:${dbPath}`,
3984
4324
  encryptionKey: _encryptionKey
@@ -3989,13 +4329,13 @@ function getShardClient(projectName) {
3989
4329
  }
3990
4330
  function shardExists(projectName) {
3991
4331
  const safeName = safeShardName(projectName);
3992
- return existsSync8(path7.join(SHARDS_DIR, `${safeName}.db`));
4332
+ return existsSync9(path8.join(SHARDS_DIR, `${safeName}.db`));
3993
4333
  }
3994
4334
  function safeShardName(projectName) {
3995
4335
  return projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
3996
4336
  }
3997
4337
  function listShards() {
3998
- if (!existsSync8(SHARDS_DIR)) return [];
4338
+ if (!existsSync9(SHARDS_DIR)) return [];
3999
4339
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
4000
4340
  }
4001
4341
  async function auditShardHealth(options = {}) {
@@ -4007,7 +4347,7 @@ async function auditShardHealth(options = {}) {
4007
4347
  const names = listShards();
4008
4348
  const shards = [];
4009
4349
  for (const name of names) {
4010
- const dbPath = path7.join(SHARDS_DIR, `${name}.db`);
4350
+ const dbPath = path8.join(SHARDS_DIR, `${name}.db`);
4011
4351
  const stat = statSync4(dbPath);
4012
4352
  const item = {
4013
4353
  name,
@@ -4042,7 +4382,7 @@ async function auditShardHealth(options = {}) {
4042
4382
  _shards.delete(name);
4043
4383
  _shardLastAccess.delete(name);
4044
4384
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
4045
- const archivedPath = path7.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
4385
+ const archivedPath = path8.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
4046
4386
  renameSync3(dbPath, archivedPath);
4047
4387
  item.archivedPath = archivedPath;
4048
4388
  }
@@ -4270,11 +4610,11 @@ async function getReadyShardClient(projectName) {
4270
4610
  client.close();
4271
4611
  _shards.delete(safeName);
4272
4612
  _shardLastAccess.delete(safeName);
4273
- const dbPath = path7.join(SHARDS_DIR, `${safeName}.db`);
4274
- if (existsSync8(dbPath)) {
4613
+ const dbPath = path8.join(SHARDS_DIR, `${safeName}.db`);
4614
+ if (existsSync9(dbPath)) {
4275
4615
  const stat = statSync4(dbPath);
4276
4616
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
4277
- const archivedPath = path7.join(SHARDS_DIR, `${safeName}.db.broken-${stamp}`);
4617
+ const archivedPath = path8.join(SHARDS_DIR, `${safeName}.db.broken-${stamp}`);
4278
4618
  renameSync3(dbPath, archivedPath);
4279
4619
  process.stderr.write(
4280
4620
  `[shard-manager] Archived unreadable shard ${safeName}: ${archivedPath} (${stat.size} bytes, mtime ${stat.mtime.toISOString()})
@@ -4342,7 +4682,7 @@ var init_shard_manager = __esm({
4342
4682
  "src/lib/shard-manager.ts"() {
4343
4683
  "use strict";
4344
4684
  init_config();
4345
- SHARDS_DIR = path7.join(EXE_AI_DIR, "shards");
4685
+ SHARDS_DIR = path8.join(EXE_AI_DIR, "shards");
4346
4686
  SHARD_IDLE_MS = 5 * 60 * 1e3;
4347
4687
  MAX_OPEN_SHARDS = 10;
4348
4688
  EVICTION_INTERVAL_MS = 60 * 1e3;
@@ -4408,11 +4748,17 @@ var init_platform_procedures = __esm({
4408
4748
  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."
4409
4749
  },
4410
4750
  {
4411
- title: "Customer orchestration maturity \u2014 recommend, never trap",
4751
+ title: "Orchestration phase guidance \u2014 recommend, never trap",
4412
4752
  domain: "workflow",
4413
4753
  priority: "p1",
4414
4754
  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."
4415
4755
  },
4756
+ {
4757
+ title: "Routing slot vs display title \u2014 internal 'coo' is plumbing, not your name",
4758
+ domain: "identity",
4759
+ priority: "p0",
4760
+ 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."
4761
+ },
4416
4762
  {
4417
4763
  title: "Single dispatch path \u2014 create_task only",
4418
4764
  domain: "workflow",
@@ -4446,6 +4792,12 @@ var init_platform_procedures = __esm({
4446
4792
  priority: "p0",
4447
4793
  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."
4448
4794
  },
4795
+ {
4796
+ title: "Destructive operations \u2014 mandatory reviewer gate",
4797
+ domain: "security",
4798
+ priority: "p0",
4799
+ 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."
4800
+ },
4449
4801
  {
4450
4802
  title: "Customer patch triage \u2014 upstream bug vs customization",
4451
4803
  domain: "support",
@@ -4597,7 +4949,7 @@ var init_platform_procedures = __esm({
4597
4949
  title: "MCP tool dispatch \u2014 all tools use action parameter",
4598
4950
  domain: "tool-use",
4599
4951
  priority: "p0",
4600
- content: 'exe-os MCP tools come in two surfaces depending on EXE_MCP_TOOL_SURFACE config. Consolidated (19 tools): action-based dispatch \u2014 memory(action="recall"), task(action="create"), etc. Legacy (108 tools): one tool per operation \u2014 recall_my_memory, create_task, etc. Both surfaces have identical functionality. Use whichever tool names are available in your session. If you see domain tools (memory, task, config, etc.), use the action parameter. If you see specific tools (recall_my_memory, create_task, etc.), call them directly.'
4952
+ content: 'exe-os MCP tools use consolidated action-based dispatch by default (19 tools). Call domain tools with an action parameter: memory(action="recall"), task(action="create"), config(action="list_employees"), etc. Legacy mode (108 separate tools like recall_my_memory, create_task) is still available via EXE_MCP_TOOL_SURFACE=legacy but will be removed in a future version. If you see specific tool names, call them directly \u2014 both surfaces are identical. Consolidated is the default and recommended surface.'
4601
4953
  },
4602
4954
  {
4603
4955
  title: "MCP tools \u2014 memory, decision, and search",
@@ -4731,10 +5083,24 @@ function stableId(memoryId, type, content) {
4731
5083
  return createHash2("sha256").update(`${memoryId}:${type}:${content}`).digest("hex").slice(0, 32);
4732
5084
  }
4733
5085
  function cleanText(text) {
4734
- return text.replace(/```[\s\S]*?```/g, " ").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
5086
+ let cleaned = text.replace(
5087
+ /```(\w*)\n(.*?)(?:\n[\s\S]*?)```/g,
5088
+ (_m, lang, firstLine) => `[code${lang ? `:${lang}` : ""}] ${firstLine.trim()}`
5089
+ );
5090
+ cleaned = cleaned.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
5091
+ return cleaned;
4735
5092
  }
4736
- function splitSentences(text) {
4737
- return cleanText(text).split(/(?<=[.!?])\s+|\n+/).map((s) => s.trim()).filter((s) => s.length >= 24 && s.length <= MAX_SENTENCE_CHARS);
5093
+ function splitSegments(text) {
5094
+ const cleaned = cleanText(text);
5095
+ 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);
5096
+ if (segments.length === 0 && cleaned.length >= MIN_SEGMENT_CHARS) {
5097
+ const lines = cleaned.split(/\n+/).map((l) => l.trim()).filter((l) => l.length >= MIN_SEGMENT_CHARS && l.length <= MAX_SEGMENT_CHARS);
5098
+ if (lines.length > 0) return lines;
5099
+ if (cleaned.length >= MIN_SEGMENT_CHARS) {
5100
+ return [cleaned.slice(0, MAX_SEGMENT_CHARS)];
5101
+ }
5102
+ }
5103
+ return segments;
4738
5104
  }
4739
5105
  function inferCardType(sentence, toolName) {
4740
5106
  const lower = sentence.toLowerCase();
@@ -4766,12 +5132,12 @@ function predicateFor(type) {
4766
5132
  }
4767
5133
  }
4768
5134
  function extractMemoryCards(row) {
4769
- const sentences = splitSentences(row.raw_text);
5135
+ const segments = splitSegments(row.raw_text);
4770
5136
  const cards = [];
4771
- for (const sentence of sentences) {
5137
+ for (const sentence of segments) {
4772
5138
  const type = inferCardType(sentence, row.tool_name);
4773
5139
  const subject = extractSubject(sentence, row.agent_id);
4774
- const content = sentence.length > MAX_SENTENCE_CHARS ? `${sentence.slice(0, MAX_SENTENCE_CHARS - 1)}\u2026` : sentence;
5140
+ const content = sentence.length > MAX_SEGMENT_CHARS ? `${sentence.slice(0, MAX_SEGMENT_CHARS - 1)}\u2026` : sentence;
4775
5141
  cards.push({
4776
5142
  id: stableId(row.id, type, content),
4777
5143
  memory_id: row.id,
@@ -4867,13 +5233,14 @@ Source memory: ${String(row.source_ref ?? row.memory_id)}`,
4867
5233
  last_accessed: String(row.timestamp)
4868
5234
  }));
4869
5235
  }
4870
- var MAX_CARDS_PER_MEMORY, MAX_SENTENCE_CHARS;
5236
+ var MAX_CARDS_PER_MEMORY, MAX_SEGMENT_CHARS, MIN_SEGMENT_CHARS;
4871
5237
  var init_memory_cards = __esm({
4872
5238
  "src/lib/memory-cards.ts"() {
4873
5239
  "use strict";
4874
5240
  init_database();
4875
- MAX_CARDS_PER_MEMORY = 6;
4876
- MAX_SENTENCE_CHARS = 360;
5241
+ MAX_CARDS_PER_MEMORY = 8;
5242
+ MAX_SEGMENT_CHARS = 500;
5243
+ MIN_SEGMENT_CHARS = 20;
4877
5244
  }
4878
5245
  });
4879
5246
 
@@ -5809,12 +6176,12 @@ var init_store = __esm({
5809
6176
  });
5810
6177
 
5811
6178
  // src/lib/session-registry.ts
5812
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4, existsSync as existsSync9 } from "fs";
5813
- import path8 from "path";
6179
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, existsSync as existsSync10 } from "fs";
6180
+ import path9 from "path";
5814
6181
  import os6 from "os";
5815
6182
  function registerSession(entry) {
5816
- const dir = path8.dirname(REGISTRY_PATH);
5817
- if (!existsSync9(dir)) {
6183
+ const dir = path9.dirname(REGISTRY_PATH);
6184
+ if (!existsSync10(dir)) {
5818
6185
  mkdirSync4(dir, { recursive: true });
5819
6186
  }
5820
6187
  const sessions = listSessions();
@@ -5824,11 +6191,11 @@ function registerSession(entry) {
5824
6191
  } else {
5825
6192
  sessions.push(entry);
5826
6193
  }
5827
- writeFileSync3(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
6194
+ writeFileSync4(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
5828
6195
  }
5829
6196
  function listSessions() {
5830
6197
  try {
5831
- const raw = readFileSync5(REGISTRY_PATH, "utf8");
6198
+ const raw = readFileSync6(REGISTRY_PATH, "utf8");
5832
6199
  return JSON.parse(raw);
5833
6200
  } catch {
5834
6201
  return [];
@@ -5838,7 +6205,7 @@ var REGISTRY_PATH;
5838
6205
  var init_session_registry = __esm({
5839
6206
  "src/lib/session-registry.ts"() {
5840
6207
  "use strict";
5841
- REGISTRY_PATH = path8.join(os6.homedir(), ".exe-os", "session-registry.json");
6208
+ REGISTRY_PATH = path9.join(os6.homedir(), ".exe-os", "session-registry.json");
5842
6209
  }
5843
6210
  });
5844
6211
 
@@ -6100,68 +6467,6 @@ var init_provider_table = __esm({
6100
6467
  }
6101
6468
  });
6102
6469
 
6103
- // src/lib/runtime-table.ts
6104
- var RUNTIME_TABLE, DEFAULT_RUNTIME;
6105
- var init_runtime_table = __esm({
6106
- "src/lib/runtime-table.ts"() {
6107
- "use strict";
6108
- RUNTIME_TABLE = {
6109
- codex: {
6110
- binary: "codex",
6111
- launchMode: "interactive",
6112
- autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
6113
- inlineFlag: "--no-alt-screen",
6114
- apiKeyEnv: "OPENAI_API_KEY",
6115
- defaultModel: "gpt-5.5"
6116
- },
6117
- opencode: {
6118
- binary: "opencode",
6119
- launchMode: "exec",
6120
- autoApproveFlag: "--dangerously-skip-permissions",
6121
- inlineFlag: "",
6122
- apiKeyEnv: "ANTHROPIC_API_KEY",
6123
- defaultModel: "anthropic/claude-sonnet-4-6"
6124
- }
6125
- };
6126
- DEFAULT_RUNTIME = "claude";
6127
- }
6128
- });
6129
-
6130
- // src/lib/agent-config.ts
6131
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync10 } from "fs";
6132
- import path9 from "path";
6133
- function loadAgentConfig() {
6134
- if (!existsSync10(AGENT_CONFIG_PATH)) return {};
6135
- try {
6136
- return JSON.parse(readFileSync6(AGENT_CONFIG_PATH, "utf-8"));
6137
- } catch {
6138
- return {};
6139
- }
6140
- }
6141
- function getAgentRuntime(agentId) {
6142
- const config = loadAgentConfig();
6143
- const entry = config[agentId];
6144
- if (entry) return entry;
6145
- const orgDefault = config["default"];
6146
- if (orgDefault) return orgDefault;
6147
- return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
6148
- }
6149
- var AGENT_CONFIG_PATH, DEFAULT_MODELS;
6150
- var init_agent_config = __esm({
6151
- "src/lib/agent-config.ts"() {
6152
- "use strict";
6153
- init_config();
6154
- init_runtime_table();
6155
- init_secure_files();
6156
- AGENT_CONFIG_PATH = path9.join(EXE_AI_DIR, "agent-config.json");
6157
- DEFAULT_MODELS = {
6158
- claude: "claude-opus-4.6",
6159
- codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
6160
- opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
6161
- };
6162
- }
6163
- });
6164
-
6165
6470
  // src/lib/intercom-queue.ts
6166
6471
  var intercom_queue_exports = {};
6167
6472
  __export(intercom_queue_exports, {
@@ -6289,6 +6594,23 @@ var init_intercom_queue = __esm({
6289
6594
  });
6290
6595
 
6291
6596
  // src/lib/license.ts
6597
+ var license_exports = {};
6598
+ __export(license_exports, {
6599
+ LICENSE_PUBLIC_KEY_PEM: () => LICENSE_PUBLIC_KEY_PEM,
6600
+ PLAN_LIMITS: () => PLAN_LIMITS,
6601
+ assertVpsLicense: () => assertVpsLicense,
6602
+ checkLicense: () => checkLicense,
6603
+ getCachedLicense: () => getCachedLicense,
6604
+ isFeatureAllowed: () => isFeatureAllowed,
6605
+ loadDeviceId: () => loadDeviceId,
6606
+ loadLicense: () => loadLicense,
6607
+ mirrorLicenseKey: () => mirrorLicenseKey,
6608
+ readCachedLicenseToken: () => readCachedLicenseToken,
6609
+ saveLicense: () => saveLicense,
6610
+ startLicenseRevalidation: () => startLicenseRevalidation,
6611
+ stopLicenseRevalidation: () => stopLicenseRevalidation,
6612
+ validateLicense: () => validateLicense
6613
+ });
6292
6614
  import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync12, mkdirSync as mkdirSync6 } from "fs";
6293
6615
  import { randomUUID as randomUUID3 } from "crypto";
6294
6616
  import { createRequire as createRequire2 } from "module";
@@ -6296,7 +6618,411 @@ import { pathToFileURL as pathToFileURL2 } from "url";
6296
6618
  import os8 from "os";
6297
6619
  import path11 from "path";
6298
6620
  import { jwtVerify, importSPKI } from "jose";
6299
- var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, PLAN_LIMITS;
6621
+ async function fetchRetry(url, init) {
6622
+ try {
6623
+ return await fetch(url, init);
6624
+ } catch {
6625
+ await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
6626
+ return fetch(url, { ...init, signal: AbortSignal.timeout(1e4) });
6627
+ }
6628
+ }
6629
+ function loadDeviceId() {
6630
+ const deviceJsonPath = path11.join(EXE_AI_DIR, "device.json");
6631
+ try {
6632
+ if (existsSync12(deviceJsonPath)) {
6633
+ const data = JSON.parse(readFileSync8(deviceJsonPath, "utf8"));
6634
+ if (data.deviceId) return data.deviceId;
6635
+ }
6636
+ } catch {
6637
+ }
6638
+ try {
6639
+ if (existsSync12(DEVICE_ID_PATH)) {
6640
+ const id2 = readFileSync8(DEVICE_ID_PATH, "utf8").trim();
6641
+ if (id2) return id2;
6642
+ }
6643
+ } catch {
6644
+ }
6645
+ const id = randomUUID3();
6646
+ mkdirSync6(EXE_AI_DIR, { recursive: true });
6647
+ writeFileSync6(DEVICE_ID_PATH, id, "utf8");
6648
+ return id;
6649
+ }
6650
+ function loadLicense() {
6651
+ try {
6652
+ if (!existsSync12(LICENSE_PATH)) return null;
6653
+ return readFileSync8(LICENSE_PATH, "utf8").trim();
6654
+ } catch {
6655
+ return null;
6656
+ }
6657
+ }
6658
+ function saveLicense(apiKey) {
6659
+ mkdirSync6(EXE_AI_DIR, { recursive: true });
6660
+ writeFileSync6(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
6661
+ }
6662
+ async function verifyLicenseJwt(token) {
6663
+ try {
6664
+ const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
6665
+ const { payload } = await jwtVerify(token, key, {
6666
+ algorithms: [LICENSE_JWT_ALG]
6667
+ });
6668
+ const plan = payload.plan ?? "free";
6669
+ const email = payload.sub ?? "";
6670
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
6671
+ return {
6672
+ valid: true,
6673
+ plan,
6674
+ email,
6675
+ expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
6676
+ deviceLimit: limits.devices,
6677
+ employeeLimit: limits.employees,
6678
+ memoryLimit: limits.memories
6679
+ };
6680
+ } catch {
6681
+ return null;
6682
+ }
6683
+ }
6684
+ async function getCachedLicense() {
6685
+ try {
6686
+ if (!existsSync12(CACHE_PATH)) return null;
6687
+ const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
6688
+ if (!raw.token || typeof raw.token !== "string") return null;
6689
+ return await verifyLicenseJwt(raw.token);
6690
+ } catch {
6691
+ return null;
6692
+ }
6693
+ }
6694
+ function readCachedLicenseToken() {
6695
+ try {
6696
+ if (!existsSync12(CACHE_PATH)) return null;
6697
+ const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
6698
+ return typeof raw.token === "string" ? raw.token : null;
6699
+ } catch {
6700
+ return null;
6701
+ }
6702
+ }
6703
+ function getRawCachedPlan() {
6704
+ try {
6705
+ const token = readCachedLicenseToken();
6706
+ if (!token) return null;
6707
+ const parts = token.split(".");
6708
+ if (parts.length !== 3) return null;
6709
+ const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
6710
+ const plan = payload.plan ?? "free";
6711
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
6712
+ process.stderr.write(
6713
+ `[license] WARN: using unverified cached plan (API unreachable, JWT expired). Plan: ${plan}
6714
+ `
6715
+ );
6716
+ return {
6717
+ valid: true,
6718
+ plan,
6719
+ email: payload.sub ?? "",
6720
+ expiresAt: payload.exp ? new Date(payload.exp * 1e3).toISOString() : null,
6721
+ deviceLimit: limits.devices,
6722
+ employeeLimit: limits.employees,
6723
+ memoryLimit: limits.memories
6724
+ };
6725
+ } catch {
6726
+ return null;
6727
+ }
6728
+ }
6729
+ function cacheResponse(token) {
6730
+ try {
6731
+ writeFileSync6(CACHE_PATH, JSON.stringify({ token }), "utf8");
6732
+ } catch {
6733
+ }
6734
+ }
6735
+ function loadPrismaForLicense() {
6736
+ if (_prismaFailed) return null;
6737
+ const dbUrl = process.env.DATABASE_URL;
6738
+ if (!dbUrl) {
6739
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path11.join(os8.homedir(), "exe-db");
6740
+ if (!existsSync12(path11.join(exeDbRoot, "package.json"))) {
6741
+ _prismaFailed = true;
6742
+ return null;
6743
+ }
6744
+ }
6745
+ if (!_prismaPromise) {
6746
+ _prismaPromise = (async () => {
6747
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
6748
+ if (explicitPath) {
6749
+ const mod2 = await import(pathToFileURL2(explicitPath).href);
6750
+ const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
6751
+ if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
6752
+ return new Ctor2();
6753
+ }
6754
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path11.join(os8.homedir(), "exe-db");
6755
+ const req = createRequire2(path11.join(exeDbRoot, "package.json"));
6756
+ const entry = req.resolve("@prisma/client");
6757
+ const mod = await import(pathToFileURL2(entry).href);
6758
+ const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
6759
+ if (!Ctor) throw new Error(`No PrismaClient in ${entry}`);
6760
+ return new Ctor();
6761
+ })().catch((err) => {
6762
+ _prismaFailed = true;
6763
+ _prismaPromise = null;
6764
+ throw err;
6765
+ });
6766
+ }
6767
+ return _prismaPromise;
6768
+ }
6769
+ async function validateViaPostgres(apiKey) {
6770
+ const loader = loadPrismaForLicense();
6771
+ if (!loader) return null;
6772
+ try {
6773
+ const prisma = await loader;
6774
+ const rows = await prisma.$queryRawUnsafe(
6775
+ `SELECT plan, email, status, device_limit, employee_limit, memory_limit, expires_at
6776
+ FROM billing.licenses WHERE key = $1 LIMIT 1`,
6777
+ apiKey
6778
+ );
6779
+ if (!rows || rows.length === 0) return null;
6780
+ const row = rows[0];
6781
+ if (row.status !== "active") return null;
6782
+ if (row.expires_at && new Date(row.expires_at) < /* @__PURE__ */ new Date()) return null;
6783
+ const plan = row.plan;
6784
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
6785
+ return {
6786
+ valid: true,
6787
+ plan,
6788
+ email: row.email,
6789
+ expiresAt: row.expires_at ? new Date(row.expires_at).toISOString() : null,
6790
+ deviceLimit: row.device_limit ?? limits.devices,
6791
+ employeeLimit: row.employee_limit ?? limits.employees,
6792
+ memoryLimit: row.memory_limit ?? limits.memories
6793
+ };
6794
+ } catch {
6795
+ return null;
6796
+ }
6797
+ }
6798
+ async function validateViaCFWorker(apiKey, deviceId) {
6799
+ try {
6800
+ const res = await fetchRetry(`${API_BASE}/auth/activate`, {
6801
+ method: "POST",
6802
+ headers: { "Content-Type": "application/json" },
6803
+ body: JSON.stringify({ apiKey, deviceId }),
6804
+ signal: AbortSignal.timeout(1e4)
6805
+ });
6806
+ if (!res.ok) return null;
6807
+ const data = await res.json();
6808
+ if (data.error === "device_limit_exceeded") return null;
6809
+ if (!data.valid) return null;
6810
+ if (data.token) {
6811
+ cacheResponse(data.token);
6812
+ const verified = await verifyLicenseJwt(data.token);
6813
+ if (verified) return verified;
6814
+ }
6815
+ const limits = PLAN_LIMITS[data.plan] ?? PLAN_LIMITS.free;
6816
+ return {
6817
+ valid: data.valid,
6818
+ plan: data.plan,
6819
+ email: data.email,
6820
+ expiresAt: data.expiresAt,
6821
+ deviceLimit: limits.devices,
6822
+ employeeLimit: limits.employees,
6823
+ memoryLimit: limits.memories
6824
+ };
6825
+ } catch {
6826
+ return null;
6827
+ }
6828
+ }
6829
+ async function validateLicense(apiKey, deviceId) {
6830
+ const did = deviceId ?? loadDeviceId();
6831
+ const pgResult = await validateViaPostgres(apiKey);
6832
+ if (pgResult) {
6833
+ try {
6834
+ writeFileSync6(CACHE_PATH, JSON.stringify({ pgLicense: pgResult, ts: Date.now() }), "utf8");
6835
+ } catch {
6836
+ }
6837
+ return pgResult;
6838
+ }
6839
+ const cfResult = await validateViaCFWorker(apiKey, did);
6840
+ if (cfResult) return cfResult;
6841
+ const cached = await getCachedLicense();
6842
+ if (cached) return cached;
6843
+ try {
6844
+ if (existsSync12(CACHE_PATH)) {
6845
+ const raw = JSON.parse(readFileSync8(CACHE_PATH, "utf8"));
6846
+ if (raw.pgLicense && raw.ts && Date.now() - raw.ts < 7 * 24 * 60 * 60 * 1e3) {
6847
+ return raw.pgLicense;
6848
+ }
6849
+ }
6850
+ } catch {
6851
+ }
6852
+ const rawFallback = getRawCachedPlan();
6853
+ if (rawFallback) return rawFallback;
6854
+ return { ...FREE_LICENSE, valid: false };
6855
+ }
6856
+ function getCacheAgeMs() {
6857
+ try {
6858
+ const { statSync: statSync5 } = __require("fs");
6859
+ const s = statSync5(CACHE_PATH);
6860
+ return Date.now() - s.mtimeMs;
6861
+ } catch {
6862
+ return Infinity;
6863
+ }
6864
+ }
6865
+ async function checkLicense() {
6866
+ let key = loadLicense();
6867
+ if (!key) {
6868
+ try {
6869
+ const configPath = path11.join(EXE_AI_DIR, "config.json");
6870
+ if (existsSync12(configPath)) {
6871
+ const raw = JSON.parse(readFileSync8(configPath, "utf8"));
6872
+ const cloud = raw.cloud;
6873
+ if (cloud?.apiKey) {
6874
+ key = cloud.apiKey;
6875
+ saveLicense(key);
6876
+ }
6877
+ }
6878
+ } catch {
6879
+ }
6880
+ }
6881
+ if (!key) return FREE_LICENSE;
6882
+ const cached = await getCachedLicense();
6883
+ if (cached && getCacheAgeMs() < CACHE_MAX_AGE_MS) return cached;
6884
+ const deviceId = loadDeviceId();
6885
+ return validateLicense(key, deviceId);
6886
+ }
6887
+ function isFeatureAllowed(license, feature) {
6888
+ switch (feature) {
6889
+ case "cloud_sync":
6890
+ case "external_agents":
6891
+ case "wiki":
6892
+ return license.plan !== "free";
6893
+ case "unlimited_employees":
6894
+ return license.plan === "team" || license.plan === "agency" || license.plan === "enterprise";
6895
+ }
6896
+ }
6897
+ function mirrorLicenseKey(apiKey) {
6898
+ const trimmed = apiKey.trim();
6899
+ if (!trimmed) return;
6900
+ saveLicense(trimmed);
6901
+ }
6902
+ async function assertVpsLicense(opts) {
6903
+ const env = opts?.env ?? process.env;
6904
+ const inProduction = env.NODE_ENV === "production";
6905
+ if (!opts?.force && !inProduction) {
6906
+ return { ...FREE_LICENSE, plan: "free" };
6907
+ }
6908
+ const envKey = env.EXE_LICENSE_KEY?.trim();
6909
+ if (envKey) {
6910
+ saveLicense(envKey);
6911
+ }
6912
+ const apiKey = envKey ?? loadLicense();
6913
+ if (!apiKey) {
6914
+ throw new Error(
6915
+ "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."
6916
+ );
6917
+ }
6918
+ const deviceId = loadDeviceId();
6919
+ let backendResponse = null;
6920
+ let explicitRejection = false;
6921
+ let transientFailure = false;
6922
+ try {
6923
+ const res = await fetchRetry(`${API_BASE}/auth/activate`, {
6924
+ method: "POST",
6925
+ headers: { "Content-Type": "application/json" },
6926
+ body: JSON.stringify({ apiKey, deviceId }),
6927
+ signal: AbortSignal.timeout(1e4)
6928
+ });
6929
+ if (res.ok) {
6930
+ backendResponse = await res.json();
6931
+ if (!backendResponse.valid) explicitRejection = true;
6932
+ } else if (res.status === 401 || res.status === 403) {
6933
+ explicitRejection = true;
6934
+ } else {
6935
+ transientFailure = true;
6936
+ }
6937
+ } catch {
6938
+ transientFailure = true;
6939
+ }
6940
+ if (backendResponse?.valid) {
6941
+ if (backendResponse.token) {
6942
+ cacheResponse(backendResponse.token);
6943
+ const verified = await verifyLicenseJwt(backendResponse.token);
6944
+ if (verified) return verified;
6945
+ }
6946
+ const limits = PLAN_LIMITS[backendResponse.plan] ?? PLAN_LIMITS.free;
6947
+ return {
6948
+ valid: true,
6949
+ plan: backendResponse.plan,
6950
+ email: backendResponse.email,
6951
+ expiresAt: backendResponse.expiresAt,
6952
+ deviceLimit: limits.devices,
6953
+ employeeLimit: limits.employees,
6954
+ memoryLimit: limits.memories
6955
+ };
6956
+ }
6957
+ if (explicitRejection) {
6958
+ throw new Error(
6959
+ `License invalid or expired. Renew at https://askexe.com, then restart. Backend rejected key ending in ...${apiKey.slice(-4)}.`
6960
+ );
6961
+ }
6962
+ if (!transientFailure) {
6963
+ throw new Error(
6964
+ "License validation failed: unknown backend state. Restore network connectivity to https://cloud.askexe.com and retry."
6965
+ );
6966
+ }
6967
+ const fresh = await getCachedLicense();
6968
+ if (fresh && fresh.valid) return fresh;
6969
+ const graceDays = opts?.offlineGraceDays ?? 7;
6970
+ const graceMs = graceDays * 24 * 60 * 60 * 1e3;
6971
+ try {
6972
+ const token = readCachedLicenseToken();
6973
+ if (token) {
6974
+ const payloadB64 = token.split(".")[1];
6975
+ if (payloadB64) {
6976
+ const payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString());
6977
+ const expMs = (payload.exp ?? 0) * 1e3;
6978
+ if (Date.now() < expMs + graceMs) {
6979
+ const key = await importSPKI(LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG);
6980
+ const { payload: verified } = await jwtVerify(token, key, {
6981
+ algorithms: [LICENSE_JWT_ALG],
6982
+ clockTolerance: graceDays * 24 * 60 * 60
6983
+ });
6984
+ const plan = verified.plan ?? "free";
6985
+ const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
6986
+ return {
6987
+ valid: true,
6988
+ plan,
6989
+ email: verified.sub ?? "",
6990
+ expiresAt: verified.exp ? new Date(verified.exp * 1e3).toISOString() : null,
6991
+ deviceLimit: limits.devices,
6992
+ employeeLimit: limits.employees,
6993
+ memoryLimit: limits.memories
6994
+ };
6995
+ }
6996
+ }
6997
+ }
6998
+ } catch {
6999
+ }
7000
+ throw new Error(
7001
+ `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.`
7002
+ );
7003
+ }
7004
+ function startLicenseRevalidation(intervalMs = 36e5) {
7005
+ if (_revalTimer) return;
7006
+ _revalTimer = setInterval(async () => {
7007
+ try {
7008
+ const license = await checkLicense();
7009
+ if (!license.valid) {
7010
+ process.stderr.write("[exe-os] License expired or invalid \u2014 features may be restricted\n");
7011
+ }
7012
+ } catch {
7013
+ }
7014
+ }, intervalMs);
7015
+ if (_revalTimer && typeof _revalTimer === "object" && "unref" in _revalTimer) {
7016
+ _revalTimer.unref();
7017
+ }
7018
+ }
7019
+ function stopLicenseRevalidation() {
7020
+ if (_revalTimer) {
7021
+ clearInterval(_revalTimer);
7022
+ _revalTimer = null;
7023
+ }
7024
+ }
7025
+ var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, RETRY_DELAY_MS, LICENSE_PUBLIC_KEY_PEM, LICENSE_JWT_ALG, PLAN_LIMITS, FREE_LICENSE, _prismaPromise, _prismaFailed, CACHE_MAX_AGE_MS, _revalTimer;
6300
7026
  var init_license = __esm({
6301
7027
  "src/lib/license.ts"() {
6302
7028
  "use strict";
@@ -6304,7 +7030,13 @@ var init_license = __esm({
6304
7030
  LICENSE_PATH = path11.join(EXE_AI_DIR, "license.key");
6305
7031
  CACHE_PATH = path11.join(EXE_AI_DIR, "license-cache.json");
6306
7032
  DEVICE_ID_PATH = path11.join(EXE_AI_DIR, "device-id");
6307
- API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com/cloud";
7033
+ API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://cloud.askexe.com";
7034
+ RETRY_DELAY_MS = 500;
7035
+ LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
7036
+ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
7037
+ 4uj+UqeKCcvtgNHKmOK278HJaJcANe9xAeji8AFYu27q3WtzCi04pHudow==
7038
+ -----END PUBLIC KEY-----`;
7039
+ LICENSE_JWT_ALG = "ES256";
6308
7040
  PLAN_LIMITS = {
6309
7041
  free: { devices: 1, employees: 1, memories: 5e3 },
6310
7042
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -6312,6 +7044,19 @@ var init_license = __esm({
6312
7044
  agency: { devices: 50, employees: 100, memories: 1e7 },
6313
7045
  enterprise: { devices: -1, employees: -1, memories: -1 }
6314
7046
  };
7047
+ FREE_LICENSE = {
7048
+ valid: true,
7049
+ plan: "free",
7050
+ email: "",
7051
+ expiresAt: null,
7052
+ deviceLimit: 1,
7053
+ employeeLimit: 1,
7054
+ memoryLimit: 5e3
7055
+ };
7056
+ _prismaPromise = null;
7057
+ _prismaFailed = false;
7058
+ CACHE_MAX_AGE_MS = 36e5;
7059
+ _revalTimer = null;
6315
7060
  }
6316
7061
  });
6317
7062
 
@@ -6751,6 +7496,19 @@ async function resolveTask(client, identifier, scopeSession) {
6751
7496
  args: [identifier, ...scope.args]
6752
7497
  });
6753
7498
  if (result.rows.length === 1) return result.rows[0];
7499
+ if (/^[a-f0-9]{7,12}$/i.test(identifier)) {
7500
+ result = await client.execute({
7501
+ sql: `SELECT * FROM tasks WHERE id LIKE ?`,
7502
+ args: [`${identifier}%`]
7503
+ });
7504
+ if (result.rows.length === 1) return result.rows[0];
7505
+ if (result.rows.length > 1) {
7506
+ const matches = result.rows.map((r) => `${String(r.id)} "${String(r.title)}" (${String(r.status)})`).join(", ");
7507
+ throw new Error(
7508
+ `Multiple tasks match short-ID "${identifier}": ${matches}. Use a longer prefix to disambiguate.`
7509
+ );
7510
+ }
7511
+ }
6754
7512
  result = await client.execute({
6755
7513
  sql: `SELECT * FROM tasks WHERE task_file LIKE ?${scope.sql}`,
6756
7514
  args: [`%${identifier}%`, ...scope.args]
@@ -7605,12 +8363,13 @@ async function cascadeUnblock(taskId, baseDir, now) {
7605
8363
  WHERE blocked_by = ? AND status = 'blocked'`,
7606
8364
  args: [now, taskId]
7607
8365
  });
7608
- if (baseDir && unblocked.rowsAffected > 0) {
7609
- const ubScope = sessionScopeFilter();
7610
- const unblockedRows = await client.execute({
7611
- sql: `SELECT task_file FROM tasks WHERE blocked_by IS NULL AND updated_at = ?${ubScope.sql}`,
7612
- args: [now, ...ubScope.args]
7613
- });
8366
+ if (unblocked.rowsAffected === 0) return;
8367
+ const ubScope = sessionScopeFilter();
8368
+ const unblockedRows = await client.execute({
8369
+ sql: `SELECT id, title, assigned_to, priority, task_file FROM tasks WHERE blocked_by IS NULL AND updated_at = ?${ubScope.sql}`,
8370
+ args: [now, ...ubScope.args]
8371
+ });
8372
+ if (baseDir) {
7614
8373
  for (const ur of unblockedRows.rows) {
7615
8374
  try {
7616
8375
  const ubFile = path18.join(baseDir, String(ur.task_file));
@@ -7622,6 +8381,19 @@ async function cascadeUnblock(taskId, baseDir, now) {
7622
8381
  }
7623
8382
  }
7624
8383
  }
8384
+ if (unblockedRows.rows.length > 0 && !process.env.VITEST) {
8385
+ try {
8386
+ const { queueIntercom: queueIntercom2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
8387
+ const dispatched = /* @__PURE__ */ new Set();
8388
+ for (const ur of unblockedRows.rows) {
8389
+ const assignee = String(ur.assigned_to);
8390
+ if (dispatched.has(assignee)) continue;
8391
+ dispatched.add(assignee);
8392
+ queueIntercom2(`${assignee}`, `unblocked: "${String(ur.title)}" is now ready`);
8393
+ }
8394
+ } catch {
8395
+ }
8396
+ }
7625
8397
  }
7626
8398
  async function findNextTask(assignedTo) {
7627
8399
  const client = getClient();
@@ -7831,6 +8603,15 @@ var init_embedder = __esm({
7831
8603
  // src/lib/behaviors.ts
7832
8604
  import crypto5 from "crypto";
7833
8605
  async function storeBehavior(opts) {
8606
+ try {
8607
+ const { loadEmployeesSync: loadEmployeesSync2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
8608
+ const roster = loadEmployeesSync2();
8609
+ if (roster.length > 0 && !roster.some((e) => e.name === opts.agentId)) {
8610
+ throw new Error(`Agent "${opts.agentId}" not found in roster. Cannot store behavior for unregistered agent.`);
8611
+ }
8612
+ } catch (e) {
8613
+ if (e instanceof Error && e.message.includes("not found in roster")) throw e;
8614
+ }
7834
8615
  const client = getClient();
7835
8616
  const id = crypto5.randomUUID();
7836
8617
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -7841,10 +8622,18 @@ async function storeBehavior(opts) {
7841
8622
  vector = new Float32Array(vec);
7842
8623
  } catch {
7843
8624
  }
8625
+ let createdByDevice = null;
8626
+ try {
8627
+ const { loadDeviceId: loadDeviceId2 } = await Promise.resolve().then(() => (init_license(), license_exports));
8628
+ createdByDevice = loadDeviceId2() ?? null;
8629
+ } catch {
8630
+ }
8631
+ const createdByAgent = process.env.AGENT_ID ?? null;
8632
+ const sourceSessionId = process.env.CLAUDE_SESSION_ID ?? process.env.SESSION_ID ?? null;
7844
8633
  await client.execute({
7845
- sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at, vector)
7846
- VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?)`,
7847
- args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now, vector ? vector.buffer : null]
8634
+ 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)
8635
+ VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?)`,
8636
+ args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now, vector ? vector.buffer : null, createdByAgent, createdByDevice, sourceSessionId]
7848
8637
  });
7849
8638
  return id;
7850
8639
  }
@@ -8276,6 +9065,12 @@ async function updateTask(input) {
8276
9065
  }
8277
9066
  }
8278
9067
  }
9068
+ if (input.status === "cancelled") {
9069
+ try {
9070
+ await cascadeUnblock(taskId, input.baseDir, now);
9071
+ } catch {
9072
+ }
9073
+ }
8279
9074
  if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
8280
9075
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
8281
9076
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
@@ -8807,11 +9602,12 @@ function getDispatchedBy(sessionKey) {
8807
9602
  }
8808
9603
  }
8809
9604
  function resolveExeSession() {
9605
+ if (process.env.EXE_SESSION_NAME) {
9606
+ const fromEnv = extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
9607
+ if (fromEnv) return fromEnv;
9608
+ }
8810
9609
  const mySession = getMySession();
8811
9610
  if (!mySession) {
8812
- if (process.env.EXE_SESSION_NAME) {
8813
- return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
8814
- }
8815
9611
  return null;
8816
9612
  }
8817
9613
  const fromSessionName = extractRootExe(mySession);
@@ -8826,6 +9622,10 @@ function resolveExeSession() {
8826
9622
  `[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
8827
9623
  `
8828
9624
  );
9625
+ try {
9626
+ registerParentExe(key, fromSessionName);
9627
+ } catch {
9628
+ }
8829
9629
  candidate = fromSessionName;
8830
9630
  } else {
8831
9631
  candidate = fromCache;