@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
@@ -386,11 +386,168 @@ var init_config = __esm({
386
386
  }
387
387
  });
388
388
 
389
+ // src/lib/runtime-table.ts
390
+ var RUNTIME_TABLE, DEFAULT_RUNTIME;
391
+ var init_runtime_table = __esm({
392
+ "src/lib/runtime-table.ts"() {
393
+ "use strict";
394
+ RUNTIME_TABLE = {
395
+ codex: {
396
+ binary: "codex",
397
+ launchMode: "interactive",
398
+ autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
399
+ inlineFlag: "--no-alt-screen",
400
+ apiKeyEnv: "OPENAI_API_KEY",
401
+ defaultModel: "gpt-5.5"
402
+ },
403
+ opencode: {
404
+ binary: "opencode",
405
+ launchMode: "exec",
406
+ autoApproveFlag: "--dangerously-skip-permissions",
407
+ inlineFlag: "",
408
+ apiKeyEnv: "ANTHROPIC_API_KEY",
409
+ defaultModel: "anthropic/claude-sonnet-4-6"
410
+ }
411
+ };
412
+ DEFAULT_RUNTIME = "claude";
413
+ }
414
+ });
415
+
416
+ // src/lib/agent-config.ts
417
+ var agent_config_exports = {};
418
+ __export(agent_config_exports, {
419
+ AGENT_CONFIG_PATH: () => AGENT_CONFIG_PATH,
420
+ DEFAULT_MODELS: () => DEFAULT_MODELS,
421
+ KNOWN_RUNTIMES: () => KNOWN_RUNTIMES,
422
+ RUNTIME_LABELS: () => RUNTIME_LABELS,
423
+ clearAgentRuntime: () => clearAgentRuntime,
424
+ getAgentRuntime: () => getAgentRuntime,
425
+ loadAgentConfig: () => loadAgentConfig,
426
+ saveAgentConfig: () => saveAgentConfig,
427
+ setAgentMcps: () => setAgentMcps,
428
+ setAgentRuntime: () => setAgentRuntime
429
+ });
430
+ import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
431
+ import path2 from "path";
432
+ function loadAgentConfig() {
433
+ if (!existsSync3(AGENT_CONFIG_PATH)) return {};
434
+ try {
435
+ return JSON.parse(readFileSync2(AGENT_CONFIG_PATH, "utf-8"));
436
+ } catch {
437
+ return {};
438
+ }
439
+ }
440
+ function saveAgentConfig(config) {
441
+ const dir = path2.dirname(AGENT_CONFIG_PATH);
442
+ ensurePrivateDirSync(dir);
443
+ writeFileSync(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
444
+ enforcePrivateFileSync(AGENT_CONFIG_PATH);
445
+ }
446
+ function getAgentRuntime(agentId) {
447
+ const config = loadAgentConfig();
448
+ const entry = config[agentId];
449
+ if (entry) return entry;
450
+ const orgDefault = config["default"];
451
+ if (orgDefault) return orgDefault;
452
+ return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
453
+ }
454
+ function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
455
+ const knownModels = KNOWN_RUNTIMES[runtime];
456
+ if (!knownModels) {
457
+ return {
458
+ ok: false,
459
+ error: `Unknown runtime "${runtime}". Valid: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`
460
+ };
461
+ }
462
+ if (!knownModels.includes(model)) {
463
+ return {
464
+ ok: false,
465
+ error: `Unknown model "${model}" for runtime "${runtime}". Valid: ${knownModels.join(", ")}`
466
+ };
467
+ }
468
+ const config = loadAgentConfig();
469
+ const existing = config[agentId];
470
+ const entry = { runtime, model };
471
+ if (reasoning_effort) entry.reasoning_effort = reasoning_effort;
472
+ if (mcps !== void 0) {
473
+ entry.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
474
+ } else if (existing?.mcps) {
475
+ entry.mcps = existing.mcps;
476
+ }
477
+ config[agentId] = entry;
478
+ saveAgentConfig(config);
479
+ return { ok: true };
480
+ }
481
+ function setAgentMcps(agentId, mcps) {
482
+ const config = loadAgentConfig();
483
+ const existing = config[agentId] ?? getAgentRuntime(agentId);
484
+ existing.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
485
+ config[agentId] = existing;
486
+ saveAgentConfig(config);
487
+ return { ok: true };
488
+ }
489
+ function clearAgentRuntime(agentId) {
490
+ const config = loadAgentConfig();
491
+ delete config[agentId];
492
+ saveAgentConfig(config);
493
+ }
494
+ var AGENT_CONFIG_PATH, KNOWN_RUNTIMES, RUNTIME_LABELS, DEFAULT_MODELS;
495
+ var init_agent_config = __esm({
496
+ "src/lib/agent-config.ts"() {
497
+ "use strict";
498
+ init_config();
499
+ init_runtime_table();
500
+ init_secure_files();
501
+ AGENT_CONFIG_PATH = path2.join(EXE_AI_DIR, "agent-config.json");
502
+ KNOWN_RUNTIMES = {
503
+ claude: ["claude-opus-4.6", "claude-opus-4", "claude-sonnet-4.6", "claude-sonnet-4", "claude-haiku-4.5"],
504
+ codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
505
+ opencode: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4", "google/gemini-2.5-pro", "deepseek/deepseek-r3", "minimax/minimax-m2.5"]
506
+ };
507
+ RUNTIME_LABELS = {
508
+ claude: "Claude Code (Anthropic)",
509
+ codex: "Codex (OpenAI)",
510
+ opencode: "OpenCode (open source)"
511
+ };
512
+ DEFAULT_MODELS = {
513
+ claude: "claude-opus-4.6",
514
+ codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
515
+ opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
516
+ };
517
+ }
518
+ });
519
+
389
520
  // src/lib/employees.ts
521
+ var employees_exports = {};
522
+ __export(employees_exports, {
523
+ COORDINATOR_ROLE: () => COORDINATOR_ROLE,
524
+ DEFAULT_COORDINATOR_TEMPLATE_NAME: () => DEFAULT_COORDINATOR_TEMPLATE_NAME,
525
+ EMPLOYEES_PATH: () => EMPLOYEES_PATH,
526
+ addEmployee: () => addEmployee,
527
+ baseAgentName: () => baseAgentName,
528
+ canCoordinate: () => canCoordinate,
529
+ getCoordinatorEmployee: () => getCoordinatorEmployee,
530
+ getCoordinatorName: () => getCoordinatorName,
531
+ getEmployee: () => getEmployee,
532
+ getEmployeeByRole: () => getEmployeeByRole,
533
+ getEmployeeNamesByRole: () => getEmployeeNamesByRole,
534
+ hasRole: () => hasRole,
535
+ hireEmployee: () => hireEmployee,
536
+ isCoordinatorName: () => isCoordinatorName,
537
+ isCoordinatorRole: () => isCoordinatorRole,
538
+ isMultiInstance: () => isMultiInstance,
539
+ loadEmployees: () => loadEmployees,
540
+ loadEmployeesSync: () => loadEmployeesSync,
541
+ normalizeRole: () => normalizeRole,
542
+ normalizeRosterCase: () => normalizeRosterCase,
543
+ registerBinSymlinks: () => registerBinSymlinks,
544
+ saveEmployees: () => saveEmployees,
545
+ validateEmployeeName: () => validateEmployeeName
546
+ });
390
547
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
391
- import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
548
+ import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
392
549
  import { execSync } from "child_process";
393
- import path2 from "path";
550
+ import path3 from "path";
394
551
  import os2 from "os";
395
552
  function normalizeRole(role) {
396
553
  return (role ?? "").trim().toLowerCase();
@@ -408,8 +565,26 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
408
565
  if (!agentName) return false;
409
566
  return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
410
567
  }
568
+ function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
569
+ return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
570
+ }
571
+ function validateEmployeeName(name) {
572
+ if (!name) {
573
+ return { valid: false, error: "Name is required" };
574
+ }
575
+ if (name.length > 32) {
576
+ return { valid: false, error: "Name must be 32 characters or fewer" };
577
+ }
578
+ if (!/^[a-z][a-z0-9]*$/.test(name)) {
579
+ return {
580
+ valid: false,
581
+ error: "Name must start with a letter and contain only lowercase alphanumeric characters"
582
+ };
583
+ }
584
+ return { valid: true };
585
+ }
411
586
  async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
412
- if (!existsSync3(employeesPath)) {
587
+ if (!existsSync4(employeesPath)) {
413
588
  return [];
414
589
  }
415
590
  const raw = await readFile2(employeesPath, "utf-8");
@@ -419,10 +594,14 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
419
594
  return [];
420
595
  }
421
596
  }
597
+ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
598
+ await mkdir2(path3.dirname(employeesPath), { recursive: true });
599
+ await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
600
+ }
422
601
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
423
- if (!existsSync3(employeesPath)) return [];
602
+ if (!existsSync4(employeesPath)) return [];
424
603
  try {
425
- return JSON.parse(readFileSync2(employeesPath, "utf-8"));
604
+ return JSON.parse(readFileSync3(employeesPath, "utf-8"));
426
605
  } catch {
427
606
  return [];
428
607
  }
@@ -430,6 +609,19 @@ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
430
609
  function getEmployee(employees, name) {
431
610
  return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
432
611
  }
612
+ function getEmployeeByRole(employees, role) {
613
+ const lower = role.toLowerCase();
614
+ return employees.find((e) => e.role.toLowerCase() === lower);
615
+ }
616
+ function getEmployeeNamesByRole(employees, role) {
617
+ const lower = role.toLowerCase();
618
+ return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
619
+ }
620
+ function hasRole(agentName, role) {
621
+ const employees = loadEmployeesSync();
622
+ const emp = getEmployee(employees, agentName);
623
+ return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
624
+ }
433
625
  function baseAgentName(name, employees) {
434
626
  const match = name.match(/^([a-zA-Z]+)\d+$/);
435
627
  if (!match) return name;
@@ -444,22 +636,147 @@ function isMultiInstance(agentName, employees) {
444
636
  if (!emp) return false;
445
637
  return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
446
638
  }
447
- var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR;
639
+ function addEmployee(employees, employee) {
640
+ const { systemPrompt: _legacyPrompt, ...rest } = employee;
641
+ const normalized = { ...rest, name: employee.name.toLowerCase() };
642
+ if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
643
+ throw new Error(`Employee '${normalized.name}' already exists`);
644
+ }
645
+ return [...employees, normalized];
646
+ }
647
+ function appendToCoordinatorTeam(employee) {
648
+ const coordinator = getCoordinatorEmployee(loadEmployeesSync());
649
+ if (!coordinator) return;
650
+ const idPath = path3.join(IDENTITY_DIR, `${coordinator.name}.md`);
651
+ if (!existsSync4(idPath)) return;
652
+ const content = readFileSync3(idPath, "utf-8");
653
+ if (content.includes(`**${capitalize(employee.name)}`)) return;
654
+ const teamMatch = content.match(TEAM_SECTION_RE);
655
+ if (!teamMatch || teamMatch.index === void 0) return;
656
+ const afterTeam = content.slice(teamMatch.index + teamMatch[0].length);
657
+ const nextHeading = afterTeam.match(/\n## /);
658
+ const entry = `
659
+ **${capitalize(employee.name)} (${employee.role}):** Newly hired. Update this description as the role develops.
660
+ `;
661
+ let updated;
662
+ if (nextHeading && nextHeading.index !== void 0) {
663
+ const insertAt = teamMatch.index + teamMatch[0].length + nextHeading.index;
664
+ updated = content.slice(0, insertAt) + entry + content.slice(insertAt);
665
+ } else {
666
+ updated = content.trimEnd() + "\n" + entry;
667
+ }
668
+ writeFileSync2(idPath, updated, "utf-8");
669
+ }
670
+ function capitalize(s) {
671
+ return s.charAt(0).toUpperCase() + s.slice(1);
672
+ }
673
+ async function hireEmployee(employee) {
674
+ const employees = await loadEmployees();
675
+ const updated = addEmployee(employees, employee);
676
+ await saveEmployees(updated);
677
+ try {
678
+ appendToCoordinatorTeam(employee);
679
+ } catch {
680
+ }
681
+ try {
682
+ const { loadAgentConfig: loadAgentConfig2, saveAgentConfig: saveAgentConfig2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
683
+ const config = loadAgentConfig2();
684
+ const name = employee.name.toLowerCase();
685
+ if (!config[name] && config["default"]) {
686
+ config[name] = { ...config["default"] };
687
+ saveAgentConfig2(config);
688
+ }
689
+ } catch {
690
+ }
691
+ return updated;
692
+ }
693
+ async function normalizeRosterCase(rosterPath) {
694
+ const employees = await loadEmployees(rosterPath);
695
+ let changed = false;
696
+ for (const emp of employees) {
697
+ if (emp.name !== emp.name.toLowerCase()) {
698
+ const oldName = emp.name;
699
+ emp.name = emp.name.toLowerCase();
700
+ changed = true;
701
+ try {
702
+ const identityDir = path3.join(os2.homedir(), ".exe-os", "identity");
703
+ const oldPath = path3.join(identityDir, `${oldName}.md`);
704
+ const newPath = path3.join(identityDir, `${emp.name}.md`);
705
+ if (existsSync4(oldPath) && !existsSync4(newPath)) {
706
+ renameSync2(oldPath, newPath);
707
+ } else if (existsSync4(oldPath) && oldPath !== newPath) {
708
+ const content = readFileSync3(oldPath, "utf-8");
709
+ writeFileSync2(newPath, content, "utf-8");
710
+ if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
711
+ unlinkSync(oldPath);
712
+ }
713
+ }
714
+ } catch {
715
+ }
716
+ }
717
+ }
718
+ if (changed) {
719
+ await saveEmployees(employees, rosterPath);
720
+ }
721
+ return changed;
722
+ }
723
+ function findExeBin() {
724
+ try {
725
+ return execSync(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
726
+ } catch {
727
+ return null;
728
+ }
729
+ }
730
+ function registerBinSymlinks(name) {
731
+ const created = [];
732
+ const skipped = [];
733
+ const errors = [];
734
+ const exeBinPath = findExeBin();
735
+ if (!exeBinPath) {
736
+ errors.push("Could not find 'exe-os' in PATH");
737
+ return { created, skipped, errors };
738
+ }
739
+ const binDir = path3.dirname(exeBinPath);
740
+ let target;
741
+ try {
742
+ target = readlinkSync(exeBinPath);
743
+ } catch {
744
+ errors.push("Could not read 'exe' symlink");
745
+ return { created, skipped, errors };
746
+ }
747
+ for (const suffix of ["", "-opencode"]) {
748
+ const linkName = `${name}${suffix}`;
749
+ const linkPath = path3.join(binDir, linkName);
750
+ if (existsSync4(linkPath)) {
751
+ skipped.push(linkName);
752
+ continue;
753
+ }
754
+ try {
755
+ symlinkSync(target, linkPath);
756
+ created.push(linkName);
757
+ } catch (err) {
758
+ errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
759
+ }
760
+ }
761
+ return { created, skipped, errors };
762
+ }
763
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR, TEAM_SECTION_RE;
448
764
  var init_employees = __esm({
449
765
  "src/lib/employees.ts"() {
450
766
  "use strict";
451
767
  init_config();
452
- EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
768
+ EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
453
769
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
454
770
  COORDINATOR_ROLE = "COO";
455
771
  MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
456
- IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
772
+ IDENTITY_DIR = path3.join(EXE_AI_DIR, "identity");
773
+ TEAM_SECTION_RE = /^## Team\b.*$/m;
457
774
  }
458
775
  });
459
776
 
460
777
  // src/lib/database-adapter.ts
461
778
  import os3 from "os";
462
- import path3 from "path";
779
+ import path4 from "path";
463
780
  import { createRequire } from "module";
464
781
  import { pathToFileURL } from "url";
465
782
  function quotedIdentifier(identifier) {
@@ -770,8 +1087,8 @@ async function loadPrismaClient() {
770
1087
  }
771
1088
  return new PrismaClient2();
772
1089
  }
773
- const exeDbRoot = process.env.EXE_DB_ROOT ?? path3.join(os3.homedir(), "exe-db");
774
- const requireFromExeDb = createRequire(path3.join(exeDbRoot, "package.json"));
1090
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path4.join(os3.homedir(), "exe-db");
1091
+ const requireFromExeDb = createRequire(path4.join(exeDbRoot, "package.json"));
775
1092
  const prismaEntry = requireFromExeDb.resolve("@prisma/client");
776
1093
  const module = await import(pathToFileURL(prismaEntry).href);
777
1094
  const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
@@ -1052,8 +1369,8 @@ var init_memory = __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
 
@@ -1103,8 +1420,8 @@ import net from "net";
1103
1420
  import os4 from "os";
1104
1421
  import { spawn, execSync as execSync2 } from "child_process";
1105
1422
  import { randomUUID } from "crypto";
1106
- import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
1107
- import path5 from "path";
1423
+ import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync5, openSync, closeSync, statSync } from "fs";
1424
+ import path6 from "path";
1108
1425
  import { fileURLToPath } from "url";
1109
1426
  function handleData(chunk) {
1110
1427
  _buffer += chunk.toString();
@@ -1140,9 +1457,9 @@ function isZombie(pid) {
1140
1457
  }
1141
1458
  }
1142
1459
  function cleanupStaleFiles() {
1143
- if (existsSync5(PID_PATH)) {
1460
+ if (existsSync6(PID_PATH)) {
1144
1461
  try {
1145
- const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
1462
+ const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
1146
1463
  if (pid > 0) {
1147
1464
  try {
1148
1465
  process.kill(pid, 0);
@@ -1167,11 +1484,11 @@ function cleanupStaleFiles() {
1167
1484
  }
1168
1485
  }
1169
1486
  function findPackageRoot() {
1170
- let dir = path5.dirname(fileURLToPath(import.meta.url));
1171
- const { root } = path5.parse(dir);
1487
+ let dir = path6.dirname(fileURLToPath(import.meta.url));
1488
+ const { root } = path6.parse(dir);
1172
1489
  while (dir !== root) {
1173
- if (existsSync5(path5.join(dir, "package.json"))) return dir;
1174
- dir = path5.dirname(dir);
1490
+ if (existsSync6(path6.join(dir, "package.json"))) return dir;
1491
+ dir = path6.dirname(dir);
1175
1492
  }
1176
1493
  return null;
1177
1494
  }
@@ -1189,8 +1506,8 @@ function spawnDaemon() {
1189
1506
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
1190
1507
  return;
1191
1508
  }
1192
- const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1193
- if (!existsSync5(daemonPath)) {
1509
+ const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1510
+ if (!existsSync6(daemonPath)) {
1194
1511
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
1195
1512
  `);
1196
1513
  return;
@@ -1199,7 +1516,7 @@ function spawnDaemon() {
1199
1516
  const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
1200
1517
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
1201
1518
  `);
1202
- const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
1519
+ const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
1203
1520
  let stderrFd = "ignore";
1204
1521
  try {
1205
1522
  stderrFd = openSync(logPath, "a");
@@ -1364,9 +1681,9 @@ function killAndRespawnDaemon() {
1364
1681
  }
1365
1682
  try {
1366
1683
  process.stderr.write("[exed-client] Killing daemon for restart...\n");
1367
- if (existsSync5(PID_PATH)) {
1684
+ if (existsSync6(PID_PATH)) {
1368
1685
  try {
1369
- const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
1686
+ const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
1370
1687
  if (pid > 0) {
1371
1688
  try {
1372
1689
  process.kill(pid, "SIGKILL");
@@ -1512,9 +1829,9 @@ var init_exe_daemon_client = __esm({
1512
1829
  "use strict";
1513
1830
  init_config();
1514
1831
  init_daemon_auth();
1515
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path5.join(EXE_AI_DIR, "exed.sock");
1516
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path5.join(EXE_AI_DIR, "exed.pid");
1517
- SPAWN_LOCK_PATH = path5.join(EXE_AI_DIR, "exed-spawn.lock");
1832
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
1833
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
1834
+ SPAWN_LOCK_PATH = path6.join(EXE_AI_DIR, "exed-spawn.lock");
1518
1835
  SPAWN_LOCK_STALE_MS = 3e4;
1519
1836
  CONNECT_TIMEOUT_MS = 15e3;
1520
1837
  REQUEST_TIMEOUT_MS = 3e4;
@@ -1782,7 +2099,7 @@ __export(database_exports, {
1782
2099
  isInitialized: () => isInitialized,
1783
2100
  setExternalClient: () => setExternalClient
1784
2101
  });
1785
- 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";
2102
+ 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";
1786
2103
  import { createClient } from "@libsql/client";
1787
2104
  import { homedir } from "os";
1788
2105
  import { join } from "path";
@@ -1835,11 +2152,11 @@ function releaseDbLock() {
1835
2152
  }
1836
2153
  async function initDatabase(config) {
1837
2154
  acquireDbLock();
1838
- if (existsSync6(config.dbPath)) {
2155
+ if (existsSync7(config.dbPath)) {
1839
2156
  const dbStat = statSync2(config.dbPath);
1840
2157
  if (dbStat.size === 0) {
1841
2158
  const walPath = config.dbPath + "-wal";
1842
- if (existsSync6(walPath) && statSync2(walPath).size > 0) {
2159
+ if (existsSync7(walPath) && statSync2(walPath).size > 0) {
1843
2160
  const backupPath = config.dbPath + ".zeroed-" + Date.now();
1844
2161
  copyFileSync(config.dbPath, backupPath);
1845
2162
  unlinkSync3(config.dbPath);
@@ -3358,6 +3675,22 @@ async function ensureSchema() {
3358
3675
  } catch (e) {
3359
3676
  logCatchDebug("migration", e);
3360
3677
  }
3678
+ try {
3679
+ await client.execute({
3680
+ sql: `ALTER TABLE memories ADD COLUMN visibility TEXT DEFAULT 'private'`,
3681
+ args: []
3682
+ });
3683
+ } catch (e) {
3684
+ logCatchDebug("migration", e);
3685
+ }
3686
+ try {
3687
+ await client.execute({
3688
+ sql: `ALTER TABLE memories ADD COLUMN strength REAL DEFAULT 1.0`,
3689
+ args: []
3690
+ });
3691
+ } catch (e) {
3692
+ logCatchDebug("migration", e);
3693
+ }
3361
3694
  }
3362
3695
  async function disposeDatabase() {
3363
3696
  if (_walCheckpointTimer) {
@@ -3410,15 +3743,15 @@ var init_database = __esm({
3410
3743
 
3411
3744
  // src/lib/keychain.ts
3412
3745
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
3413
- import { existsSync as existsSync7, statSync as statSync3 } from "fs";
3746
+ import { existsSync as existsSync8, statSync as statSync3 } from "fs";
3414
3747
  import { execSync as execSync3 } from "child_process";
3415
- import path6 from "path";
3748
+ import path7 from "path";
3416
3749
  import os5 from "os";
3417
3750
  function getKeyDir() {
3418
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path6.join(os5.homedir(), ".exe-os");
3751
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path7.join(os5.homedir(), ".exe-os");
3419
3752
  }
3420
3753
  function getKeyPath() {
3421
- return path6.join(getKeyDir(), "master.key");
3754
+ return path7.join(getKeyDir(), "master.key");
3422
3755
  }
3423
3756
  function nativeKeychainAllowed() {
3424
3757
  return process.env.EXE_OS_DISABLE_NATIVE_KEYCHAIN !== "1";
@@ -3449,7 +3782,7 @@ function isRootOnlyTrustedServerKeyFile(keyPath) {
3449
3782
  if (!st.isFile() || (st.mode & 63) !== 0) return false;
3450
3783
  if (uid === 0) return true;
3451
3784
  const exeOsDir = process.env.EXE_OS_DIR;
3452
- return Boolean(exeOsDir && path6.resolve(keyPath).startsWith(path6.resolve(exeOsDir) + path6.sep));
3785
+ return Boolean(exeOsDir && path7.resolve(keyPath).startsWith(path7.resolve(exeOsDir) + path7.sep));
3453
3786
  } catch {
3454
3787
  return false;
3455
3788
  }
@@ -3646,7 +3979,7 @@ async function getMasterKey() {
3646
3979
  }
3647
3980
  }
3648
3981
  const keyPath = getKeyPath();
3649
- if (!existsSync7(keyPath)) {
3982
+ if (!existsSync8(keyPath)) {
3650
3983
  process.stderr.write(
3651
3984
  `[keychain] Key not found at ${keyPath} (HOME=${os5.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
3652
3985
  `
@@ -3980,14 +4313,14 @@ __export(shard_manager_exports, {
3980
4313
  listShards: () => listShards,
3981
4314
  shardExists: () => shardExists
3982
4315
  });
3983
- import path7 from "path";
3984
- import { existsSync as existsSync8, mkdirSync as mkdirSync3, readdirSync, renameSync as renameSync3, statSync as statSync4 } from "fs";
4316
+ import path8 from "path";
4317
+ import { existsSync as existsSync9, mkdirSync as mkdirSync3, readdirSync, renameSync as renameSync3, statSync as statSync4 } from "fs";
3985
4318
  import { createClient as createClient2 } from "@libsql/client";
3986
4319
  function initShardManager(encryptionKey) {
3987
4320
  _encryptionKey = encryptionKey;
3988
4321
  _keyValidated = false;
3989
4322
  _keyValidationPromise = null;
3990
- if (!existsSync8(SHARDS_DIR)) {
4323
+ if (!existsSync9(SHARDS_DIR)) {
3991
4324
  mkdirSync3(SHARDS_DIR, { recursive: true });
3992
4325
  }
3993
4326
  const existingShards = readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db"));
@@ -4008,7 +4341,7 @@ async function validateEncryptionKey() {
4008
4341
  return true;
4009
4342
  }
4010
4343
  for (const shardFile of existingShards.slice(0, 3)) {
4011
- const dbPath = path7.join(SHARDS_DIR, shardFile);
4344
+ const dbPath = path8.join(SHARDS_DIR, shardFile);
4012
4345
  const testClient = createClient2({ url: `file:${dbPath}`, encryptionKey: _encryptionKey });
4013
4346
  try {
4014
4347
  await testClient.execute("SELECT COUNT(*) FROM sqlite_schema");
@@ -4051,7 +4384,7 @@ function getShardClient(projectName) {
4051
4384
  while (_shards.size >= MAX_OPEN_SHARDS) {
4052
4385
  evictLRU();
4053
4386
  }
4054
- const dbPath = path7.join(SHARDS_DIR, `${safeName}.db`);
4387
+ const dbPath = path8.join(SHARDS_DIR, `${safeName}.db`);
4055
4388
  const client = createClient2({
4056
4389
  url: `file:${dbPath}`,
4057
4390
  encryptionKey: _encryptionKey
@@ -4062,13 +4395,13 @@ function getShardClient(projectName) {
4062
4395
  }
4063
4396
  function shardExists(projectName) {
4064
4397
  const safeName = safeShardName(projectName);
4065
- return existsSync8(path7.join(SHARDS_DIR, `${safeName}.db`));
4398
+ return existsSync9(path8.join(SHARDS_DIR, `${safeName}.db`));
4066
4399
  }
4067
4400
  function safeShardName(projectName) {
4068
4401
  return projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
4069
4402
  }
4070
4403
  function listShards() {
4071
- if (!existsSync8(SHARDS_DIR)) return [];
4404
+ if (!existsSync9(SHARDS_DIR)) return [];
4072
4405
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
4073
4406
  }
4074
4407
  async function auditShardHealth(options = {}) {
@@ -4080,7 +4413,7 @@ async function auditShardHealth(options = {}) {
4080
4413
  const names = listShards();
4081
4414
  const shards = [];
4082
4415
  for (const name of names) {
4083
- const dbPath = path7.join(SHARDS_DIR, `${name}.db`);
4416
+ const dbPath = path8.join(SHARDS_DIR, `${name}.db`);
4084
4417
  const stat = statSync4(dbPath);
4085
4418
  const item = {
4086
4419
  name,
@@ -4115,7 +4448,7 @@ async function auditShardHealth(options = {}) {
4115
4448
  _shards.delete(name);
4116
4449
  _shardLastAccess.delete(name);
4117
4450
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
4118
- const archivedPath = path7.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
4451
+ const archivedPath = path8.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
4119
4452
  renameSync3(dbPath, archivedPath);
4120
4453
  item.archivedPath = archivedPath;
4121
4454
  }
@@ -4343,11 +4676,11 @@ async function getReadyShardClient(projectName) {
4343
4676
  client.close();
4344
4677
  _shards.delete(safeName);
4345
4678
  _shardLastAccess.delete(safeName);
4346
- const dbPath = path7.join(SHARDS_DIR, `${safeName}.db`);
4347
- if (existsSync8(dbPath)) {
4679
+ const dbPath = path8.join(SHARDS_DIR, `${safeName}.db`);
4680
+ if (existsSync9(dbPath)) {
4348
4681
  const stat = statSync4(dbPath);
4349
4682
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
4350
- const archivedPath = path7.join(SHARDS_DIR, `${safeName}.db.broken-${stamp}`);
4683
+ const archivedPath = path8.join(SHARDS_DIR, `${safeName}.db.broken-${stamp}`);
4351
4684
  renameSync3(dbPath, archivedPath);
4352
4685
  process.stderr.write(
4353
4686
  `[shard-manager] Archived unreadable shard ${safeName}: ${archivedPath} (${stat.size} bytes, mtime ${stat.mtime.toISOString()})
@@ -4415,7 +4748,7 @@ var init_shard_manager = __esm({
4415
4748
  "src/lib/shard-manager.ts"() {
4416
4749
  "use strict";
4417
4750
  init_config();
4418
- SHARDS_DIR = path7.join(EXE_AI_DIR, "shards");
4751
+ SHARDS_DIR = path8.join(EXE_AI_DIR, "shards");
4419
4752
  SHARD_IDLE_MS = 5 * 60 * 1e3;
4420
4753
  MAX_OPEN_SHARDS = 10;
4421
4754
  EVICTION_INTERVAL_MS = 60 * 1e3;
@@ -4481,11 +4814,17 @@ var init_platform_procedures = __esm({
4481
4814
  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."
4482
4815
  },
4483
4816
  {
4484
- title: "Customer orchestration maturity \u2014 recommend, never trap",
4817
+ title: "Orchestration phase guidance \u2014 recommend, never trap",
4485
4818
  domain: "workflow",
4486
4819
  priority: "p1",
4487
4820
  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."
4488
4821
  },
4822
+ {
4823
+ title: "Routing slot vs display title \u2014 internal 'coo' is plumbing, not your name",
4824
+ domain: "identity",
4825
+ priority: "p0",
4826
+ 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."
4827
+ },
4489
4828
  {
4490
4829
  title: "Single dispatch path \u2014 create_task only",
4491
4830
  domain: "workflow",
@@ -4519,6 +4858,12 @@ var init_platform_procedures = __esm({
4519
4858
  priority: "p0",
4520
4859
  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."
4521
4860
  },
4861
+ {
4862
+ title: "Destructive operations \u2014 mandatory reviewer gate",
4863
+ domain: "security",
4864
+ priority: "p0",
4865
+ 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."
4866
+ },
4522
4867
  {
4523
4868
  title: "Customer patch triage \u2014 upstream bug vs customization",
4524
4869
  domain: "support",
@@ -4804,10 +5149,24 @@ function stableId(memoryId, type, content) {
4804
5149
  return createHash2("sha256").update(`${memoryId}:${type}:${content}`).digest("hex").slice(0, 32);
4805
5150
  }
4806
5151
  function cleanText(text) {
4807
- return text.replace(/```[\s\S]*?```/g, " ").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
5152
+ let cleaned = text.replace(
5153
+ /```(\w*)\n(.*?)(?:\n[\s\S]*?)```/g,
5154
+ (_m, lang, firstLine) => `[code${lang ? `:${lang}` : ""}] ${firstLine.trim()}`
5155
+ );
5156
+ cleaned = cleaned.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
5157
+ return cleaned;
4808
5158
  }
4809
- function splitSentences(text) {
4810
- return cleanText(text).split(/(?<=[.!?])\s+|\n+/).map((s) => s.trim()).filter((s) => s.length >= 24 && s.length <= MAX_SENTENCE_CHARS);
5159
+ function splitSegments(text) {
5160
+ const cleaned = cleanText(text);
5161
+ 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);
5162
+ if (segments.length === 0 && cleaned.length >= MIN_SEGMENT_CHARS) {
5163
+ const lines = cleaned.split(/\n+/).map((l) => l.trim()).filter((l) => l.length >= MIN_SEGMENT_CHARS && l.length <= MAX_SEGMENT_CHARS);
5164
+ if (lines.length > 0) return lines;
5165
+ if (cleaned.length >= MIN_SEGMENT_CHARS) {
5166
+ return [cleaned.slice(0, MAX_SEGMENT_CHARS)];
5167
+ }
5168
+ }
5169
+ return segments;
4811
5170
  }
4812
5171
  function inferCardType(sentence, toolName) {
4813
5172
  const lower = sentence.toLowerCase();
@@ -4839,12 +5198,12 @@ function predicateFor(type) {
4839
5198
  }
4840
5199
  }
4841
5200
  function extractMemoryCards(row) {
4842
- const sentences = splitSentences(row.raw_text);
5201
+ const segments = splitSegments(row.raw_text);
4843
5202
  const cards = [];
4844
- for (const sentence of sentences) {
5203
+ for (const sentence of segments) {
4845
5204
  const type = inferCardType(sentence, row.tool_name);
4846
5205
  const subject = extractSubject(sentence, row.agent_id);
4847
- const content = sentence.length > MAX_SENTENCE_CHARS ? `${sentence.slice(0, MAX_SENTENCE_CHARS - 1)}\u2026` : sentence;
5206
+ const content = sentence.length > MAX_SEGMENT_CHARS ? `${sentence.slice(0, MAX_SEGMENT_CHARS - 1)}\u2026` : sentence;
4848
5207
  cards.push({
4849
5208
  id: stableId(row.id, type, content),
4850
5209
  memory_id: row.id,
@@ -4940,13 +5299,14 @@ Source memory: ${String(row.source_ref ?? row.memory_id)}`,
4940
5299
  last_accessed: String(row.timestamp)
4941
5300
  }));
4942
5301
  }
4943
- var MAX_CARDS_PER_MEMORY, MAX_SENTENCE_CHARS;
5302
+ var MAX_CARDS_PER_MEMORY, MAX_SEGMENT_CHARS, MIN_SEGMENT_CHARS;
4944
5303
  var init_memory_cards = __esm({
4945
5304
  "src/lib/memory-cards.ts"() {
4946
5305
  "use strict";
4947
5306
  init_database();
4948
- MAX_CARDS_PER_MEMORY = 6;
4949
- MAX_SENTENCE_CHARS = 360;
5307
+ MAX_CARDS_PER_MEMORY = 8;
5308
+ MAX_SEGMENT_CHARS = 500;
5309
+ MIN_SEGMENT_CHARS = 20;
4950
5310
  }
4951
5311
  });
4952
5312
 
@@ -5954,12 +6314,12 @@ var init_fast_db_init = __esm({
5954
6314
  });
5955
6315
 
5956
6316
  // src/lib/session-registry.ts
5957
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4, existsSync as existsSync9 } from "fs";
5958
- import path8 from "path";
6317
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, existsSync as existsSync10 } from "fs";
6318
+ import path9 from "path";
5959
6319
  import os6 from "os";
5960
6320
  function registerSession(entry) {
5961
- const dir = path8.dirname(REGISTRY_PATH);
5962
- if (!existsSync9(dir)) {
6321
+ const dir = path9.dirname(REGISTRY_PATH);
6322
+ if (!existsSync10(dir)) {
5963
6323
  mkdirSync4(dir, { recursive: true });
5964
6324
  }
5965
6325
  const sessions = listSessions();
@@ -5969,11 +6329,11 @@ function registerSession(entry) {
5969
6329
  } else {
5970
6330
  sessions.push(entry);
5971
6331
  }
5972
- writeFileSync3(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
6332
+ writeFileSync4(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
5973
6333
  }
5974
6334
  function listSessions() {
5975
6335
  try {
5976
- const raw = readFileSync5(REGISTRY_PATH, "utf8");
6336
+ const raw = readFileSync6(REGISTRY_PATH, "utf8");
5977
6337
  return JSON.parse(raw);
5978
6338
  } catch {
5979
6339
  return [];
@@ -5983,7 +6343,7 @@ var REGISTRY_PATH;
5983
6343
  var init_session_registry = __esm({
5984
6344
  "src/lib/session-registry.ts"() {
5985
6345
  "use strict";
5986
- REGISTRY_PATH = path8.join(os6.homedir(), ".exe-os", "session-registry.json");
6346
+ REGISTRY_PATH = path9.join(os6.homedir(), ".exe-os", "session-registry.json");
5987
6347
  }
5988
6348
  });
5989
6349
 
@@ -6257,68 +6617,6 @@ var init_provider_table = __esm({
6257
6617
  }
6258
6618
  });
6259
6619
 
6260
- // src/lib/runtime-table.ts
6261
- var RUNTIME_TABLE, DEFAULT_RUNTIME;
6262
- var init_runtime_table = __esm({
6263
- "src/lib/runtime-table.ts"() {
6264
- "use strict";
6265
- RUNTIME_TABLE = {
6266
- codex: {
6267
- binary: "codex",
6268
- launchMode: "interactive",
6269
- autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
6270
- inlineFlag: "--no-alt-screen",
6271
- apiKeyEnv: "OPENAI_API_KEY",
6272
- defaultModel: "gpt-5.5"
6273
- },
6274
- opencode: {
6275
- binary: "opencode",
6276
- launchMode: "exec",
6277
- autoApproveFlag: "--dangerously-skip-permissions",
6278
- inlineFlag: "",
6279
- apiKeyEnv: "ANTHROPIC_API_KEY",
6280
- defaultModel: "anthropic/claude-sonnet-4-6"
6281
- }
6282
- };
6283
- DEFAULT_RUNTIME = "claude";
6284
- }
6285
- });
6286
-
6287
- // src/lib/agent-config.ts
6288
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync10 } from "fs";
6289
- import path9 from "path";
6290
- function loadAgentConfig() {
6291
- if (!existsSync10(AGENT_CONFIG_PATH)) return {};
6292
- try {
6293
- return JSON.parse(readFileSync6(AGENT_CONFIG_PATH, "utf-8"));
6294
- } catch {
6295
- return {};
6296
- }
6297
- }
6298
- function getAgentRuntime(agentId) {
6299
- const config = loadAgentConfig();
6300
- const entry = config[agentId];
6301
- if (entry) return entry;
6302
- const orgDefault = config["default"];
6303
- if (orgDefault) return orgDefault;
6304
- return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
6305
- }
6306
- var AGENT_CONFIG_PATH, DEFAULT_MODELS;
6307
- var init_agent_config = __esm({
6308
- "src/lib/agent-config.ts"() {
6309
- "use strict";
6310
- init_config();
6311
- init_runtime_table();
6312
- init_secure_files();
6313
- AGENT_CONFIG_PATH = path9.join(EXE_AI_DIR, "agent-config.json");
6314
- DEFAULT_MODELS = {
6315
- claude: "claude-opus-4.6",
6316
- codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
6317
- opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
6318
- };
6319
- }
6320
- });
6321
-
6322
6620
  // src/lib/intercom-queue.ts
6323
6621
  var intercom_queue_exports = {};
6324
6622
  __export(intercom_queue_exports, {
@@ -6813,7 +7111,7 @@ async function assertVpsLicense(opts) {
6813
7111
  }
6814
7112
  if (!transientFailure) {
6815
7113
  throw new Error(
6816
- "License validation failed: unknown backend state. Restore network connectivity to https://askexe.com/cloud and retry."
7114
+ "License validation failed: unknown backend state. Restore network connectivity to https://cloud.askexe.com and retry."
6817
7115
  );
6818
7116
  }
6819
7117
  const fresh = await getCachedLicense();
@@ -6850,7 +7148,7 @@ async function assertVpsLicense(opts) {
6850
7148
  } catch {
6851
7149
  }
6852
7150
  throw new Error(
6853
- `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.`
7151
+ `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.`
6854
7152
  );
6855
7153
  }
6856
7154
  function startLicenseRevalidation(intervalMs = 36e5) {
@@ -6882,7 +7180,7 @@ var init_license = __esm({
6882
7180
  LICENSE_PATH = path11.join(EXE_AI_DIR, "license.key");
6883
7181
  CACHE_PATH = path11.join(EXE_AI_DIR, "license-cache.json");
6884
7182
  DEVICE_ID_PATH = path11.join(EXE_AI_DIR, "device-id");
6885
- API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com/cloud";
7183
+ API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://cloud.askexe.com";
6886
7184
  RETRY_DELAY_MS = 500;
6887
7185
  LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
6888
7186
  MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
@@ -7284,6 +7582,19 @@ async function resolveTask(client, identifier, scopeSession) {
7284
7582
  args: [identifier, ...scope.args]
7285
7583
  });
7286
7584
  if (result.rows.length === 1) return result.rows[0];
7585
+ if (/^[a-f0-9]{7,12}$/i.test(identifier)) {
7586
+ result = await client.execute({
7587
+ sql: `SELECT * FROM tasks WHERE id LIKE ?`,
7588
+ args: [`${identifier}%`]
7589
+ });
7590
+ if (result.rows.length === 1) return result.rows[0];
7591
+ if (result.rows.length > 1) {
7592
+ const matches = result.rows.map((r) => `${String(r.id)} "${String(r.title)}" (${String(r.status)})`).join(", ");
7593
+ throw new Error(
7594
+ `Multiple tasks match short-ID "${identifier}": ${matches}. Use a longer prefix to disambiguate.`
7595
+ );
7596
+ }
7597
+ }
7287
7598
  result = await client.execute({
7288
7599
  sql: `SELECT * FROM tasks WHERE task_file LIKE ?${scope.sql}`,
7289
7600
  args: [`%${identifier}%`, ...scope.args]
@@ -7830,12 +8141,13 @@ async function cascadeUnblock(taskId, baseDir, now) {
7830
8141
  WHERE blocked_by = ? AND status = 'blocked'`,
7831
8142
  args: [now, taskId]
7832
8143
  });
7833
- if (baseDir && unblocked.rowsAffected > 0) {
7834
- const ubScope = sessionScopeFilter();
7835
- const unblockedRows = await client.execute({
7836
- sql: `SELECT task_file FROM tasks WHERE blocked_by IS NULL AND updated_at = ?${ubScope.sql}`,
7837
- args: [now, ...ubScope.args]
7838
- });
8144
+ if (unblocked.rowsAffected === 0) return;
8145
+ const ubScope = sessionScopeFilter();
8146
+ const unblockedRows = await client.execute({
8147
+ sql: `SELECT id, title, assigned_to, priority, task_file FROM tasks WHERE blocked_by IS NULL AND updated_at = ?${ubScope.sql}`,
8148
+ args: [now, ...ubScope.args]
8149
+ });
8150
+ if (baseDir) {
7839
8151
  for (const ur of unblockedRows.rows) {
7840
8152
  try {
7841
8153
  const ubFile = path16.join(baseDir, String(ur.task_file));
@@ -7847,6 +8159,19 @@ async function cascadeUnblock(taskId, baseDir, now) {
7847
8159
  }
7848
8160
  }
7849
8161
  }
8162
+ if (unblockedRows.rows.length > 0 && !process.env.VITEST) {
8163
+ try {
8164
+ const { queueIntercom: queueIntercom2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
8165
+ const dispatched = /* @__PURE__ */ new Set();
8166
+ for (const ur of unblockedRows.rows) {
8167
+ const assignee = String(ur.assigned_to);
8168
+ if (dispatched.has(assignee)) continue;
8169
+ dispatched.add(assignee);
8170
+ queueIntercom2(`${assignee}`, `unblocked: "${String(ur.title)}" is now ready`);
8171
+ }
8172
+ } catch {
8173
+ }
8174
+ }
7850
8175
  }
7851
8176
  async function findNextTask(assignedTo) {
7852
8177
  const client = getClient();
@@ -8056,6 +8381,15 @@ var init_embedder = __esm({
8056
8381
  // src/lib/behaviors.ts
8057
8382
  import crypto4 from "crypto";
8058
8383
  async function storeBehavior(opts) {
8384
+ try {
8385
+ const { loadEmployeesSync: loadEmployeesSync2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
8386
+ const roster = loadEmployeesSync2();
8387
+ if (roster.length > 0 && !roster.some((e) => e.name === opts.agentId)) {
8388
+ throw new Error(`Agent "${opts.agentId}" not found in roster. Cannot store behavior for unregistered agent.`);
8389
+ }
8390
+ } catch (e) {
8391
+ if (e instanceof Error && e.message.includes("not found in roster")) throw e;
8392
+ }
8059
8393
  const client = getClient();
8060
8394
  const id = crypto4.randomUUID();
8061
8395
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -8509,6 +8843,12 @@ async function updateTask(input) {
8509
8843
  }
8510
8844
  }
8511
8845
  }
8846
+ if (input.status === "cancelled") {
8847
+ try {
8848
+ await cascadeUnblock(taskId, input.baseDir, now);
8849
+ } catch {
8850
+ }
8851
+ }
8512
8852
  if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
8513
8853
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
8514
8854
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
@@ -9040,11 +9380,12 @@ function getDispatchedBy(sessionKey) {
9040
9380
  }
9041
9381
  }
9042
9382
  function resolveExeSession() {
9383
+ if (process.env.EXE_SESSION_NAME) {
9384
+ const fromEnv = extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
9385
+ if (fromEnv) return fromEnv;
9386
+ }
9043
9387
  const mySession = getMySession();
9044
9388
  if (!mySession) {
9045
- if (process.env.EXE_SESSION_NAME) {
9046
- return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
9047
- }
9048
9389
  return null;
9049
9390
  }
9050
9391
  const fromSessionName = extractRootExe(mySession);
@@ -9059,6 +9400,10 @@ function resolveExeSession() {
9059
9400
  `[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
9060
9401
  `
9061
9402
  );
9403
+ try {
9404
+ registerParentExe(key, fromSessionName);
9405
+ } catch {
9406
+ }
9062
9407
  candidate = fromSessionName;
9063
9408
  } else {
9064
9409
  candidate = fromCache;