@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
@@ -395,11 +395,168 @@ var init_config = __esm({
395
395
  }
396
396
  });
397
397
 
398
+ // src/lib/runtime-table.ts
399
+ var RUNTIME_TABLE, DEFAULT_RUNTIME;
400
+ var init_runtime_table = __esm({
401
+ "src/lib/runtime-table.ts"() {
402
+ "use strict";
403
+ RUNTIME_TABLE = {
404
+ codex: {
405
+ binary: "codex",
406
+ launchMode: "interactive",
407
+ autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
408
+ inlineFlag: "--no-alt-screen",
409
+ apiKeyEnv: "OPENAI_API_KEY",
410
+ defaultModel: "gpt-5.5"
411
+ },
412
+ opencode: {
413
+ binary: "opencode",
414
+ launchMode: "exec",
415
+ autoApproveFlag: "--dangerously-skip-permissions",
416
+ inlineFlag: "",
417
+ apiKeyEnv: "ANTHROPIC_API_KEY",
418
+ defaultModel: "anthropic/claude-sonnet-4-6"
419
+ }
420
+ };
421
+ DEFAULT_RUNTIME = "claude";
422
+ }
423
+ });
424
+
425
+ // src/lib/agent-config.ts
426
+ var agent_config_exports = {};
427
+ __export(agent_config_exports, {
428
+ AGENT_CONFIG_PATH: () => AGENT_CONFIG_PATH,
429
+ DEFAULT_MODELS: () => DEFAULT_MODELS,
430
+ KNOWN_RUNTIMES: () => KNOWN_RUNTIMES,
431
+ RUNTIME_LABELS: () => RUNTIME_LABELS,
432
+ clearAgentRuntime: () => clearAgentRuntime,
433
+ getAgentRuntime: () => getAgentRuntime,
434
+ loadAgentConfig: () => loadAgentConfig,
435
+ saveAgentConfig: () => saveAgentConfig,
436
+ setAgentMcps: () => setAgentMcps,
437
+ setAgentRuntime: () => setAgentRuntime
438
+ });
439
+ import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
440
+ import path2 from "path";
441
+ function loadAgentConfig() {
442
+ if (!existsSync3(AGENT_CONFIG_PATH)) return {};
443
+ try {
444
+ return JSON.parse(readFileSync2(AGENT_CONFIG_PATH, "utf-8"));
445
+ } catch {
446
+ return {};
447
+ }
448
+ }
449
+ function saveAgentConfig(config) {
450
+ const dir = path2.dirname(AGENT_CONFIG_PATH);
451
+ ensurePrivateDirSync(dir);
452
+ writeFileSync(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
453
+ enforcePrivateFileSync(AGENT_CONFIG_PATH);
454
+ }
455
+ function getAgentRuntime(agentId) {
456
+ const config = loadAgentConfig();
457
+ const entry = config[agentId];
458
+ if (entry) return entry;
459
+ const orgDefault = config["default"];
460
+ if (orgDefault) return orgDefault;
461
+ return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
462
+ }
463
+ function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
464
+ const knownModels = KNOWN_RUNTIMES[runtime];
465
+ if (!knownModels) {
466
+ return {
467
+ ok: false,
468
+ error: `Unknown runtime "${runtime}". Valid: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`
469
+ };
470
+ }
471
+ if (!knownModels.includes(model)) {
472
+ return {
473
+ ok: false,
474
+ error: `Unknown model "${model}" for runtime "${runtime}". Valid: ${knownModels.join(", ")}`
475
+ };
476
+ }
477
+ const config = loadAgentConfig();
478
+ const existing = config[agentId];
479
+ const entry = { runtime, model };
480
+ if (reasoning_effort) entry.reasoning_effort = reasoning_effort;
481
+ if (mcps !== void 0) {
482
+ entry.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
483
+ } else if (existing?.mcps) {
484
+ entry.mcps = existing.mcps;
485
+ }
486
+ config[agentId] = entry;
487
+ saveAgentConfig(config);
488
+ return { ok: true };
489
+ }
490
+ function setAgentMcps(agentId, mcps) {
491
+ const config = loadAgentConfig();
492
+ const existing = config[agentId] ?? getAgentRuntime(agentId);
493
+ existing.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
494
+ config[agentId] = existing;
495
+ saveAgentConfig(config);
496
+ return { ok: true };
497
+ }
498
+ function clearAgentRuntime(agentId) {
499
+ const config = loadAgentConfig();
500
+ delete config[agentId];
501
+ saveAgentConfig(config);
502
+ }
503
+ var AGENT_CONFIG_PATH, KNOWN_RUNTIMES, RUNTIME_LABELS, DEFAULT_MODELS;
504
+ var init_agent_config = __esm({
505
+ "src/lib/agent-config.ts"() {
506
+ "use strict";
507
+ init_config();
508
+ init_runtime_table();
509
+ init_secure_files();
510
+ AGENT_CONFIG_PATH = path2.join(EXE_AI_DIR, "agent-config.json");
511
+ KNOWN_RUNTIMES = {
512
+ claude: ["claude-opus-4.6", "claude-opus-4", "claude-sonnet-4.6", "claude-sonnet-4", "claude-haiku-4.5"],
513
+ codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
514
+ opencode: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4", "google/gemini-2.5-pro", "deepseek/deepseek-r3", "minimax/minimax-m2.5"]
515
+ };
516
+ RUNTIME_LABELS = {
517
+ claude: "Claude Code (Anthropic)",
518
+ codex: "Codex (OpenAI)",
519
+ opencode: "OpenCode (open source)"
520
+ };
521
+ DEFAULT_MODELS = {
522
+ claude: "claude-opus-4.6",
523
+ codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
524
+ opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
525
+ };
526
+ }
527
+ });
528
+
398
529
  // src/lib/employees.ts
530
+ var employees_exports = {};
531
+ __export(employees_exports, {
532
+ COORDINATOR_ROLE: () => COORDINATOR_ROLE,
533
+ DEFAULT_COORDINATOR_TEMPLATE_NAME: () => DEFAULT_COORDINATOR_TEMPLATE_NAME,
534
+ EMPLOYEES_PATH: () => EMPLOYEES_PATH,
535
+ addEmployee: () => addEmployee,
536
+ baseAgentName: () => baseAgentName,
537
+ canCoordinate: () => canCoordinate,
538
+ getCoordinatorEmployee: () => getCoordinatorEmployee,
539
+ getCoordinatorName: () => getCoordinatorName,
540
+ getEmployee: () => getEmployee,
541
+ getEmployeeByRole: () => getEmployeeByRole,
542
+ getEmployeeNamesByRole: () => getEmployeeNamesByRole,
543
+ hasRole: () => hasRole,
544
+ hireEmployee: () => hireEmployee,
545
+ isCoordinatorName: () => isCoordinatorName,
546
+ isCoordinatorRole: () => isCoordinatorRole,
547
+ isMultiInstance: () => isMultiInstance,
548
+ loadEmployees: () => loadEmployees,
549
+ loadEmployeesSync: () => loadEmployeesSync,
550
+ normalizeRole: () => normalizeRole,
551
+ normalizeRosterCase: () => normalizeRosterCase,
552
+ registerBinSymlinks: () => registerBinSymlinks,
553
+ saveEmployees: () => saveEmployees,
554
+ validateEmployeeName: () => validateEmployeeName
555
+ });
399
556
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
400
- import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
557
+ import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
401
558
  import { execSync } from "child_process";
402
- import path2 from "path";
559
+ import path3 from "path";
403
560
  import os2 from "os";
404
561
  function normalizeRole(role) {
405
562
  return (role ?? "").trim().toLowerCase();
@@ -417,8 +574,26 @@ function isCoordinatorName(agentName2, employees = loadEmployeesSync()) {
417
574
  if (!agentName2) return false;
418
575
  return agentName2.toLowerCase() === getCoordinatorName(employees).toLowerCase();
419
576
  }
577
+ function canCoordinate(agentName2, agentRole, employees = loadEmployeesSync()) {
578
+ return agentName2 === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName2, employees);
579
+ }
580
+ function validateEmployeeName(name) {
581
+ if (!name) {
582
+ return { valid: false, error: "Name is required" };
583
+ }
584
+ if (name.length > 32) {
585
+ return { valid: false, error: "Name must be 32 characters or fewer" };
586
+ }
587
+ if (!/^[a-z][a-z0-9]*$/.test(name)) {
588
+ return {
589
+ valid: false,
590
+ error: "Name must start with a letter and contain only lowercase alphanumeric characters"
591
+ };
592
+ }
593
+ return { valid: true };
594
+ }
420
595
  async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
421
- if (!existsSync3(employeesPath)) {
596
+ if (!existsSync4(employeesPath)) {
422
597
  return [];
423
598
  }
424
599
  const raw = await readFile2(employeesPath, "utf-8");
@@ -428,10 +603,14 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
428
603
  return [];
429
604
  }
430
605
  }
606
+ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
607
+ await mkdir2(path3.dirname(employeesPath), { recursive: true });
608
+ await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
609
+ }
431
610
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
432
- if (!existsSync3(employeesPath)) return [];
611
+ if (!existsSync4(employeesPath)) return [];
433
612
  try {
434
- return JSON.parse(readFileSync2(employeesPath, "utf-8"));
613
+ return JSON.parse(readFileSync3(employeesPath, "utf-8"));
435
614
  } catch {
436
615
  return [];
437
616
  }
@@ -439,6 +618,19 @@ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
439
618
  function getEmployee(employees, name) {
440
619
  return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
441
620
  }
621
+ function getEmployeeByRole(employees, role) {
622
+ const lower = role.toLowerCase();
623
+ return employees.find((e) => e.role.toLowerCase() === lower);
624
+ }
625
+ function getEmployeeNamesByRole(employees, role) {
626
+ const lower = role.toLowerCase();
627
+ return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
628
+ }
629
+ function hasRole(agentName2, role) {
630
+ const employees = loadEmployeesSync();
631
+ const emp = getEmployee(employees, agentName2);
632
+ return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
633
+ }
442
634
  function baseAgentName(name, employees) {
443
635
  const match = name.match(/^([a-zA-Z]+)\d+$/);
444
636
  if (!match) return name;
@@ -453,22 +645,147 @@ function isMultiInstance(agentName2, employees) {
453
645
  if (!emp) return false;
454
646
  return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
455
647
  }
456
- var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR;
648
+ function addEmployee(employees, employee) {
649
+ const { systemPrompt: _legacyPrompt, ...rest } = employee;
650
+ const normalized = { ...rest, name: employee.name.toLowerCase() };
651
+ if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
652
+ throw new Error(`Employee '${normalized.name}' already exists`);
653
+ }
654
+ return [...employees, normalized];
655
+ }
656
+ function appendToCoordinatorTeam(employee) {
657
+ const coordinator = getCoordinatorEmployee(loadEmployeesSync());
658
+ if (!coordinator) return;
659
+ const idPath = path3.join(IDENTITY_DIR, `${coordinator.name}.md`);
660
+ if (!existsSync4(idPath)) return;
661
+ const content = readFileSync3(idPath, "utf-8");
662
+ if (content.includes(`**${capitalize(employee.name)}`)) return;
663
+ const teamMatch = content.match(TEAM_SECTION_RE);
664
+ if (!teamMatch || teamMatch.index === void 0) return;
665
+ const afterTeam = content.slice(teamMatch.index + teamMatch[0].length);
666
+ const nextHeading = afterTeam.match(/\n## /);
667
+ const entry = `
668
+ **${capitalize(employee.name)} (${employee.role}):** Newly hired. Update this description as the role develops.
669
+ `;
670
+ let updated;
671
+ if (nextHeading && nextHeading.index !== void 0) {
672
+ const insertAt = teamMatch.index + teamMatch[0].length + nextHeading.index;
673
+ updated = content.slice(0, insertAt) + entry + content.slice(insertAt);
674
+ } else {
675
+ updated = content.trimEnd() + "\n" + entry;
676
+ }
677
+ writeFileSync2(idPath, updated, "utf-8");
678
+ }
679
+ function capitalize(s) {
680
+ return s.charAt(0).toUpperCase() + s.slice(1);
681
+ }
682
+ async function hireEmployee(employee) {
683
+ const employees = await loadEmployees();
684
+ const updated = addEmployee(employees, employee);
685
+ await saveEmployees(updated);
686
+ try {
687
+ appendToCoordinatorTeam(employee);
688
+ } catch {
689
+ }
690
+ try {
691
+ const { loadAgentConfig: loadAgentConfig2, saveAgentConfig: saveAgentConfig2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
692
+ const config = loadAgentConfig2();
693
+ const name = employee.name.toLowerCase();
694
+ if (!config[name] && config["default"]) {
695
+ config[name] = { ...config["default"] };
696
+ saveAgentConfig2(config);
697
+ }
698
+ } catch {
699
+ }
700
+ return updated;
701
+ }
702
+ async function normalizeRosterCase(rosterPath) {
703
+ const employees = await loadEmployees(rosterPath);
704
+ let changed = false;
705
+ for (const emp of employees) {
706
+ if (emp.name !== emp.name.toLowerCase()) {
707
+ const oldName = emp.name;
708
+ emp.name = emp.name.toLowerCase();
709
+ changed = true;
710
+ try {
711
+ const identityDir = path3.join(os2.homedir(), ".exe-os", "identity");
712
+ const oldPath = path3.join(identityDir, `${oldName}.md`);
713
+ const newPath = path3.join(identityDir, `${emp.name}.md`);
714
+ if (existsSync4(oldPath) && !existsSync4(newPath)) {
715
+ renameSync2(oldPath, newPath);
716
+ } else if (existsSync4(oldPath) && oldPath !== newPath) {
717
+ const content = readFileSync3(oldPath, "utf-8");
718
+ writeFileSync2(newPath, content, "utf-8");
719
+ if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
720
+ unlinkSync(oldPath);
721
+ }
722
+ }
723
+ } catch {
724
+ }
725
+ }
726
+ }
727
+ if (changed) {
728
+ await saveEmployees(employees, rosterPath);
729
+ }
730
+ return changed;
731
+ }
732
+ function findExeBin() {
733
+ try {
734
+ return execSync(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
735
+ } catch {
736
+ return null;
737
+ }
738
+ }
739
+ function registerBinSymlinks(name) {
740
+ const created = [];
741
+ const skipped = [];
742
+ const errors = [];
743
+ const exeBinPath = findExeBin();
744
+ if (!exeBinPath) {
745
+ errors.push("Could not find 'exe-os' in PATH");
746
+ return { created, skipped, errors };
747
+ }
748
+ const binDir = path3.dirname(exeBinPath);
749
+ let target;
750
+ try {
751
+ target = readlinkSync(exeBinPath);
752
+ } catch {
753
+ errors.push("Could not read 'exe' symlink");
754
+ return { created, skipped, errors };
755
+ }
756
+ for (const suffix of ["", "-opencode"]) {
757
+ const linkName = `${name}${suffix}`;
758
+ const linkPath = path3.join(binDir, linkName);
759
+ if (existsSync4(linkPath)) {
760
+ skipped.push(linkName);
761
+ continue;
762
+ }
763
+ try {
764
+ symlinkSync(target, linkPath);
765
+ created.push(linkName);
766
+ } catch (err) {
767
+ errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
768
+ }
769
+ }
770
+ return { created, skipped, errors };
771
+ }
772
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR, TEAM_SECTION_RE;
457
773
  var init_employees = __esm({
458
774
  "src/lib/employees.ts"() {
459
775
  "use strict";
460
776
  init_config();
461
- EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
777
+ EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
462
778
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
463
779
  COORDINATOR_ROLE = "COO";
464
780
  MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
465
- IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
781
+ IDENTITY_DIR = path3.join(EXE_AI_DIR, "identity");
782
+ TEAM_SECTION_RE = /^## Team\b.*$/m;
466
783
  }
467
784
  });
468
785
 
469
786
  // src/lib/database-adapter.ts
470
787
  import os3 from "os";
471
- import path3 from "path";
788
+ import path4 from "path";
472
789
  import { createRequire } from "module";
473
790
  import { pathToFileURL } from "url";
474
791
  function quotedIdentifier(identifier) {
@@ -779,8 +1096,8 @@ async function loadPrismaClient() {
779
1096
  }
780
1097
  return new PrismaClient2();
781
1098
  }
782
- const exeDbRoot = process.env.EXE_DB_ROOT ?? path3.join(os3.homedir(), "exe-db");
783
- const requireFromExeDb = createRequire(path3.join(exeDbRoot, "package.json"));
1099
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path4.join(os3.homedir(), "exe-db");
1100
+ const requireFromExeDb = createRequire(path4.join(exeDbRoot, "package.json"));
784
1101
  const prismaEntry = requireFromExeDb.resolve("@prisma/client");
785
1102
  const module = await import(pathToFileURL(prismaEntry).href);
786
1103
  const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
@@ -1052,8 +1369,8 @@ var init_database_adapter = __esm({
1052
1369
 
1053
1370
  // src/lib/daemon-auth.ts
1054
1371
  import crypto from "crypto";
1055
- import path4 from "path";
1056
- import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
1372
+ import path5 from "path";
1373
+ import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
1057
1374
  function normalizeToken(token) {
1058
1375
  if (!token) return null;
1059
1376
  const trimmed = token.trim();
@@ -1061,8 +1378,8 @@ function normalizeToken(token) {
1061
1378
  }
1062
1379
  function readDaemonToken() {
1063
1380
  try {
1064
- if (!existsSync4(DAEMON_TOKEN_PATH)) return null;
1065
- return normalizeToken(readFileSync3(DAEMON_TOKEN_PATH, "utf8"));
1381
+ if (!existsSync5(DAEMON_TOKEN_PATH)) return null;
1382
+ return normalizeToken(readFileSync4(DAEMON_TOKEN_PATH, "utf8"));
1066
1383
  } catch {
1067
1384
  return null;
1068
1385
  }
@@ -1072,7 +1389,7 @@ function ensureDaemonToken(seed) {
1072
1389
  if (existing) return existing;
1073
1390
  const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
1074
1391
  ensurePrivateDirSync(EXE_AI_DIR);
1075
- writeFileSync2(DAEMON_TOKEN_PATH, `${token}
1392
+ writeFileSync3(DAEMON_TOKEN_PATH, `${token}
1076
1393
  `, "utf8");
1077
1394
  enforcePrivateFileSync(DAEMON_TOKEN_PATH);
1078
1395
  return token;
@@ -1083,7 +1400,7 @@ var init_daemon_auth = __esm({
1083
1400
  "use strict";
1084
1401
  init_config();
1085
1402
  init_secure_files();
1086
- DAEMON_TOKEN_PATH = path4.join(EXE_AI_DIR, "exed.token");
1403
+ DAEMON_TOKEN_PATH = path5.join(EXE_AI_DIR, "exed.token");
1087
1404
  }
1088
1405
  });
1089
1406
 
@@ -1092,8 +1409,8 @@ import net from "net";
1092
1409
  import os4 from "os";
1093
1410
  import { spawn, execSync as execSync2 } from "child_process";
1094
1411
  import { randomUUID } from "crypto";
1095
- import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
1096
- import path5 from "path";
1412
+ import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync5, openSync, closeSync, statSync } from "fs";
1413
+ import path6 from "path";
1097
1414
  import { fileURLToPath } from "url";
1098
1415
  function handleData(chunk) {
1099
1416
  _buffer += chunk.toString();
@@ -1129,9 +1446,9 @@ function isZombie(pid) {
1129
1446
  }
1130
1447
  }
1131
1448
  function cleanupStaleFiles() {
1132
- if (existsSync5(PID_PATH)) {
1449
+ if (existsSync6(PID_PATH)) {
1133
1450
  try {
1134
- const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
1451
+ const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
1135
1452
  if (pid > 0) {
1136
1453
  try {
1137
1454
  process.kill(pid, 0);
@@ -1156,11 +1473,11 @@ function cleanupStaleFiles() {
1156
1473
  }
1157
1474
  }
1158
1475
  function findPackageRoot() {
1159
- let dir = path5.dirname(fileURLToPath(import.meta.url));
1160
- const { root } = path5.parse(dir);
1476
+ let dir = path6.dirname(fileURLToPath(import.meta.url));
1477
+ const { root } = path6.parse(dir);
1161
1478
  while (dir !== root) {
1162
- if (existsSync5(path5.join(dir, "package.json"))) return dir;
1163
- dir = path5.dirname(dir);
1479
+ if (existsSync6(path6.join(dir, "package.json"))) return dir;
1480
+ dir = path6.dirname(dir);
1164
1481
  }
1165
1482
  return null;
1166
1483
  }
@@ -1178,8 +1495,8 @@ function spawnDaemon() {
1178
1495
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
1179
1496
  return;
1180
1497
  }
1181
- const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1182
- if (!existsSync5(daemonPath)) {
1498
+ const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1499
+ if (!existsSync6(daemonPath)) {
1183
1500
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
1184
1501
  `);
1185
1502
  return;
@@ -1188,7 +1505,7 @@ function spawnDaemon() {
1188
1505
  const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
1189
1506
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
1190
1507
  `);
1191
- const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
1508
+ const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
1192
1509
  let stderrFd = "ignore";
1193
1510
  try {
1194
1511
  stderrFd = openSync(logPath, "a");
@@ -1353,9 +1670,9 @@ function killAndRespawnDaemon() {
1353
1670
  }
1354
1671
  try {
1355
1672
  process.stderr.write("[exed-client] Killing daemon for restart...\n");
1356
- if (existsSync5(PID_PATH)) {
1673
+ if (existsSync6(PID_PATH)) {
1357
1674
  try {
1358
- const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
1675
+ const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
1359
1676
  if (pid > 0) {
1360
1677
  try {
1361
1678
  process.kill(pid, "SIGKILL");
@@ -1481,9 +1798,9 @@ var init_exe_daemon_client = __esm({
1481
1798
  "use strict";
1482
1799
  init_config();
1483
1800
  init_daemon_auth();
1484
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path5.join(EXE_AI_DIR, "exed.sock");
1485
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path5.join(EXE_AI_DIR, "exed.pid");
1486
- SPAWN_LOCK_PATH = path5.join(EXE_AI_DIR, "exed-spawn.lock");
1801
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
1802
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
1803
+ SPAWN_LOCK_PATH = path6.join(EXE_AI_DIR, "exed-spawn.lock");
1487
1804
  SPAWN_LOCK_STALE_MS = 3e4;
1488
1805
  CONNECT_TIMEOUT_MS = 15e3;
1489
1806
  REQUEST_TIMEOUT_MS = 3e4;
@@ -1716,7 +2033,7 @@ __export(database_exports, {
1716
2033
  isInitialized: () => isInitialized,
1717
2034
  setExternalClient: () => setExternalClient
1718
2035
  });
1719
- import { chmodSync as chmodSync2, existsSync as existsSync6, statSync as statSync2, copyFileSync, unlinkSync as unlinkSync3, openSync as openSync2, closeSync as closeSync2, mkdirSync as mkdirSync2 } from "fs";
2036
+ import { chmodSync as chmodSync2, existsSync as existsSync7, statSync as statSync2, copyFileSync, unlinkSync as unlinkSync3, openSync as openSync2, closeSync as closeSync2, mkdirSync as mkdirSync2 } from "fs";
1720
2037
  import { createClient } from "@libsql/client";
1721
2038
  import { homedir } from "os";
1722
2039
  import { join } from "path";
@@ -1769,11 +2086,11 @@ function releaseDbLock() {
1769
2086
  }
1770
2087
  async function initDatabase(config) {
1771
2088
  acquireDbLock();
1772
- if (existsSync6(config.dbPath)) {
2089
+ if (existsSync7(config.dbPath)) {
1773
2090
  const dbStat = statSync2(config.dbPath);
1774
2091
  if (dbStat.size === 0) {
1775
2092
  const walPath = config.dbPath + "-wal";
1776
- if (existsSync6(walPath) && statSync2(walPath).size > 0) {
2093
+ if (existsSync7(walPath) && statSync2(walPath).size > 0) {
1777
2094
  const backupPath = config.dbPath + ".zeroed-" + Date.now();
1778
2095
  copyFileSync(config.dbPath, backupPath);
1779
2096
  unlinkSync3(config.dbPath);
@@ -3292,6 +3609,22 @@ async function ensureSchema() {
3292
3609
  } catch (e) {
3293
3610
  logCatchDebug("migration", e);
3294
3611
  }
3612
+ try {
3613
+ await client.execute({
3614
+ sql: `ALTER TABLE memories ADD COLUMN visibility TEXT DEFAULT 'private'`,
3615
+ args: []
3616
+ });
3617
+ } catch (e) {
3618
+ logCatchDebug("migration", e);
3619
+ }
3620
+ try {
3621
+ await client.execute({
3622
+ sql: `ALTER TABLE memories ADD COLUMN strength REAL DEFAULT 1.0`,
3623
+ args: []
3624
+ });
3625
+ } catch (e) {
3626
+ logCatchDebug("migration", e);
3627
+ }
3295
3628
  }
3296
3629
  async function disposeDatabase() {
3297
3630
  if (_walCheckpointTimer) {
@@ -3344,15 +3677,15 @@ var init_database = __esm({
3344
3677
 
3345
3678
  // src/lib/keychain.ts
3346
3679
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
3347
- import { existsSync as existsSync7, statSync as statSync3 } from "fs";
3680
+ import { existsSync as existsSync8, statSync as statSync3 } from "fs";
3348
3681
  import { execSync as execSync3 } from "child_process";
3349
- import path6 from "path";
3682
+ import path7 from "path";
3350
3683
  import os5 from "os";
3351
3684
  function getKeyDir() {
3352
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path6.join(os5.homedir(), ".exe-os");
3685
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path7.join(os5.homedir(), ".exe-os");
3353
3686
  }
3354
3687
  function getKeyPath() {
3355
- return path6.join(getKeyDir(), "master.key");
3688
+ return path7.join(getKeyDir(), "master.key");
3356
3689
  }
3357
3690
  function nativeKeychainAllowed() {
3358
3691
  return process.env.EXE_OS_DISABLE_NATIVE_KEYCHAIN !== "1";
@@ -3383,7 +3716,7 @@ function isRootOnlyTrustedServerKeyFile(keyPath) {
3383
3716
  if (!st.isFile() || (st.mode & 63) !== 0) return false;
3384
3717
  if (uid === 0) return true;
3385
3718
  const exeOsDir = process.env.EXE_OS_DIR;
3386
- return Boolean(exeOsDir && path6.resolve(keyPath).startsWith(path6.resolve(exeOsDir) + path6.sep));
3719
+ return Boolean(exeOsDir && path7.resolve(keyPath).startsWith(path7.resolve(exeOsDir) + path7.sep));
3387
3720
  } catch {
3388
3721
  return false;
3389
3722
  }
@@ -3580,7 +3913,7 @@ async function getMasterKey() {
3580
3913
  }
3581
3914
  }
3582
3915
  const keyPath = getKeyPath();
3583
- if (!existsSync7(keyPath)) {
3916
+ if (!existsSync8(keyPath)) {
3584
3917
  process.stderr.write(
3585
3918
  `[keychain] Key not found at ${keyPath} (HOME=${os5.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
3586
3919
  `
@@ -3914,14 +4247,14 @@ __export(shard_manager_exports, {
3914
4247
  listShards: () => listShards,
3915
4248
  shardExists: () => shardExists
3916
4249
  });
3917
- import path7 from "path";
3918
- import { existsSync as existsSync8, mkdirSync as mkdirSync3, readdirSync, renameSync as renameSync3, statSync as statSync4 } from "fs";
4250
+ import path8 from "path";
4251
+ import { existsSync as existsSync9, mkdirSync as mkdirSync3, readdirSync, renameSync as renameSync3, statSync as statSync4 } from "fs";
3919
4252
  import { createClient as createClient2 } from "@libsql/client";
3920
4253
  function initShardManager(encryptionKey) {
3921
4254
  _encryptionKey = encryptionKey;
3922
4255
  _keyValidated = false;
3923
4256
  _keyValidationPromise = null;
3924
- if (!existsSync8(SHARDS_DIR)) {
4257
+ if (!existsSync9(SHARDS_DIR)) {
3925
4258
  mkdirSync3(SHARDS_DIR, { recursive: true });
3926
4259
  }
3927
4260
  const existingShards = readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db"));
@@ -3942,7 +4275,7 @@ async function validateEncryptionKey() {
3942
4275
  return true;
3943
4276
  }
3944
4277
  for (const shardFile of existingShards.slice(0, 3)) {
3945
- const dbPath = path7.join(SHARDS_DIR, shardFile);
4278
+ const dbPath = path8.join(SHARDS_DIR, shardFile);
3946
4279
  const testClient = createClient2({ url: `file:${dbPath}`, encryptionKey: _encryptionKey });
3947
4280
  try {
3948
4281
  await testClient.execute("SELECT COUNT(*) FROM sqlite_schema");
@@ -3985,7 +4318,7 @@ function getShardClient(projectName) {
3985
4318
  while (_shards.size >= MAX_OPEN_SHARDS) {
3986
4319
  evictLRU();
3987
4320
  }
3988
- const dbPath = path7.join(SHARDS_DIR, `${safeName}.db`);
4321
+ const dbPath = path8.join(SHARDS_DIR, `${safeName}.db`);
3989
4322
  const client = createClient2({
3990
4323
  url: `file:${dbPath}`,
3991
4324
  encryptionKey: _encryptionKey
@@ -3996,13 +4329,13 @@ function getShardClient(projectName) {
3996
4329
  }
3997
4330
  function shardExists(projectName) {
3998
4331
  const safeName = safeShardName(projectName);
3999
- return existsSync8(path7.join(SHARDS_DIR, `${safeName}.db`));
4332
+ return existsSync9(path8.join(SHARDS_DIR, `${safeName}.db`));
4000
4333
  }
4001
4334
  function safeShardName(projectName) {
4002
4335
  return projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
4003
4336
  }
4004
4337
  function listShards() {
4005
- if (!existsSync8(SHARDS_DIR)) return [];
4338
+ if (!existsSync9(SHARDS_DIR)) return [];
4006
4339
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
4007
4340
  }
4008
4341
  async function auditShardHealth(options = {}) {
@@ -4014,7 +4347,7 @@ async function auditShardHealth(options = {}) {
4014
4347
  const names = listShards();
4015
4348
  const shards = [];
4016
4349
  for (const name of names) {
4017
- const dbPath = path7.join(SHARDS_DIR, `${name}.db`);
4350
+ const dbPath = path8.join(SHARDS_DIR, `${name}.db`);
4018
4351
  const stat = statSync4(dbPath);
4019
4352
  const item = {
4020
4353
  name,
@@ -4049,7 +4382,7 @@ async function auditShardHealth(options = {}) {
4049
4382
  _shards.delete(name);
4050
4383
  _shardLastAccess.delete(name);
4051
4384
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
4052
- const archivedPath = path7.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
4385
+ const archivedPath = path8.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
4053
4386
  renameSync3(dbPath, archivedPath);
4054
4387
  item.archivedPath = archivedPath;
4055
4388
  }
@@ -4277,11 +4610,11 @@ async function getReadyShardClient(projectName) {
4277
4610
  client.close();
4278
4611
  _shards.delete(safeName);
4279
4612
  _shardLastAccess.delete(safeName);
4280
- const dbPath = path7.join(SHARDS_DIR, `${safeName}.db`);
4281
- if (existsSync8(dbPath)) {
4613
+ const dbPath = path8.join(SHARDS_DIR, `${safeName}.db`);
4614
+ if (existsSync9(dbPath)) {
4282
4615
  const stat = statSync4(dbPath);
4283
4616
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
4284
- const archivedPath = path7.join(SHARDS_DIR, `${safeName}.db.broken-${stamp}`);
4617
+ const archivedPath = path8.join(SHARDS_DIR, `${safeName}.db.broken-${stamp}`);
4285
4618
  renameSync3(dbPath, archivedPath);
4286
4619
  process.stderr.write(
4287
4620
  `[shard-manager] Archived unreadable shard ${safeName}: ${archivedPath} (${stat.size} bytes, mtime ${stat.mtime.toISOString()})
@@ -4349,7 +4682,7 @@ var init_shard_manager = __esm({
4349
4682
  "src/lib/shard-manager.ts"() {
4350
4683
  "use strict";
4351
4684
  init_config();
4352
- SHARDS_DIR = path7.join(EXE_AI_DIR, "shards");
4685
+ SHARDS_DIR = path8.join(EXE_AI_DIR, "shards");
4353
4686
  SHARD_IDLE_MS = 5 * 60 * 1e3;
4354
4687
  MAX_OPEN_SHARDS = 10;
4355
4688
  EVICTION_INTERVAL_MS = 60 * 1e3;
@@ -4415,11 +4748,17 @@ var init_platform_procedures = __esm({
4415
4748
  content: "Founder -> coordinator (the executive agent, internally routed as 'COO') -> CTO/CMO. CTO -> engineers. CMO -> content production. Never skip levels: the coordinator does not bypass managers for specialist work. Specialists report to their manager. If you need cross-team info, use ask_team_memory \u2014 don't read other agents' task folders. Each level owns dispatch downward and review upward."
4416
4749
  },
4417
4750
  {
4418
- title: "Customer orchestration maturity \u2014 recommend, never trap",
4751
+ title: "Orchestration phase guidance \u2014 recommend, never trap",
4419
4752
  domain: "workflow",
4420
4753
  priority: "p1",
4421
4754
  content: "New customers start best in Phase 1: founder \u2194 coordinator/Chief of Staff, building company context. Suggest Phase 2 executives when domain work repeats; suggest Phase 3 parallel execution only when review/permission gates are ready. This is guidance, not a blocker: users may jump phases anytime. Never overwrite their phase, role titles, identities, or custom org design."
4422
4755
  },
4756
+ {
4757
+ title: "Routing slot vs display title \u2014 internal 'coo' is plumbing, not your name",
4758
+ domain: "identity",
4759
+ priority: "p0",
4760
+ content: "These procedures reference 'COO' as a shorthand for the coordinator role. This is an INTERNAL routing slot used by exe-os code (chain-of-command checks, dispatch logic, session detection). It is NOT your display title. Your actual title comes from your identity file's `title:` field \u2014 that is what you use externally: introductions, sign-offs, team comms, and any user-facing text. If your identity says `title: AI Chief of Staff`, you are the AI Chief of Staff. The routing slot stays `role: coo` for code compatibility \u2014 never rename it, but also never introduce yourself as 'COO' unless your identity file explicitly says so. The founder chose your title; respect it."
4761
+ },
4423
4762
  {
4424
4763
  title: "Single dispatch path \u2014 create_task only",
4425
4764
  domain: "workflow",
@@ -4453,6 +4792,12 @@ var init_platform_procedures = __esm({
4453
4792
  priority: "p0",
4454
4793
  content: "NEVER: (1) Access the database directly \u2014 it's SQLCipher encrypted, always fails. Use MCP tools only. (2) Manually spawn tmux sessions \u2014 create_task handles it. (3) Run git checkout main \u2014 agents work in worktrees. (4) Modify another agent's in-progress task. (5) Push to remote \u2014 the COO reviews and pushes. (6) Skip update_task(done) \u2014 it's the ONLY way your work gets reviewed. (7) Run git init."
4455
4794
  },
4795
+ {
4796
+ title: "Destructive operations \u2014 mandatory reviewer gate",
4797
+ domain: "security",
4798
+ priority: "p0",
4799
+ content: "Before ANY destructive operation (delete, remove, overwrite, drop, reset, force-push, truncate), you MUST: (1) Have your full task spec accessible \u2014 if you cannot read it, STOP and report to your reviewer. Never improvise destructive actions. (2) Confirm with your reviewer (assigned_by or COO) before executing. (3) If the task spec explicitly authorizes the operation, proceed \u2014 but log it. Violation = immediate task failure. This applies to ALL agents regardless of role."
4800
+ },
4456
4801
  {
4457
4802
  title: "Customer patch triage \u2014 upstream bug vs customization",
4458
4803
  domain: "support",
@@ -4738,10 +5083,24 @@ function stableId(memoryId, type, content) {
4738
5083
  return createHash2("sha256").update(`${memoryId}:${type}:${content}`).digest("hex").slice(0, 32);
4739
5084
  }
4740
5085
  function cleanText(text) {
4741
- return text.replace(/```[\s\S]*?```/g, " ").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
5086
+ let cleaned = text.replace(
5087
+ /```(\w*)\n(.*?)(?:\n[\s\S]*?)```/g,
5088
+ (_m, lang, firstLine) => `[code${lang ? `:${lang}` : ""}] ${firstLine.trim()}`
5089
+ );
5090
+ cleaned = cleaned.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
5091
+ return cleaned;
4742
5092
  }
4743
- function splitSentences(text) {
4744
- return cleanText(text).split(/(?<=[.!?])\s+|\n+/).map((s) => s.trim()).filter((s) => s.length >= 24 && s.length <= MAX_SENTENCE_CHARS);
5093
+ function splitSegments(text) {
5094
+ const cleaned = cleanText(text);
5095
+ const segments = cleaned.split(/(?<=[.!?:;])\s+|\n{2,}|(?<=\))\s+(?=[A-Z])|\s*[|│]\s*/).map((s) => s.trim()).filter((s) => s.length >= MIN_SEGMENT_CHARS && s.length <= MAX_SEGMENT_CHARS);
5096
+ if (segments.length === 0 && cleaned.length >= MIN_SEGMENT_CHARS) {
5097
+ const lines = cleaned.split(/\n+/).map((l) => l.trim()).filter((l) => l.length >= MIN_SEGMENT_CHARS && l.length <= MAX_SEGMENT_CHARS);
5098
+ if (lines.length > 0) return lines;
5099
+ if (cleaned.length >= MIN_SEGMENT_CHARS) {
5100
+ return [cleaned.slice(0, MAX_SEGMENT_CHARS)];
5101
+ }
5102
+ }
5103
+ return segments;
4745
5104
  }
4746
5105
  function inferCardType(sentence, toolName) {
4747
5106
  const lower = sentence.toLowerCase();
@@ -4773,12 +5132,12 @@ function predicateFor(type) {
4773
5132
  }
4774
5133
  }
4775
5134
  function extractMemoryCards(row) {
4776
- const sentences = splitSentences(row.raw_text);
5135
+ const segments = splitSegments(row.raw_text);
4777
5136
  const cards = [];
4778
- for (const sentence of sentences) {
5137
+ for (const sentence of segments) {
4779
5138
  const type = inferCardType(sentence, row.tool_name);
4780
5139
  const subject = extractSubject(sentence, row.agent_id);
4781
- const content = sentence.length > MAX_SENTENCE_CHARS ? `${sentence.slice(0, MAX_SENTENCE_CHARS - 1)}\u2026` : sentence;
5140
+ const content = sentence.length > MAX_SEGMENT_CHARS ? `${sentence.slice(0, MAX_SEGMENT_CHARS - 1)}\u2026` : sentence;
4782
5141
  cards.push({
4783
5142
  id: stableId(row.id, type, content),
4784
5143
  memory_id: row.id,
@@ -4874,13 +5233,14 @@ Source memory: ${String(row.source_ref ?? row.memory_id)}`,
4874
5233
  last_accessed: String(row.timestamp)
4875
5234
  }));
4876
5235
  }
4877
- var MAX_CARDS_PER_MEMORY, MAX_SENTENCE_CHARS;
5236
+ var MAX_CARDS_PER_MEMORY, MAX_SEGMENT_CHARS, MIN_SEGMENT_CHARS;
4878
5237
  var init_memory_cards = __esm({
4879
5238
  "src/lib/memory-cards.ts"() {
4880
5239
  "use strict";
4881
5240
  init_database();
4882
- MAX_CARDS_PER_MEMORY = 6;
4883
- MAX_SENTENCE_CHARS = 360;
5241
+ MAX_CARDS_PER_MEMORY = 8;
5242
+ MAX_SEGMENT_CHARS = 500;
5243
+ MIN_SEGMENT_CHARS = 20;
4884
5244
  }
4885
5245
  });
4886
5246
 
@@ -5816,12 +6176,12 @@ var init_store = __esm({
5816
6176
  });
5817
6177
 
5818
6178
  // src/lib/session-registry.ts
5819
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4, existsSync as existsSync9 } from "fs";
5820
- import path8 from "path";
6179
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, existsSync as existsSync10 } from "fs";
6180
+ import path9 from "path";
5821
6181
  import os6 from "os";
5822
6182
  function registerSession(entry) {
5823
- const dir = path8.dirname(REGISTRY_PATH);
5824
- if (!existsSync9(dir)) {
6183
+ const dir = path9.dirname(REGISTRY_PATH);
6184
+ if (!existsSync10(dir)) {
5825
6185
  mkdirSync4(dir, { recursive: true });
5826
6186
  }
5827
6187
  const sessions = listSessions();
@@ -5831,11 +6191,11 @@ function registerSession(entry) {
5831
6191
  } else {
5832
6192
  sessions.push(entry);
5833
6193
  }
5834
- writeFileSync3(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
6194
+ writeFileSync4(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
5835
6195
  }
5836
6196
  function listSessions() {
5837
6197
  try {
5838
- const raw = readFileSync5(REGISTRY_PATH, "utf8");
6198
+ const raw = readFileSync6(REGISTRY_PATH, "utf8");
5839
6199
  return JSON.parse(raw);
5840
6200
  } catch {
5841
6201
  return [];
@@ -5845,7 +6205,7 @@ var REGISTRY_PATH;
5845
6205
  var init_session_registry = __esm({
5846
6206
  "src/lib/session-registry.ts"() {
5847
6207
  "use strict";
5848
- REGISTRY_PATH = path8.join(os6.homedir(), ".exe-os", "session-registry.json");
6208
+ REGISTRY_PATH = path9.join(os6.homedir(), ".exe-os", "session-registry.json");
5849
6209
  }
5850
6210
  });
5851
6211
 
@@ -6107,68 +6467,6 @@ var init_provider_table = __esm({
6107
6467
  }
6108
6468
  });
6109
6469
 
6110
- // src/lib/runtime-table.ts
6111
- var RUNTIME_TABLE, DEFAULT_RUNTIME;
6112
- var init_runtime_table = __esm({
6113
- "src/lib/runtime-table.ts"() {
6114
- "use strict";
6115
- RUNTIME_TABLE = {
6116
- codex: {
6117
- binary: "codex",
6118
- launchMode: "interactive",
6119
- autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
6120
- inlineFlag: "--no-alt-screen",
6121
- apiKeyEnv: "OPENAI_API_KEY",
6122
- defaultModel: "gpt-5.5"
6123
- },
6124
- opencode: {
6125
- binary: "opencode",
6126
- launchMode: "exec",
6127
- autoApproveFlag: "--dangerously-skip-permissions",
6128
- inlineFlag: "",
6129
- apiKeyEnv: "ANTHROPIC_API_KEY",
6130
- defaultModel: "anthropic/claude-sonnet-4-6"
6131
- }
6132
- };
6133
- DEFAULT_RUNTIME = "claude";
6134
- }
6135
- });
6136
-
6137
- // src/lib/agent-config.ts
6138
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync10 } from "fs";
6139
- import path9 from "path";
6140
- function loadAgentConfig() {
6141
- if (!existsSync10(AGENT_CONFIG_PATH)) return {};
6142
- try {
6143
- return JSON.parse(readFileSync6(AGENT_CONFIG_PATH, "utf-8"));
6144
- } catch {
6145
- return {};
6146
- }
6147
- }
6148
- function getAgentRuntime(agentId) {
6149
- const config = loadAgentConfig();
6150
- const entry = config[agentId];
6151
- if (entry) return entry;
6152
- const orgDefault = config["default"];
6153
- if (orgDefault) return orgDefault;
6154
- return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
6155
- }
6156
- var AGENT_CONFIG_PATH, DEFAULT_MODELS;
6157
- var init_agent_config = __esm({
6158
- "src/lib/agent-config.ts"() {
6159
- "use strict";
6160
- init_config();
6161
- init_runtime_table();
6162
- init_secure_files();
6163
- AGENT_CONFIG_PATH = path9.join(EXE_AI_DIR, "agent-config.json");
6164
- DEFAULT_MODELS = {
6165
- claude: "claude-opus-4.6",
6166
- codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
6167
- opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
6168
- };
6169
- }
6170
- });
6171
-
6172
6470
  // src/lib/intercom-queue.ts
6173
6471
  var intercom_queue_exports = {};
6174
6472
  __export(intercom_queue_exports, {
@@ -6663,7 +6961,7 @@ async function assertVpsLicense(opts) {
6663
6961
  }
6664
6962
  if (!transientFailure) {
6665
6963
  throw new Error(
6666
- "License validation failed: unknown backend state. Restore network connectivity to https://askexe.com/cloud and retry."
6964
+ "License validation failed: unknown backend state. Restore network connectivity to https://cloud.askexe.com and retry."
6667
6965
  );
6668
6966
  }
6669
6967
  const fresh = await getCachedLicense();
@@ -6700,7 +6998,7 @@ async function assertVpsLicense(opts) {
6700
6998
  } catch {
6701
6999
  }
6702
7000
  throw new Error(
6703
- `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.`
7001
+ `License validation unreachable for more than ${graceDays} days. Restore network connectivity to https://cloud.askexe.com and retry. This VPS image refuses to boot after the offline grace window.`
6704
7002
  );
6705
7003
  }
6706
7004
  function startLicenseRevalidation(intervalMs = 36e5) {
@@ -6732,7 +7030,7 @@ var init_license = __esm({
6732
7030
  LICENSE_PATH = path11.join(EXE_AI_DIR, "license.key");
6733
7031
  CACHE_PATH = path11.join(EXE_AI_DIR, "license-cache.json");
6734
7032
  DEVICE_ID_PATH = path11.join(EXE_AI_DIR, "device-id");
6735
- API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com/cloud";
7033
+ API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://cloud.askexe.com";
6736
7034
  RETRY_DELAY_MS = 500;
6737
7035
  LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
6738
7036
  MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
@@ -7198,6 +7496,19 @@ async function resolveTask(client, identifier, scopeSession) {
7198
7496
  args: [identifier, ...scope.args]
7199
7497
  });
7200
7498
  if (result.rows.length === 1) return result.rows[0];
7499
+ if (/^[a-f0-9]{7,12}$/i.test(identifier)) {
7500
+ result = await client.execute({
7501
+ sql: `SELECT * FROM tasks WHERE id LIKE ?`,
7502
+ args: [`${identifier}%`]
7503
+ });
7504
+ if (result.rows.length === 1) return result.rows[0];
7505
+ if (result.rows.length > 1) {
7506
+ const matches = result.rows.map((r) => `${String(r.id)} "${String(r.title)}" (${String(r.status)})`).join(", ");
7507
+ throw new Error(
7508
+ `Multiple tasks match short-ID "${identifier}": ${matches}. Use a longer prefix to disambiguate.`
7509
+ );
7510
+ }
7511
+ }
7201
7512
  result = await client.execute({
7202
7513
  sql: `SELECT * FROM tasks WHERE task_file LIKE ?${scope.sql}`,
7203
7514
  args: [`%${identifier}%`, ...scope.args]
@@ -8052,12 +8363,13 @@ async function cascadeUnblock(taskId, baseDir, now) {
8052
8363
  WHERE blocked_by = ? AND status = 'blocked'`,
8053
8364
  args: [now, taskId]
8054
8365
  });
8055
- if (baseDir && unblocked.rowsAffected > 0) {
8056
- const ubScope = sessionScopeFilter();
8057
- const unblockedRows = await client.execute({
8058
- sql: `SELECT task_file FROM tasks WHERE blocked_by IS NULL AND updated_at = ?${ubScope.sql}`,
8059
- args: [now, ...ubScope.args]
8060
- });
8366
+ if (unblocked.rowsAffected === 0) return;
8367
+ const ubScope = sessionScopeFilter();
8368
+ const unblockedRows = await client.execute({
8369
+ sql: `SELECT id, title, assigned_to, priority, task_file FROM tasks WHERE blocked_by IS NULL AND updated_at = ?${ubScope.sql}`,
8370
+ args: [now, ...ubScope.args]
8371
+ });
8372
+ if (baseDir) {
8061
8373
  for (const ur of unblockedRows.rows) {
8062
8374
  try {
8063
8375
  const ubFile = path18.join(baseDir, String(ur.task_file));
@@ -8069,6 +8381,19 @@ async function cascadeUnblock(taskId, baseDir, now) {
8069
8381
  }
8070
8382
  }
8071
8383
  }
8384
+ if (unblockedRows.rows.length > 0 && !process.env.VITEST) {
8385
+ try {
8386
+ const { queueIntercom: queueIntercom2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
8387
+ const dispatched = /* @__PURE__ */ new Set();
8388
+ for (const ur of unblockedRows.rows) {
8389
+ const assignee = String(ur.assigned_to);
8390
+ if (dispatched.has(assignee)) continue;
8391
+ dispatched.add(assignee);
8392
+ queueIntercom2(`${assignee}`, `unblocked: "${String(ur.title)}" is now ready`);
8393
+ }
8394
+ } catch {
8395
+ }
8396
+ }
8072
8397
  }
8073
8398
  async function findNextTask(assignedTo) {
8074
8399
  const client = getClient();
@@ -8278,6 +8603,15 @@ var init_embedder = __esm({
8278
8603
  // src/lib/behaviors.ts
8279
8604
  import crypto5 from "crypto";
8280
8605
  async function storeBehavior(opts) {
8606
+ try {
8607
+ const { loadEmployeesSync: loadEmployeesSync2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
8608
+ const roster = loadEmployeesSync2();
8609
+ if (roster.length > 0 && !roster.some((e) => e.name === opts.agentId)) {
8610
+ throw new Error(`Agent "${opts.agentId}" not found in roster. Cannot store behavior for unregistered agent.`);
8611
+ }
8612
+ } catch (e) {
8613
+ if (e instanceof Error && e.message.includes("not found in roster")) throw e;
8614
+ }
8281
8615
  const client = getClient();
8282
8616
  const id = crypto5.randomUUID();
8283
8617
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -8731,6 +9065,12 @@ async function updateTask(input) {
8731
9065
  }
8732
9066
  }
8733
9067
  }
9068
+ if (input.status === "cancelled") {
9069
+ try {
9070
+ await cascadeUnblock(taskId, input.baseDir, now);
9071
+ } catch {
9072
+ }
9073
+ }
8734
9074
  if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
8735
9075
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
8736
9076
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
@@ -9262,11 +9602,12 @@ function getDispatchedBy(sessionKey) {
9262
9602
  }
9263
9603
  }
9264
9604
  function resolveExeSession() {
9605
+ if (process.env.EXE_SESSION_NAME) {
9606
+ const fromEnv = extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
9607
+ if (fromEnv) return fromEnv;
9608
+ }
9265
9609
  const mySession = getMySession();
9266
9610
  if (!mySession) {
9267
- if (process.env.EXE_SESSION_NAME) {
9268
- return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
9269
- }
9270
9611
  return null;
9271
9612
  }
9272
9613
  const fromSessionName = extractRootExe(mySession);
@@ -9281,6 +9622,10 @@ function resolveExeSession() {
9281
9622
  `[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
9282
9623
  `
9283
9624
  );
9625
+ try {
9626
+ registerParentExe(key, fromSessionName);
9627
+ } catch {
9628
+ }
9284
9629
  candidate = fromSessionName;
9285
9630
  } else {
9286
9631
  candidate = fromCache;