@askexenow/exe-os 0.9.112 → 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 +54 -11
  3. package/dist/bin/agentic-reflection-backfill.js +29 -1
  4. package/dist/bin/agentic-semantic-label.js +29 -1
  5. package/dist/bin/backfill-conversations.js +53 -10
  6. package/dist/bin/backfill-responses.js +54 -11
  7. package/dist/bin/backfill-vectors.js +29 -1
  8. package/dist/bin/bulk-sync-postgres.js +55 -12
  9. package/dist/bin/cleanup-stale-review-tasks.js +75 -15
  10. package/dist/bin/cli.js +293 -76
  11. package/dist/bin/exe-agent-config.js +7 -1
  12. package/dist/bin/exe-agent.js +28 -2
  13. package/dist/bin/exe-assign.js +54 -11
  14. package/dist/bin/exe-boot.js +481 -147
  15. package/dist/bin/exe-call.js +45 -4
  16. package/dist/bin/exe-cloud.js +93 -15
  17. package/dist/bin/exe-dispatch.js +369 -24
  18. package/dist/bin/exe-doctor.js +53 -10
  19. package/dist/bin/exe-export-behaviors.js +54 -11
  20. package/dist/bin/exe-forget.js +54 -11
  21. package/dist/bin/exe-gateway.js +128 -23
  22. package/dist/bin/exe-heartbeat.js +75 -15
  23. package/dist/bin/exe-kill.js +54 -11
  24. package/dist/bin/exe-launch-agent.js +70 -12
  25. package/dist/bin/exe-new-employee.js +175 -7
  26. package/dist/bin/exe-pending-messages.js +75 -15
  27. package/dist/bin/exe-pending-notifications.js +75 -15
  28. package/dist/bin/exe-pending-reviews.js +75 -15
  29. package/dist/bin/exe-rename.js +54 -11
  30. package/dist/bin/exe-review.js +54 -11
  31. package/dist/bin/exe-search.js +54 -11
  32. package/dist/bin/exe-session-cleanup.js +491 -146
  33. package/dist/bin/exe-settings.js +10 -4
  34. package/dist/bin/exe-start-codex.js +524 -245
  35. package/dist/bin/exe-start-opencode.js +534 -165
  36. package/dist/bin/exe-status.js +75 -15
  37. package/dist/bin/exe-support.js +1 -1
  38. package/dist/bin/exe-team.js +54 -11
  39. package/dist/bin/git-sweep.js +369 -24
  40. package/dist/bin/graph-backfill.js +54 -11
  41. package/dist/bin/graph-export.js +54 -11
  42. package/dist/bin/install.js +62 -4
  43. package/dist/bin/intercom-check.js +491 -146
  44. package/dist/bin/pre-publish.js +13 -1
  45. package/dist/bin/scan-tasks.js +369 -24
  46. package/dist/bin/setup.js +91 -13
  47. package/dist/bin/shard-migrate.js +54 -11
  48. package/dist/bin/stack-update.js +1 -1
  49. package/dist/bin/update.js +3 -3
  50. package/dist/gateway/index.js +128 -23
  51. package/dist/hooks/bug-report-worker.js +128 -23
  52. package/dist/hooks/codex-stop-task-finalizer.js +512 -140
  53. package/dist/hooks/commit-complete.js +369 -24
  54. package/dist/hooks/error-recall.js +54 -11
  55. package/dist/hooks/ingest.js +4575 -252
  56. package/dist/hooks/instructions-loaded.js +54 -11
  57. package/dist/hooks/notification.js +54 -11
  58. package/dist/hooks/post-compact.js +75 -15
  59. package/dist/hooks/post-tool-combined.js +75 -15
  60. package/dist/hooks/pre-compact.js +449 -104
  61. package/dist/hooks/pre-tool-use.js +90 -15
  62. package/dist/hooks/prompt-submit.js +129 -24
  63. package/dist/hooks/session-end.js +451 -109
  64. package/dist/hooks/session-start.js +104 -16
  65. package/dist/hooks/stop.js +74 -14
  66. package/dist/hooks/subagent-stop.js +75 -15
  67. package/dist/hooks/summary-worker.js +73 -7
  68. package/dist/index.js +128 -23
  69. package/dist/lib/agent-config.js +16 -1
  70. package/dist/lib/cloud-sync.js +38 -1
  71. package/dist/lib/consolidation.js +16 -1
  72. package/dist/lib/database.js +16 -0
  73. package/dist/lib/db.js +16 -0
  74. package/dist/lib/device-registry.js +16 -0
  75. package/dist/lib/employee-templates.js +29 -3
  76. package/dist/lib/employees.js +16 -1
  77. package/dist/lib/exe-daemon.js +268 -42
  78. package/dist/lib/hybrid-search.js +54 -11
  79. package/dist/lib/license.js +3 -3
  80. package/dist/lib/messaging.js +21 -4
  81. package/dist/lib/schedules.js +29 -1
  82. package/dist/lib/skill-learning.js +458 -70
  83. package/dist/lib/status-brief.js +14 -1
  84. package/dist/lib/store.js +54 -11
  85. package/dist/lib/tasks.js +393 -91
  86. package/dist/lib/tmux-routing.js +316 -14
  87. package/dist/mcp/server.js +169 -30
  88. package/dist/mcp/tools/create-task.js +75 -13
  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 +390 -91
  93. package/dist/runtime/index.js +446 -101
  94. package/dist/tui/App.js +208 -54
  95. package/package.json +1 -1
@@ -398,11 +398,168 @@ var init_session_key = __esm({
398
398
  }
399
399
  });
400
400
 
401
+ // src/lib/runtime-table.ts
402
+ var RUNTIME_TABLE, DEFAULT_RUNTIME;
403
+ var init_runtime_table = __esm({
404
+ "src/lib/runtime-table.ts"() {
405
+ "use strict";
406
+ RUNTIME_TABLE = {
407
+ codex: {
408
+ binary: "codex",
409
+ launchMode: "interactive",
410
+ autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
411
+ inlineFlag: "--no-alt-screen",
412
+ apiKeyEnv: "OPENAI_API_KEY",
413
+ defaultModel: "gpt-5.5"
414
+ },
415
+ opencode: {
416
+ binary: "opencode",
417
+ launchMode: "exec",
418
+ autoApproveFlag: "--dangerously-skip-permissions",
419
+ inlineFlag: "",
420
+ apiKeyEnv: "ANTHROPIC_API_KEY",
421
+ defaultModel: "anthropic/claude-sonnet-4-6"
422
+ }
423
+ };
424
+ DEFAULT_RUNTIME = "claude";
425
+ }
426
+ });
427
+
428
+ // src/lib/agent-config.ts
429
+ var agent_config_exports = {};
430
+ __export(agent_config_exports, {
431
+ AGENT_CONFIG_PATH: () => AGENT_CONFIG_PATH,
432
+ DEFAULT_MODELS: () => DEFAULT_MODELS,
433
+ KNOWN_RUNTIMES: () => KNOWN_RUNTIMES,
434
+ RUNTIME_LABELS: () => RUNTIME_LABELS,
435
+ clearAgentRuntime: () => clearAgentRuntime,
436
+ getAgentRuntime: () => getAgentRuntime,
437
+ loadAgentConfig: () => loadAgentConfig,
438
+ saveAgentConfig: () => saveAgentConfig,
439
+ setAgentMcps: () => setAgentMcps,
440
+ setAgentRuntime: () => setAgentRuntime
441
+ });
442
+ import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
443
+ import path2 from "path";
444
+ function loadAgentConfig() {
445
+ if (!existsSync3(AGENT_CONFIG_PATH)) return {};
446
+ try {
447
+ return JSON.parse(readFileSync2(AGENT_CONFIG_PATH, "utf-8"));
448
+ } catch {
449
+ return {};
450
+ }
451
+ }
452
+ function saveAgentConfig(config) {
453
+ const dir = path2.dirname(AGENT_CONFIG_PATH);
454
+ ensurePrivateDirSync(dir);
455
+ writeFileSync(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
456
+ enforcePrivateFileSync(AGENT_CONFIG_PATH);
457
+ }
458
+ function getAgentRuntime(agentId) {
459
+ const config = loadAgentConfig();
460
+ const entry = config[agentId];
461
+ if (entry) return entry;
462
+ const orgDefault = config["default"];
463
+ if (orgDefault) return orgDefault;
464
+ return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
465
+ }
466
+ function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
467
+ const knownModels = KNOWN_RUNTIMES[runtime];
468
+ if (!knownModels) {
469
+ return {
470
+ ok: false,
471
+ error: `Unknown runtime "${runtime}". Valid: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`
472
+ };
473
+ }
474
+ if (!knownModels.includes(model)) {
475
+ return {
476
+ ok: false,
477
+ error: `Unknown model "${model}" for runtime "${runtime}". Valid: ${knownModels.join(", ")}`
478
+ };
479
+ }
480
+ const config = loadAgentConfig();
481
+ const existing = config[agentId];
482
+ const entry = { runtime, model };
483
+ if (reasoning_effort) entry.reasoning_effort = reasoning_effort;
484
+ if (mcps !== void 0) {
485
+ entry.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
486
+ } else if (existing?.mcps) {
487
+ entry.mcps = existing.mcps;
488
+ }
489
+ config[agentId] = entry;
490
+ saveAgentConfig(config);
491
+ return { ok: true };
492
+ }
493
+ function setAgentMcps(agentId, mcps) {
494
+ const config = loadAgentConfig();
495
+ const existing = config[agentId] ?? getAgentRuntime(agentId);
496
+ existing.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
497
+ config[agentId] = existing;
498
+ saveAgentConfig(config);
499
+ return { ok: true };
500
+ }
501
+ function clearAgentRuntime(agentId) {
502
+ const config = loadAgentConfig();
503
+ delete config[agentId];
504
+ saveAgentConfig(config);
505
+ }
506
+ var AGENT_CONFIG_PATH, KNOWN_RUNTIMES, RUNTIME_LABELS, DEFAULT_MODELS;
507
+ var init_agent_config = __esm({
508
+ "src/lib/agent-config.ts"() {
509
+ "use strict";
510
+ init_config();
511
+ init_runtime_table();
512
+ init_secure_files();
513
+ AGENT_CONFIG_PATH = path2.join(EXE_AI_DIR, "agent-config.json");
514
+ KNOWN_RUNTIMES = {
515
+ claude: ["claude-opus-4.6", "claude-opus-4", "claude-sonnet-4.6", "claude-sonnet-4", "claude-haiku-4.5"],
516
+ codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
517
+ opencode: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4", "google/gemini-2.5-pro", "deepseek/deepseek-r3", "minimax/minimax-m2.5"]
518
+ };
519
+ RUNTIME_LABELS = {
520
+ claude: "Claude Code (Anthropic)",
521
+ codex: "Codex (OpenAI)",
522
+ opencode: "OpenCode (open source)"
523
+ };
524
+ DEFAULT_MODELS = {
525
+ claude: "claude-opus-4.6",
526
+ codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
527
+ opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
528
+ };
529
+ }
530
+ });
531
+
401
532
  // src/lib/employees.ts
533
+ var employees_exports = {};
534
+ __export(employees_exports, {
535
+ COORDINATOR_ROLE: () => COORDINATOR_ROLE,
536
+ DEFAULT_COORDINATOR_TEMPLATE_NAME: () => DEFAULT_COORDINATOR_TEMPLATE_NAME,
537
+ EMPLOYEES_PATH: () => EMPLOYEES_PATH,
538
+ addEmployee: () => addEmployee,
539
+ baseAgentName: () => baseAgentName,
540
+ canCoordinate: () => canCoordinate,
541
+ getCoordinatorEmployee: () => getCoordinatorEmployee,
542
+ getCoordinatorName: () => getCoordinatorName,
543
+ getEmployee: () => getEmployee,
544
+ getEmployeeByRole: () => getEmployeeByRole,
545
+ getEmployeeNamesByRole: () => getEmployeeNamesByRole,
546
+ hasRole: () => hasRole,
547
+ hireEmployee: () => hireEmployee,
548
+ isCoordinatorName: () => isCoordinatorName,
549
+ isCoordinatorRole: () => isCoordinatorRole,
550
+ isMultiInstance: () => isMultiInstance,
551
+ loadEmployees: () => loadEmployees,
552
+ loadEmployeesSync: () => loadEmployeesSync,
553
+ normalizeRole: () => normalizeRole,
554
+ normalizeRosterCase: () => normalizeRosterCase,
555
+ registerBinSymlinks: () => registerBinSymlinks,
556
+ saveEmployees: () => saveEmployees,
557
+ validateEmployeeName: () => validateEmployeeName
558
+ });
402
559
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
403
- import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
560
+ import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
404
561
  import { execSync as execSync2 } from "child_process";
405
- import path2 from "path";
562
+ import path3 from "path";
406
563
  import os2 from "os";
407
564
  function normalizeRole(role) {
408
565
  return (role ?? "").trim().toLowerCase();
@@ -420,8 +577,26 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
420
577
  if (!agentName) return false;
421
578
  return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
422
579
  }
580
+ function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
581
+ return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
582
+ }
583
+ function validateEmployeeName(name) {
584
+ if (!name) {
585
+ return { valid: false, error: "Name is required" };
586
+ }
587
+ if (name.length > 32) {
588
+ return { valid: false, error: "Name must be 32 characters or fewer" };
589
+ }
590
+ if (!/^[a-z][a-z0-9]*$/.test(name)) {
591
+ return {
592
+ valid: false,
593
+ error: "Name must start with a letter and contain only lowercase alphanumeric characters"
594
+ };
595
+ }
596
+ return { valid: true };
597
+ }
423
598
  async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
424
- if (!existsSync3(employeesPath)) {
599
+ if (!existsSync4(employeesPath)) {
425
600
  return [];
426
601
  }
427
602
  const raw = await readFile2(employeesPath, "utf-8");
@@ -431,10 +606,14 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
431
606
  return [];
432
607
  }
433
608
  }
609
+ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
610
+ await mkdir2(path3.dirname(employeesPath), { recursive: true });
611
+ await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
612
+ }
434
613
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
435
- if (!existsSync3(employeesPath)) return [];
614
+ if (!existsSync4(employeesPath)) return [];
436
615
  try {
437
- return JSON.parse(readFileSync2(employeesPath, "utf-8"));
616
+ return JSON.parse(readFileSync3(employeesPath, "utf-8"));
438
617
  } catch {
439
618
  return [];
440
619
  }
@@ -442,6 +621,19 @@ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
442
621
  function getEmployee(employees, name) {
443
622
  return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
444
623
  }
624
+ function getEmployeeByRole(employees, role) {
625
+ const lower = role.toLowerCase();
626
+ return employees.find((e) => e.role.toLowerCase() === lower);
627
+ }
628
+ function getEmployeeNamesByRole(employees, role) {
629
+ const lower = role.toLowerCase();
630
+ return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
631
+ }
632
+ function hasRole(agentName, role) {
633
+ const employees = loadEmployeesSync();
634
+ const emp = getEmployee(employees, agentName);
635
+ return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
636
+ }
445
637
  function baseAgentName(name, employees) {
446
638
  const match = name.match(/^([a-zA-Z]+)\d+$/);
447
639
  if (!match) return name;
@@ -456,26 +648,151 @@ function isMultiInstance(agentName, employees) {
456
648
  if (!emp) return false;
457
649
  return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
458
650
  }
459
- var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR;
651
+ function addEmployee(employees, employee) {
652
+ const { systemPrompt: _legacyPrompt, ...rest } = employee;
653
+ const normalized = { ...rest, name: employee.name.toLowerCase() };
654
+ if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
655
+ throw new Error(`Employee '${normalized.name}' already exists`);
656
+ }
657
+ return [...employees, normalized];
658
+ }
659
+ function appendToCoordinatorTeam(employee) {
660
+ const coordinator = getCoordinatorEmployee(loadEmployeesSync());
661
+ if (!coordinator) return;
662
+ const idPath = path3.join(IDENTITY_DIR, `${coordinator.name}.md`);
663
+ if (!existsSync4(idPath)) return;
664
+ const content = readFileSync3(idPath, "utf-8");
665
+ if (content.includes(`**${capitalize(employee.name)}`)) return;
666
+ const teamMatch = content.match(TEAM_SECTION_RE);
667
+ if (!teamMatch || teamMatch.index === void 0) return;
668
+ const afterTeam = content.slice(teamMatch.index + teamMatch[0].length);
669
+ const nextHeading = afterTeam.match(/\n## /);
670
+ const entry = `
671
+ **${capitalize(employee.name)} (${employee.role}):** Newly hired. Update this description as the role develops.
672
+ `;
673
+ let updated;
674
+ if (nextHeading && nextHeading.index !== void 0) {
675
+ const insertAt = teamMatch.index + teamMatch[0].length + nextHeading.index;
676
+ updated = content.slice(0, insertAt) + entry + content.slice(insertAt);
677
+ } else {
678
+ updated = content.trimEnd() + "\n" + entry;
679
+ }
680
+ writeFileSync2(idPath, updated, "utf-8");
681
+ }
682
+ function capitalize(s) {
683
+ return s.charAt(0).toUpperCase() + s.slice(1);
684
+ }
685
+ async function hireEmployee(employee) {
686
+ const employees = await loadEmployees();
687
+ const updated = addEmployee(employees, employee);
688
+ await saveEmployees(updated);
689
+ try {
690
+ appendToCoordinatorTeam(employee);
691
+ } catch {
692
+ }
693
+ try {
694
+ const { loadAgentConfig: loadAgentConfig2, saveAgentConfig: saveAgentConfig2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
695
+ const config = loadAgentConfig2();
696
+ const name = employee.name.toLowerCase();
697
+ if (!config[name] && config["default"]) {
698
+ config[name] = { ...config["default"] };
699
+ saveAgentConfig2(config);
700
+ }
701
+ } catch {
702
+ }
703
+ return updated;
704
+ }
705
+ async function normalizeRosterCase(rosterPath) {
706
+ const employees = await loadEmployees(rosterPath);
707
+ let changed = false;
708
+ for (const emp of employees) {
709
+ if (emp.name !== emp.name.toLowerCase()) {
710
+ const oldName = emp.name;
711
+ emp.name = emp.name.toLowerCase();
712
+ changed = true;
713
+ try {
714
+ const identityDir = path3.join(os2.homedir(), ".exe-os", "identity");
715
+ const oldPath = path3.join(identityDir, `${oldName}.md`);
716
+ const newPath = path3.join(identityDir, `${emp.name}.md`);
717
+ if (existsSync4(oldPath) && !existsSync4(newPath)) {
718
+ renameSync2(oldPath, newPath);
719
+ } else if (existsSync4(oldPath) && oldPath !== newPath) {
720
+ const content = readFileSync3(oldPath, "utf-8");
721
+ writeFileSync2(newPath, content, "utf-8");
722
+ if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
723
+ unlinkSync(oldPath);
724
+ }
725
+ }
726
+ } catch {
727
+ }
728
+ }
729
+ }
730
+ if (changed) {
731
+ await saveEmployees(employees, rosterPath);
732
+ }
733
+ return changed;
734
+ }
735
+ function findExeBin() {
736
+ try {
737
+ return execSync2(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
738
+ } catch {
739
+ return null;
740
+ }
741
+ }
742
+ function registerBinSymlinks(name) {
743
+ const created = [];
744
+ const skipped = [];
745
+ const errors = [];
746
+ const exeBinPath = findExeBin();
747
+ if (!exeBinPath) {
748
+ errors.push("Could not find 'exe-os' in PATH");
749
+ return { created, skipped, errors };
750
+ }
751
+ const binDir = path3.dirname(exeBinPath);
752
+ let target;
753
+ try {
754
+ target = readlinkSync(exeBinPath);
755
+ } catch {
756
+ errors.push("Could not read 'exe' symlink");
757
+ return { created, skipped, errors };
758
+ }
759
+ for (const suffix of ["", "-opencode"]) {
760
+ const linkName = `${name}${suffix}`;
761
+ const linkPath = path3.join(binDir, linkName);
762
+ if (existsSync4(linkPath)) {
763
+ skipped.push(linkName);
764
+ continue;
765
+ }
766
+ try {
767
+ symlinkSync(target, linkPath);
768
+ created.push(linkName);
769
+ } catch (err) {
770
+ errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
771
+ }
772
+ }
773
+ return { created, skipped, errors };
774
+ }
775
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR, TEAM_SECTION_RE;
460
776
  var init_employees = __esm({
461
777
  "src/lib/employees.ts"() {
462
778
  "use strict";
463
779
  init_config();
464
- EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
780
+ EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
465
781
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
466
782
  COORDINATOR_ROLE = "COO";
467
783
  MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
468
- IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
784
+ IDENTITY_DIR = path3.join(EXE_AI_DIR, "identity");
785
+ TEAM_SECTION_RE = /^## Team\b.*$/m;
469
786
  }
470
787
  });
471
788
 
472
789
  // src/lib/session-registry.ts
473
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync4 } from "fs";
474
- import path4 from "path";
790
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3, existsSync as existsSync5 } from "fs";
791
+ import path5 from "path";
475
792
  import os3 from "os";
476
793
  function registerSession(entry) {
477
- const dir = path4.dirname(REGISTRY_PATH);
478
- if (!existsSync4(dir)) {
794
+ const dir = path5.dirname(REGISTRY_PATH);
795
+ if (!existsSync5(dir)) {
479
796
  mkdirSync3(dir, { recursive: true });
480
797
  }
481
798
  const sessions = listSessions();
@@ -485,11 +802,11 @@ function registerSession(entry) {
485
802
  } else {
486
803
  sessions.push(entry);
487
804
  }
488
- writeFileSync3(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
805
+ writeFileSync4(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
489
806
  }
490
807
  function listSessions() {
491
808
  try {
492
- const raw = readFileSync4(REGISTRY_PATH, "utf8");
809
+ const raw = readFileSync5(REGISTRY_PATH, "utf8");
493
810
  return JSON.parse(raw);
494
811
  } catch {
495
812
  return [];
@@ -499,7 +816,7 @@ var REGISTRY_PATH;
499
816
  var init_session_registry = __esm({
500
817
  "src/lib/session-registry.ts"() {
501
818
  "use strict";
502
- REGISTRY_PATH = path4.join(os3.homedir(), ".exe-os", "session-registry.json");
819
+ REGISTRY_PATH = path5.join(os3.homedir(), ".exe-os", "session-registry.json");
503
820
  }
504
821
  });
505
822
 
@@ -693,68 +1010,6 @@ var init_provider_table = __esm({
693
1010
  }
694
1011
  });
695
1012
 
696
- // src/lib/runtime-table.ts
697
- var RUNTIME_TABLE, DEFAULT_RUNTIME;
698
- var init_runtime_table = __esm({
699
- "src/lib/runtime-table.ts"() {
700
- "use strict";
701
- RUNTIME_TABLE = {
702
- codex: {
703
- binary: "codex",
704
- launchMode: "interactive",
705
- autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
706
- inlineFlag: "--no-alt-screen",
707
- apiKeyEnv: "OPENAI_API_KEY",
708
- defaultModel: "gpt-5.5"
709
- },
710
- opencode: {
711
- binary: "opencode",
712
- launchMode: "exec",
713
- autoApproveFlag: "--dangerously-skip-permissions",
714
- inlineFlag: "",
715
- apiKeyEnv: "ANTHROPIC_API_KEY",
716
- defaultModel: "anthropic/claude-sonnet-4-6"
717
- }
718
- };
719
- DEFAULT_RUNTIME = "claude";
720
- }
721
- });
722
-
723
- // src/lib/agent-config.ts
724
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync5 } from "fs";
725
- import path5 from "path";
726
- function loadAgentConfig() {
727
- if (!existsSync5(AGENT_CONFIG_PATH)) return {};
728
- try {
729
- return JSON.parse(readFileSync5(AGENT_CONFIG_PATH, "utf-8"));
730
- } catch {
731
- return {};
732
- }
733
- }
734
- function getAgentRuntime(agentId) {
735
- const config = loadAgentConfig();
736
- const entry = config[agentId];
737
- if (entry) return entry;
738
- const orgDefault = config["default"];
739
- if (orgDefault) return orgDefault;
740
- return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
741
- }
742
- var AGENT_CONFIG_PATH, DEFAULT_MODELS;
743
- var init_agent_config = __esm({
744
- "src/lib/agent-config.ts"() {
745
- "use strict";
746
- init_config();
747
- init_runtime_table();
748
- init_secure_files();
749
- AGENT_CONFIG_PATH = path5.join(EXE_AI_DIR, "agent-config.json");
750
- DEFAULT_MODELS = {
751
- claude: "claude-opus-4.6",
752
- codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
753
- opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
754
- };
755
- }
756
- });
757
-
758
1013
  // src/lib/intercom-queue.ts
759
1014
  var intercom_queue_exports = {};
760
1015
  __export(intercom_queue_exports, {
@@ -3837,6 +4092,22 @@ async function ensureSchema() {
3837
4092
  } catch (e) {
3838
4093
  logCatchDebug("migration", e);
3839
4094
  }
4095
+ try {
4096
+ await client.execute({
4097
+ sql: `ALTER TABLE memories ADD COLUMN visibility TEXT DEFAULT 'private'`,
4098
+ args: []
4099
+ });
4100
+ } catch (e) {
4101
+ logCatchDebug("migration", e);
4102
+ }
4103
+ try {
4104
+ await client.execute({
4105
+ sql: `ALTER TABLE memories ADD COLUMN strength REAL DEFAULT 1.0`,
4106
+ args: []
4107
+ });
4108
+ } catch (e) {
4109
+ logCatchDebug("migration", e);
4110
+ }
3840
4111
  }
3841
4112
  async function disposeDatabase() {
3842
4113
  if (_walCheckpointTimer) {
@@ -4255,7 +4526,7 @@ async function assertVpsLicense(opts) {
4255
4526
  }
4256
4527
  if (!transientFailure) {
4257
4528
  throw new Error(
4258
- "License validation failed: unknown backend state. Restore network connectivity to https://askexe.com/cloud and retry."
4529
+ "License validation failed: unknown backend state. Restore network connectivity to https://cloud.askexe.com and retry."
4259
4530
  );
4260
4531
  }
4261
4532
  const fresh = await getCachedLicense();
@@ -4292,7 +4563,7 @@ async function assertVpsLicense(opts) {
4292
4563
  } catch {
4293
4564
  }
4294
4565
  throw new Error(
4295
- `License validation unreachable for more than ${graceDays} days. Restore network connectivity to https://askexe.com/cloud and retry. This VPS image refuses to boot after the offline grace window.`
4566
+ `License validation unreachable for more than ${graceDays} days. Restore network connectivity to https://cloud.askexe.com and retry. This VPS image refuses to boot after the offline grace window.`
4296
4567
  );
4297
4568
  }
4298
4569
  function startLicenseRevalidation(intervalMs = 36e5) {
@@ -4324,7 +4595,7 @@ var init_license = __esm({
4324
4595
  LICENSE_PATH = path10.join(EXE_AI_DIR, "license.key");
4325
4596
  CACHE_PATH = path10.join(EXE_AI_DIR, "license-cache.json");
4326
4597
  DEVICE_ID_PATH = path10.join(EXE_AI_DIR, "device-id");
4327
- API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com/cloud";
4598
+ API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://cloud.askexe.com";
4328
4599
  RETRY_DELAY_MS = 500;
4329
4600
  LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
4330
4601
  MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
@@ -4829,6 +5100,19 @@ async function resolveTask(client, identifier, scopeSession) {
4829
5100
  args: [identifier, ...scope.args]
4830
5101
  });
4831
5102
  if (result.rows.length === 1) return result.rows[0];
5103
+ if (/^[a-f0-9]{7,12}$/i.test(identifier)) {
5104
+ result = await client.execute({
5105
+ sql: `SELECT * FROM tasks WHERE id LIKE ?`,
5106
+ args: [`${identifier}%`]
5107
+ });
5108
+ if (result.rows.length === 1) return result.rows[0];
5109
+ if (result.rows.length > 1) {
5110
+ const matches = result.rows.map((r) => `${String(r.id)} "${String(r.title)}" (${String(r.status)})`).join(", ");
5111
+ throw new Error(
5112
+ `Multiple tasks match short-ID "${identifier}": ${matches}. Use a longer prefix to disambiguate.`
5113
+ );
5114
+ }
5115
+ }
4832
5116
  result = await client.execute({
4833
5117
  sql: `SELECT * FROM tasks WHERE task_file LIKE ?${scope.sql}`,
4834
5118
  args: [`%${identifier}%`, ...scope.args]
@@ -5683,12 +5967,13 @@ async function cascadeUnblock(taskId, baseDir, now) {
5683
5967
  WHERE blocked_by = ? AND status = 'blocked'`,
5684
5968
  args: [now, taskId]
5685
5969
  });
5686
- if (baseDir && unblocked.rowsAffected > 0) {
5687
- const ubScope = sessionScopeFilter();
5688
- const unblockedRows = await client.execute({
5689
- sql: `SELECT task_file FROM tasks WHERE blocked_by IS NULL AND updated_at = ?${ubScope.sql}`,
5690
- args: [now, ...ubScope.args]
5691
- });
5970
+ if (unblocked.rowsAffected === 0) return;
5971
+ const ubScope = sessionScopeFilter();
5972
+ const unblockedRows = await client.execute({
5973
+ sql: `SELECT id, title, assigned_to, priority, task_file FROM tasks WHERE blocked_by IS NULL AND updated_at = ?${ubScope.sql}`,
5974
+ args: [now, ...ubScope.args]
5975
+ });
5976
+ if (baseDir) {
5692
5977
  for (const ur of unblockedRows.rows) {
5693
5978
  try {
5694
5979
  const ubFile = path17.join(baseDir, String(ur.task_file));
@@ -5700,6 +5985,19 @@ async function cascadeUnblock(taskId, baseDir, now) {
5700
5985
  }
5701
5986
  }
5702
5987
  }
5988
+ if (unblockedRows.rows.length > 0 && !process.env.VITEST) {
5989
+ try {
5990
+ const { queueIntercom: queueIntercom2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
5991
+ const dispatched = /* @__PURE__ */ new Set();
5992
+ for (const ur of unblockedRows.rows) {
5993
+ const assignee = String(ur.assigned_to);
5994
+ if (dispatched.has(assignee)) continue;
5995
+ dispatched.add(assignee);
5996
+ queueIntercom2(`${assignee}`, `unblocked: "${String(ur.title)}" is now ready`);
5997
+ }
5998
+ } catch {
5999
+ }
6000
+ }
5703
6001
  }
5704
6002
  async function findNextTask(assignedTo) {
5705
6003
  const client = getClient();
@@ -5909,6 +6207,15 @@ var init_embedder = __esm({
5909
6207
  // src/lib/behaviors.ts
5910
6208
  import crypto5 from "crypto";
5911
6209
  async function storeBehavior(opts) {
6210
+ try {
6211
+ const { loadEmployeesSync: loadEmployeesSync2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
6212
+ const roster = loadEmployeesSync2();
6213
+ if (roster.length > 0 && !roster.some((e) => e.name === opts.agentId)) {
6214
+ throw new Error(`Agent "${opts.agentId}" not found in roster. Cannot store behavior for unregistered agent.`);
6215
+ }
6216
+ } catch (e) {
6217
+ if (e instanceof Error && e.message.includes("not found in roster")) throw e;
6218
+ }
5912
6219
  const client = getClient();
5913
6220
  const id = crypto5.randomUUID();
5914
6221
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -6362,6 +6669,12 @@ async function updateTask(input2) {
6362
6669
  }
6363
6670
  }
6364
6671
  }
6672
+ if (input2.status === "cancelled") {
6673
+ try {
6674
+ await cascadeUnblock(taskId, input2.baseDir, now);
6675
+ } catch {
6676
+ }
6677
+ }
6365
6678
  if ((input2.status === "done" || input2.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
6366
6679
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
6367
6680
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
@@ -6893,11 +7206,12 @@ function getDispatchedBy(sessionKey) {
6893
7206
  }
6894
7207
  }
6895
7208
  function resolveExeSession() {
7209
+ if (process.env.EXE_SESSION_NAME) {
7210
+ const fromEnv = extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
7211
+ if (fromEnv) return fromEnv;
7212
+ }
6896
7213
  const mySession = getMySession();
6897
7214
  if (!mySession) {
6898
- if (process.env.EXE_SESSION_NAME) {
6899
- return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
6900
- }
6901
7215
  return null;
6902
7216
  }
6903
7217
  const fromSessionName = extractRootExe(mySession);
@@ -6912,6 +7226,10 @@ function resolveExeSession() {
6912
7226
  `[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
6913
7227
  `
6914
7228
  );
7229
+ try {
7230
+ registerParentExe(key, fromSessionName);
7231
+ } catch {
7232
+ }
6915
7233
  candidate = fromSessionName;
6916
7234
  } else {
6917
7235
  candidate = fromCache;
@@ -8639,11 +8957,17 @@ var init_platform_procedures = __esm({
8639
8957
  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."
8640
8958
  },
8641
8959
  {
8642
- title: "Customer orchestration maturity \u2014 recommend, never trap",
8960
+ title: "Orchestration phase guidance \u2014 recommend, never trap",
8643
8961
  domain: "workflow",
8644
8962
  priority: "p1",
8645
8963
  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."
8646
8964
  },
8965
+ {
8966
+ title: "Routing slot vs display title \u2014 internal 'coo' is plumbing, not your name",
8967
+ domain: "identity",
8968
+ priority: "p0",
8969
+ 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."
8970
+ },
8647
8971
  {
8648
8972
  title: "Single dispatch path \u2014 create_task only",
8649
8973
  domain: "workflow",
@@ -8677,6 +9001,12 @@ var init_platform_procedures = __esm({
8677
9001
  priority: "p0",
8678
9002
  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."
8679
9003
  },
9004
+ {
9005
+ title: "Destructive operations \u2014 mandatory reviewer gate",
9006
+ domain: "security",
9007
+ priority: "p0",
9008
+ 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."
9009
+ },
8680
9010
  {
8681
9011
  title: "Customer patch triage \u2014 upstream bug vs customization",
8682
9012
  domain: "support",
@@ -8962,10 +9292,24 @@ function stableId(memoryId, type, content) {
8962
9292
  return createHash2("sha256").update(`${memoryId}:${type}:${content}`).digest("hex").slice(0, 32);
8963
9293
  }
8964
9294
  function cleanText(text) {
8965
- return text.replace(/```[\s\S]*?```/g, " ").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
9295
+ let cleaned = text.replace(
9296
+ /```(\w*)\n(.*?)(?:\n[\s\S]*?)```/g,
9297
+ (_m, lang, firstLine) => `[code${lang ? `:${lang}` : ""}] ${firstLine.trim()}`
9298
+ );
9299
+ cleaned = cleaned.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
9300
+ return cleaned;
8966
9301
  }
8967
- function splitSentences(text) {
8968
- return cleanText(text).split(/(?<=[.!?])\s+|\n+/).map((s) => s.trim()).filter((s) => s.length >= 24 && s.length <= MAX_SENTENCE_CHARS);
9302
+ function splitSegments(text) {
9303
+ const cleaned = cleanText(text);
9304
+ 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);
9305
+ if (segments.length === 0 && cleaned.length >= MIN_SEGMENT_CHARS) {
9306
+ const lines = cleaned.split(/\n+/).map((l) => l.trim()).filter((l) => l.length >= MIN_SEGMENT_CHARS && l.length <= MAX_SEGMENT_CHARS);
9307
+ if (lines.length > 0) return lines;
9308
+ if (cleaned.length >= MIN_SEGMENT_CHARS) {
9309
+ return [cleaned.slice(0, MAX_SEGMENT_CHARS)];
9310
+ }
9311
+ }
9312
+ return segments;
8969
9313
  }
8970
9314
  function inferCardType(sentence, toolName) {
8971
9315
  const lower = sentence.toLowerCase();
@@ -8997,12 +9341,12 @@ function predicateFor(type) {
8997
9341
  }
8998
9342
  }
8999
9343
  function extractMemoryCards(row) {
9000
- const sentences = splitSentences(row.raw_text);
9344
+ const segments = splitSegments(row.raw_text);
9001
9345
  const cards = [];
9002
- for (const sentence of sentences) {
9346
+ for (const sentence of segments) {
9003
9347
  const type = inferCardType(sentence, row.tool_name);
9004
9348
  const subject = extractSubject(sentence, row.agent_id);
9005
- const content = sentence.length > MAX_SENTENCE_CHARS ? `${sentence.slice(0, MAX_SENTENCE_CHARS - 1)}\u2026` : sentence;
9349
+ const content = sentence.length > MAX_SEGMENT_CHARS ? `${sentence.slice(0, MAX_SEGMENT_CHARS - 1)}\u2026` : sentence;
9006
9350
  cards.push({
9007
9351
  id: stableId(row.id, type, content),
9008
9352
  memory_id: row.id,
@@ -9098,13 +9442,14 @@ Source memory: ${String(row.source_ref ?? row.memory_id)}`,
9098
9442
  last_accessed: String(row.timestamp)
9099
9443
  }));
9100
9444
  }
9101
- var MAX_CARDS_PER_MEMORY, MAX_SENTENCE_CHARS;
9445
+ var MAX_CARDS_PER_MEMORY, MAX_SEGMENT_CHARS, MIN_SEGMENT_CHARS;
9102
9446
  var init_memory_cards = __esm({
9103
9447
  "src/lib/memory-cards.ts"() {
9104
9448
  "use strict";
9105
9449
  init_database();
9106
- MAX_CARDS_PER_MEMORY = 6;
9107
- MAX_SENTENCE_CHARS = 360;
9450
+ MAX_CARDS_PER_MEMORY = 8;
9451
+ MAX_SEGMENT_CHARS = 500;
9452
+ MIN_SEGMENT_CHARS = 20;
9108
9453
  }
9109
9454
  });
9110
9455
 
@@ -10114,9 +10459,9 @@ var init_fast_db_init = __esm({
10114
10459
  // src/lib/active-agent.ts
10115
10460
  init_config();
10116
10461
  init_session_key();
10117
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2, readdirSync } from "fs";
10462
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2, readdirSync } from "fs";
10118
10463
  import { execSync as execSync3 } from "child_process";
10119
- import path3 from "path";
10464
+ import path4 from "path";
10120
10465
 
10121
10466
  // src/mcp/agent-context.ts
10122
10467
  import { AsyncLocalStorage } from "async_hooks";
@@ -10127,7 +10472,7 @@ function getAgentContext() {
10127
10472
 
10128
10473
  // src/lib/active-agent.ts
10129
10474
  init_employees();
10130
- var CACHE_DIR = path3.join(EXE_AI_DIR, "session-cache");
10475
+ var CACHE_DIR = path4.join(EXE_AI_DIR, "session-cache");
10131
10476
  var STALE_MS = 24 * 60 * 60 * 1e3;
10132
10477
  function isNameWithOptionalInstance(candidate, baseName) {
10133
10478
  if (candidate === baseName) return true;
@@ -10172,14 +10517,14 @@ function resolveActiveAgentFromTmuxSession(sessionName) {
10172
10517
  return null;
10173
10518
  }
10174
10519
  function getMarkerPath() {
10175
- return path3.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
10520
+ return path4.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
10176
10521
  }
10177
10522
  function getActiveAgent() {
10178
10523
  const httpCtx = getAgentContext();
10179
10524
  if (httpCtx) return httpCtx;
10180
10525
  try {
10181
10526
  const markerPath = getMarkerPath();
10182
- const raw = readFileSync3(markerPath, "utf8");
10527
+ const raw = readFileSync4(markerPath, "utf8");
10183
10528
  const data = JSON.parse(raw);
10184
10529
  if (data.agentId) {
10185
10530
  if (data.startedAt) {