@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
@@ -330,11 +330,168 @@ var init_config = __esm({
330
330
  }
331
331
  });
332
332
 
333
+ // src/lib/runtime-table.ts
334
+ var RUNTIME_TABLE, DEFAULT_RUNTIME;
335
+ var init_runtime_table = __esm({
336
+ "src/lib/runtime-table.ts"() {
337
+ "use strict";
338
+ RUNTIME_TABLE = {
339
+ codex: {
340
+ binary: "codex",
341
+ launchMode: "interactive",
342
+ autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
343
+ inlineFlag: "--no-alt-screen",
344
+ apiKeyEnv: "OPENAI_API_KEY",
345
+ defaultModel: "gpt-5.5"
346
+ },
347
+ opencode: {
348
+ binary: "opencode",
349
+ launchMode: "exec",
350
+ autoApproveFlag: "--dangerously-skip-permissions",
351
+ inlineFlag: "",
352
+ apiKeyEnv: "ANTHROPIC_API_KEY",
353
+ defaultModel: "anthropic/claude-sonnet-4-6"
354
+ }
355
+ };
356
+ DEFAULT_RUNTIME = "claude";
357
+ }
358
+ });
359
+
360
+ // src/lib/agent-config.ts
361
+ var agent_config_exports = {};
362
+ __export(agent_config_exports, {
363
+ AGENT_CONFIG_PATH: () => AGENT_CONFIG_PATH,
364
+ DEFAULT_MODELS: () => DEFAULT_MODELS,
365
+ KNOWN_RUNTIMES: () => KNOWN_RUNTIMES,
366
+ RUNTIME_LABELS: () => RUNTIME_LABELS,
367
+ clearAgentRuntime: () => clearAgentRuntime,
368
+ getAgentRuntime: () => getAgentRuntime,
369
+ loadAgentConfig: () => loadAgentConfig,
370
+ saveAgentConfig: () => saveAgentConfig,
371
+ setAgentMcps: () => setAgentMcps,
372
+ setAgentRuntime: () => setAgentRuntime
373
+ });
374
+ import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
375
+ import path2 from "path";
376
+ function loadAgentConfig() {
377
+ if (!existsSync3(AGENT_CONFIG_PATH)) return {};
378
+ try {
379
+ return JSON.parse(readFileSync2(AGENT_CONFIG_PATH, "utf-8"));
380
+ } catch {
381
+ return {};
382
+ }
383
+ }
384
+ function saveAgentConfig(config) {
385
+ const dir = path2.dirname(AGENT_CONFIG_PATH);
386
+ ensurePrivateDirSync(dir);
387
+ writeFileSync(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
388
+ enforcePrivateFileSync(AGENT_CONFIG_PATH);
389
+ }
390
+ function getAgentRuntime(agentId) {
391
+ const config = loadAgentConfig();
392
+ const entry = config[agentId];
393
+ if (entry) return entry;
394
+ const orgDefault = config["default"];
395
+ if (orgDefault) return orgDefault;
396
+ return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
397
+ }
398
+ function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
399
+ const knownModels = KNOWN_RUNTIMES[runtime];
400
+ if (!knownModels) {
401
+ return {
402
+ ok: false,
403
+ error: `Unknown runtime "${runtime}". Valid: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`
404
+ };
405
+ }
406
+ if (!knownModels.includes(model)) {
407
+ return {
408
+ ok: false,
409
+ error: `Unknown model "${model}" for runtime "${runtime}". Valid: ${knownModels.join(", ")}`
410
+ };
411
+ }
412
+ const config = loadAgentConfig();
413
+ const existing = config[agentId];
414
+ const entry = { runtime, model };
415
+ if (reasoning_effort) entry.reasoning_effort = reasoning_effort;
416
+ if (mcps !== void 0) {
417
+ entry.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
418
+ } else if (existing?.mcps) {
419
+ entry.mcps = existing.mcps;
420
+ }
421
+ config[agentId] = entry;
422
+ saveAgentConfig(config);
423
+ return { ok: true };
424
+ }
425
+ function setAgentMcps(agentId, mcps) {
426
+ const config = loadAgentConfig();
427
+ const existing = config[agentId] ?? getAgentRuntime(agentId);
428
+ existing.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
429
+ config[agentId] = existing;
430
+ saveAgentConfig(config);
431
+ return { ok: true };
432
+ }
433
+ function clearAgentRuntime(agentId) {
434
+ const config = loadAgentConfig();
435
+ delete config[agentId];
436
+ saveAgentConfig(config);
437
+ }
438
+ var AGENT_CONFIG_PATH, KNOWN_RUNTIMES, RUNTIME_LABELS, DEFAULT_MODELS;
439
+ var init_agent_config = __esm({
440
+ "src/lib/agent-config.ts"() {
441
+ "use strict";
442
+ init_config();
443
+ init_runtime_table();
444
+ init_secure_files();
445
+ AGENT_CONFIG_PATH = path2.join(EXE_AI_DIR, "agent-config.json");
446
+ KNOWN_RUNTIMES = {
447
+ claude: ["claude-opus-4.6", "claude-opus-4", "claude-sonnet-4.6", "claude-sonnet-4", "claude-haiku-4.5"],
448
+ codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
449
+ opencode: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4", "google/gemini-2.5-pro", "deepseek/deepseek-r3", "minimax/minimax-m2.5"]
450
+ };
451
+ RUNTIME_LABELS = {
452
+ claude: "Claude Code (Anthropic)",
453
+ codex: "Codex (OpenAI)",
454
+ opencode: "OpenCode (open source)"
455
+ };
456
+ DEFAULT_MODELS = {
457
+ claude: "claude-opus-4.6",
458
+ codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
459
+ opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
460
+ };
461
+ }
462
+ });
463
+
333
464
  // src/lib/employees.ts
465
+ var employees_exports = {};
466
+ __export(employees_exports, {
467
+ COORDINATOR_ROLE: () => COORDINATOR_ROLE,
468
+ DEFAULT_COORDINATOR_TEMPLATE_NAME: () => DEFAULT_COORDINATOR_TEMPLATE_NAME,
469
+ EMPLOYEES_PATH: () => EMPLOYEES_PATH,
470
+ addEmployee: () => addEmployee,
471
+ baseAgentName: () => baseAgentName,
472
+ canCoordinate: () => canCoordinate,
473
+ getCoordinatorEmployee: () => getCoordinatorEmployee,
474
+ getCoordinatorName: () => getCoordinatorName,
475
+ getEmployee: () => getEmployee,
476
+ getEmployeeByRole: () => getEmployeeByRole,
477
+ getEmployeeNamesByRole: () => getEmployeeNamesByRole,
478
+ hasRole: () => hasRole,
479
+ hireEmployee: () => hireEmployee,
480
+ isCoordinatorName: () => isCoordinatorName,
481
+ isCoordinatorRole: () => isCoordinatorRole,
482
+ isMultiInstance: () => isMultiInstance,
483
+ loadEmployees: () => loadEmployees,
484
+ loadEmployeesSync: () => loadEmployeesSync,
485
+ normalizeRole: () => normalizeRole,
486
+ normalizeRosterCase: () => normalizeRosterCase,
487
+ registerBinSymlinks: () => registerBinSymlinks,
488
+ saveEmployees: () => saveEmployees,
489
+ validateEmployeeName: () => validateEmployeeName
490
+ });
334
491
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
335
- import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
492
+ import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
336
493
  import { execSync } from "child_process";
337
- import path2 from "path";
494
+ import path3 from "path";
338
495
  import os2 from "os";
339
496
  function normalizeRole(role) {
340
497
  return (role ?? "").trim().toLowerCase();
@@ -355,10 +512,40 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
355
512
  function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
356
513
  return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
357
514
  }
515
+ function validateEmployeeName(name) {
516
+ if (!name) {
517
+ return { valid: false, error: "Name is required" };
518
+ }
519
+ if (name.length > 32) {
520
+ return { valid: false, error: "Name must be 32 characters or fewer" };
521
+ }
522
+ if (!/^[a-z][a-z0-9]*$/.test(name)) {
523
+ return {
524
+ valid: false,
525
+ error: "Name must start with a letter and contain only lowercase alphanumeric characters"
526
+ };
527
+ }
528
+ return { valid: true };
529
+ }
530
+ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
531
+ if (!existsSync4(employeesPath)) {
532
+ return [];
533
+ }
534
+ const raw = await readFile2(employeesPath, "utf-8");
535
+ try {
536
+ return JSON.parse(raw);
537
+ } catch {
538
+ return [];
539
+ }
540
+ }
541
+ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
542
+ await mkdir2(path3.dirname(employeesPath), { recursive: true });
543
+ await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
544
+ }
358
545
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
359
- if (!existsSync3(employeesPath)) return [];
546
+ if (!existsSync4(employeesPath)) return [];
360
547
  try {
361
- return JSON.parse(readFileSync2(employeesPath, "utf-8"));
548
+ return JSON.parse(readFileSync3(employeesPath, "utf-8"));
362
549
  } catch {
363
550
  return [];
364
551
  }
@@ -366,6 +553,19 @@ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
366
553
  function getEmployee(employees, name) {
367
554
  return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
368
555
  }
556
+ function getEmployeeByRole(employees, role) {
557
+ const lower = role.toLowerCase();
558
+ return employees.find((e) => e.role.toLowerCase() === lower);
559
+ }
560
+ function getEmployeeNamesByRole(employees, role) {
561
+ const lower = role.toLowerCase();
562
+ return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
563
+ }
564
+ function hasRole(agentName, role) {
565
+ const employees = loadEmployeesSync();
566
+ const emp = getEmployee(employees, agentName);
567
+ return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
568
+ }
369
569
  function baseAgentName(name, employees) {
370
570
  const match = name.match(/^([a-zA-Z]+)\d+$/);
371
571
  if (!match) return name;
@@ -374,15 +574,147 @@ function baseAgentName(name, employees) {
374
574
  if (getEmployee(roster, base)) return base;
375
575
  return name;
376
576
  }
377
- var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, IDENTITY_DIR;
577
+ function isMultiInstance(agentName, employees) {
578
+ const roster = employees ?? loadEmployeesSync();
579
+ const emp = getEmployee(roster, agentName);
580
+ if (!emp) return false;
581
+ return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
582
+ }
583
+ function addEmployee(employees, employee) {
584
+ const { systemPrompt: _legacyPrompt, ...rest } = employee;
585
+ const normalized = { ...rest, name: employee.name.toLowerCase() };
586
+ if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
587
+ throw new Error(`Employee '${normalized.name}' already exists`);
588
+ }
589
+ return [...employees, normalized];
590
+ }
591
+ function appendToCoordinatorTeam(employee) {
592
+ const coordinator = getCoordinatorEmployee(loadEmployeesSync());
593
+ if (!coordinator) return;
594
+ const idPath = path3.join(IDENTITY_DIR, `${coordinator.name}.md`);
595
+ if (!existsSync4(idPath)) return;
596
+ const content = readFileSync3(idPath, "utf-8");
597
+ if (content.includes(`**${capitalize(employee.name)}`)) return;
598
+ const teamMatch = content.match(TEAM_SECTION_RE);
599
+ if (!teamMatch || teamMatch.index === void 0) return;
600
+ const afterTeam = content.slice(teamMatch.index + teamMatch[0].length);
601
+ const nextHeading = afterTeam.match(/\n## /);
602
+ const entry = `
603
+ **${capitalize(employee.name)} (${employee.role}):** Newly hired. Update this description as the role develops.
604
+ `;
605
+ let updated;
606
+ if (nextHeading && nextHeading.index !== void 0) {
607
+ const insertAt = teamMatch.index + teamMatch[0].length + nextHeading.index;
608
+ updated = content.slice(0, insertAt) + entry + content.slice(insertAt);
609
+ } else {
610
+ updated = content.trimEnd() + "\n" + entry;
611
+ }
612
+ writeFileSync2(idPath, updated, "utf-8");
613
+ }
614
+ function capitalize(s) {
615
+ return s.charAt(0).toUpperCase() + s.slice(1);
616
+ }
617
+ async function hireEmployee(employee) {
618
+ const employees = await loadEmployees();
619
+ const updated = addEmployee(employees, employee);
620
+ await saveEmployees(updated);
621
+ try {
622
+ appendToCoordinatorTeam(employee);
623
+ } catch {
624
+ }
625
+ try {
626
+ const { loadAgentConfig: loadAgentConfig2, saveAgentConfig: saveAgentConfig2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
627
+ const config = loadAgentConfig2();
628
+ const name = employee.name.toLowerCase();
629
+ if (!config[name] && config["default"]) {
630
+ config[name] = { ...config["default"] };
631
+ saveAgentConfig2(config);
632
+ }
633
+ } catch {
634
+ }
635
+ return updated;
636
+ }
637
+ async function normalizeRosterCase(rosterPath) {
638
+ const employees = await loadEmployees(rosterPath);
639
+ let changed = false;
640
+ for (const emp of employees) {
641
+ if (emp.name !== emp.name.toLowerCase()) {
642
+ const oldName = emp.name;
643
+ emp.name = emp.name.toLowerCase();
644
+ changed = true;
645
+ try {
646
+ const identityDir = path3.join(os2.homedir(), ".exe-os", "identity");
647
+ const oldPath = path3.join(identityDir, `${oldName}.md`);
648
+ const newPath = path3.join(identityDir, `${emp.name}.md`);
649
+ if (existsSync4(oldPath) && !existsSync4(newPath)) {
650
+ renameSync2(oldPath, newPath);
651
+ } else if (existsSync4(oldPath) && oldPath !== newPath) {
652
+ const content = readFileSync3(oldPath, "utf-8");
653
+ writeFileSync2(newPath, content, "utf-8");
654
+ if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
655
+ unlinkSync(oldPath);
656
+ }
657
+ }
658
+ } catch {
659
+ }
660
+ }
661
+ }
662
+ if (changed) {
663
+ await saveEmployees(employees, rosterPath);
664
+ }
665
+ return changed;
666
+ }
667
+ function findExeBin() {
668
+ try {
669
+ return execSync(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
670
+ } catch {
671
+ return null;
672
+ }
673
+ }
674
+ function registerBinSymlinks(name) {
675
+ const created = [];
676
+ const skipped = [];
677
+ const errors = [];
678
+ const exeBinPath = findExeBin();
679
+ if (!exeBinPath) {
680
+ errors.push("Could not find 'exe-os' in PATH");
681
+ return { created, skipped, errors };
682
+ }
683
+ const binDir = path3.dirname(exeBinPath);
684
+ let target;
685
+ try {
686
+ target = readlinkSync(exeBinPath);
687
+ } catch {
688
+ errors.push("Could not read 'exe' symlink");
689
+ return { created, skipped, errors };
690
+ }
691
+ for (const suffix of ["", "-opencode"]) {
692
+ const linkName = `${name}${suffix}`;
693
+ const linkPath = path3.join(binDir, linkName);
694
+ if (existsSync4(linkPath)) {
695
+ skipped.push(linkName);
696
+ continue;
697
+ }
698
+ try {
699
+ symlinkSync(target, linkPath);
700
+ created.push(linkName);
701
+ } catch (err) {
702
+ errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
703
+ }
704
+ }
705
+ return { created, skipped, errors };
706
+ }
707
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR, TEAM_SECTION_RE;
378
708
  var init_employees = __esm({
379
709
  "src/lib/employees.ts"() {
380
710
  "use strict";
381
711
  init_config();
382
- EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
712
+ EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
383
713
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
384
714
  COORDINATOR_ROLE = "COO";
385
- IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
715
+ MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
716
+ IDENTITY_DIR = path3.join(EXE_AI_DIR, "identity");
717
+ TEAM_SECTION_RE = /^## Team\b.*$/m;
386
718
  }
387
719
  });
388
720
 
@@ -443,7 +775,7 @@ var init_db_retry = __esm({
443
775
 
444
776
  // src/lib/database-adapter.ts
445
777
  import os3 from "os";
446
- import path3 from "path";
778
+ import path4 from "path";
447
779
  import { createRequire } from "module";
448
780
  import { pathToFileURL } from "url";
449
781
  function quotedIdentifier(identifier) {
@@ -754,8 +1086,8 @@ async function loadPrismaClient() {
754
1086
  }
755
1087
  return new PrismaClient2();
756
1088
  }
757
- const exeDbRoot = process.env.EXE_DB_ROOT ?? path3.join(os3.homedir(), "exe-db");
758
- const requireFromExeDb = createRequire(path3.join(exeDbRoot, "package.json"));
1089
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path4.join(os3.homedir(), "exe-db");
1090
+ const requireFromExeDb = createRequire(path4.join(exeDbRoot, "package.json"));
759
1091
  const prismaEntry = requireFromExeDb.resolve("@prisma/client");
760
1092
  const module = await import(pathToFileURL(prismaEntry).href);
761
1093
  const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
@@ -1036,8 +1368,8 @@ var init_memory = __esm({
1036
1368
 
1037
1369
  // src/lib/daemon-auth.ts
1038
1370
  import crypto from "crypto";
1039
- import path4 from "path";
1040
- import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
1371
+ import path5 from "path";
1372
+ import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
1041
1373
  function normalizeToken(token) {
1042
1374
  if (!token) return null;
1043
1375
  const trimmed = token.trim();
@@ -1045,8 +1377,8 @@ function normalizeToken(token) {
1045
1377
  }
1046
1378
  function readDaemonToken() {
1047
1379
  try {
1048
- if (!existsSync4(DAEMON_TOKEN_PATH)) return null;
1049
- return normalizeToken(readFileSync3(DAEMON_TOKEN_PATH, "utf8"));
1380
+ if (!existsSync5(DAEMON_TOKEN_PATH)) return null;
1381
+ return normalizeToken(readFileSync4(DAEMON_TOKEN_PATH, "utf8"));
1050
1382
  } catch {
1051
1383
  return null;
1052
1384
  }
@@ -1056,7 +1388,7 @@ function ensureDaemonToken(seed) {
1056
1388
  if (existing) return existing;
1057
1389
  const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
1058
1390
  ensurePrivateDirSync(EXE_AI_DIR);
1059
- writeFileSync2(DAEMON_TOKEN_PATH, `${token}
1391
+ writeFileSync3(DAEMON_TOKEN_PATH, `${token}
1060
1392
  `, "utf8");
1061
1393
  enforcePrivateFileSync(DAEMON_TOKEN_PATH);
1062
1394
  return token;
@@ -1067,7 +1399,7 @@ var init_daemon_auth = __esm({
1067
1399
  "use strict";
1068
1400
  init_config();
1069
1401
  init_secure_files();
1070
- DAEMON_TOKEN_PATH = path4.join(EXE_AI_DIR, "exed.token");
1402
+ DAEMON_TOKEN_PATH = path5.join(EXE_AI_DIR, "exed.token");
1071
1403
  }
1072
1404
  });
1073
1405
 
@@ -1087,8 +1419,8 @@ import net from "net";
1087
1419
  import os4 from "os";
1088
1420
  import { spawn, execSync as execSync2 } from "child_process";
1089
1421
  import { randomUUID } from "crypto";
1090
- import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
1091
- import path5 from "path";
1422
+ import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync5, openSync, closeSync, statSync } from "fs";
1423
+ import path6 from "path";
1092
1424
  import { fileURLToPath } from "url";
1093
1425
  function handleData(chunk) {
1094
1426
  _buffer += chunk.toString();
@@ -1124,9 +1456,9 @@ function isZombie(pid) {
1124
1456
  }
1125
1457
  }
1126
1458
  function cleanupStaleFiles() {
1127
- if (existsSync5(PID_PATH)) {
1459
+ if (existsSync6(PID_PATH)) {
1128
1460
  try {
1129
- const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
1461
+ const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
1130
1462
  if (pid > 0) {
1131
1463
  try {
1132
1464
  process.kill(pid, 0);
@@ -1151,11 +1483,11 @@ function cleanupStaleFiles() {
1151
1483
  }
1152
1484
  }
1153
1485
  function findPackageRoot() {
1154
- let dir = path5.dirname(fileURLToPath(import.meta.url));
1155
- const { root } = path5.parse(dir);
1486
+ let dir = path6.dirname(fileURLToPath(import.meta.url));
1487
+ const { root } = path6.parse(dir);
1156
1488
  while (dir !== root) {
1157
- if (existsSync5(path5.join(dir, "package.json"))) return dir;
1158
- dir = path5.dirname(dir);
1489
+ if (existsSync6(path6.join(dir, "package.json"))) return dir;
1490
+ dir = path6.dirname(dir);
1159
1491
  }
1160
1492
  return null;
1161
1493
  }
@@ -1173,8 +1505,8 @@ function spawnDaemon() {
1173
1505
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
1174
1506
  return;
1175
1507
  }
1176
- const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1177
- if (!existsSync5(daemonPath)) {
1508
+ const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1509
+ if (!existsSync6(daemonPath)) {
1178
1510
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
1179
1511
  `);
1180
1512
  return;
@@ -1183,7 +1515,7 @@ function spawnDaemon() {
1183
1515
  const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
1184
1516
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
1185
1517
  `);
1186
- const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
1518
+ const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
1187
1519
  let stderrFd = "ignore";
1188
1520
  try {
1189
1521
  stderrFd = openSync(logPath, "a");
@@ -1348,9 +1680,9 @@ function killAndRespawnDaemon() {
1348
1680
  }
1349
1681
  try {
1350
1682
  process.stderr.write("[exed-client] Killing daemon for restart...\n");
1351
- if (existsSync5(PID_PATH)) {
1683
+ if (existsSync6(PID_PATH)) {
1352
1684
  try {
1353
- const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
1685
+ const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
1354
1686
  if (pid > 0) {
1355
1687
  try {
1356
1688
  process.kill(pid, "SIGKILL");
@@ -1496,9 +1828,9 @@ var init_exe_daemon_client = __esm({
1496
1828
  "use strict";
1497
1829
  init_config();
1498
1830
  init_daemon_auth();
1499
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path5.join(EXE_AI_DIR, "exed.sock");
1500
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path5.join(EXE_AI_DIR, "exed.pid");
1501
- SPAWN_LOCK_PATH = path5.join(EXE_AI_DIR, "exed-spawn.lock");
1831
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
1832
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
1833
+ SPAWN_LOCK_PATH = path6.join(EXE_AI_DIR, "exed-spawn.lock");
1502
1834
  SPAWN_LOCK_STALE_MS = 3e4;
1503
1835
  CONNECT_TIMEOUT_MS = 15e3;
1504
1836
  REQUEST_TIMEOUT_MS = 3e4;
@@ -1766,7 +2098,7 @@ __export(database_exports, {
1766
2098
  isInitialized: () => isInitialized,
1767
2099
  setExternalClient: () => setExternalClient
1768
2100
  });
1769
- 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";
2101
+ 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";
1770
2102
  import { createClient } from "@libsql/client";
1771
2103
  import { homedir } from "os";
1772
2104
  import { join } from "path";
@@ -1819,11 +2151,11 @@ function releaseDbLock() {
1819
2151
  }
1820
2152
  async function initDatabase(config) {
1821
2153
  acquireDbLock();
1822
- if (existsSync6(config.dbPath)) {
2154
+ if (existsSync7(config.dbPath)) {
1823
2155
  const dbStat = statSync2(config.dbPath);
1824
2156
  if (dbStat.size === 0) {
1825
2157
  const walPath = config.dbPath + "-wal";
1826
- if (existsSync6(walPath) && statSync2(walPath).size > 0) {
2158
+ if (existsSync7(walPath) && statSync2(walPath).size > 0) {
1827
2159
  const backupPath = config.dbPath + ".zeroed-" + Date.now();
1828
2160
  copyFileSync(config.dbPath, backupPath);
1829
2161
  unlinkSync3(config.dbPath);
@@ -3342,6 +3674,22 @@ async function ensureSchema() {
3342
3674
  } catch (e) {
3343
3675
  logCatchDebug("migration", e);
3344
3676
  }
3677
+ try {
3678
+ await client.execute({
3679
+ sql: `ALTER TABLE memories ADD COLUMN visibility TEXT DEFAULT 'private'`,
3680
+ args: []
3681
+ });
3682
+ } catch (e) {
3683
+ logCatchDebug("migration", e);
3684
+ }
3685
+ try {
3686
+ await client.execute({
3687
+ sql: `ALTER TABLE memories ADD COLUMN strength REAL DEFAULT 1.0`,
3688
+ args: []
3689
+ });
3690
+ } catch (e) {
3691
+ logCatchDebug("migration", e);
3692
+ }
3345
3693
  }
3346
3694
  async function disposeDatabase() {
3347
3695
  if (_walCheckpointTimer) {
@@ -3394,15 +3742,15 @@ var init_database = __esm({
3394
3742
 
3395
3743
  // src/lib/keychain.ts
3396
3744
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
3397
- import { existsSync as existsSync7, statSync as statSync3 } from "fs";
3745
+ import { existsSync as existsSync8, statSync as statSync3 } from "fs";
3398
3746
  import { execSync as execSync3 } from "child_process";
3399
- import path6 from "path";
3747
+ import path7 from "path";
3400
3748
  import os5 from "os";
3401
3749
  function getKeyDir() {
3402
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path6.join(os5.homedir(), ".exe-os");
3750
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path7.join(os5.homedir(), ".exe-os");
3403
3751
  }
3404
3752
  function getKeyPath() {
3405
- return path6.join(getKeyDir(), "master.key");
3753
+ return path7.join(getKeyDir(), "master.key");
3406
3754
  }
3407
3755
  function nativeKeychainAllowed() {
3408
3756
  return process.env.EXE_OS_DISABLE_NATIVE_KEYCHAIN !== "1";
@@ -3433,7 +3781,7 @@ function isRootOnlyTrustedServerKeyFile(keyPath) {
3433
3781
  if (!st.isFile() || (st.mode & 63) !== 0) return false;
3434
3782
  if (uid === 0) return true;
3435
3783
  const exeOsDir = process.env.EXE_OS_DIR;
3436
- return Boolean(exeOsDir && path6.resolve(keyPath).startsWith(path6.resolve(exeOsDir) + path6.sep));
3784
+ return Boolean(exeOsDir && path7.resolve(keyPath).startsWith(path7.resolve(exeOsDir) + path7.sep));
3437
3785
  } catch {
3438
3786
  return false;
3439
3787
  }
@@ -3630,7 +3978,7 @@ async function getMasterKey() {
3630
3978
  }
3631
3979
  }
3632
3980
  const keyPath = getKeyPath();
3633
- if (!existsSync7(keyPath)) {
3981
+ if (!existsSync8(keyPath)) {
3634
3982
  process.stderr.write(
3635
3983
  `[keychain] Key not found at ${keyPath} (HOME=${os5.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
3636
3984
  `
@@ -3964,14 +4312,14 @@ __export(shard_manager_exports, {
3964
4312
  listShards: () => listShards,
3965
4313
  shardExists: () => shardExists
3966
4314
  });
3967
- import path7 from "path";
3968
- import { existsSync as existsSync8, mkdirSync as mkdirSync3, readdirSync, renameSync as renameSync3, statSync as statSync4 } from "fs";
4315
+ import path8 from "path";
4316
+ import { existsSync as existsSync9, mkdirSync as mkdirSync3, readdirSync, renameSync as renameSync3, statSync as statSync4 } from "fs";
3969
4317
  import { createClient as createClient2 } from "@libsql/client";
3970
4318
  function initShardManager(encryptionKey) {
3971
4319
  _encryptionKey = encryptionKey;
3972
4320
  _keyValidated = false;
3973
4321
  _keyValidationPromise = null;
3974
- if (!existsSync8(SHARDS_DIR)) {
4322
+ if (!existsSync9(SHARDS_DIR)) {
3975
4323
  mkdirSync3(SHARDS_DIR, { recursive: true });
3976
4324
  }
3977
4325
  const existingShards = readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db"));
@@ -3992,7 +4340,7 @@ async function validateEncryptionKey() {
3992
4340
  return true;
3993
4341
  }
3994
4342
  for (const shardFile of existingShards.slice(0, 3)) {
3995
- const dbPath = path7.join(SHARDS_DIR, shardFile);
4343
+ const dbPath = path8.join(SHARDS_DIR, shardFile);
3996
4344
  const testClient = createClient2({ url: `file:${dbPath}`, encryptionKey: _encryptionKey });
3997
4345
  try {
3998
4346
  await testClient.execute("SELECT COUNT(*) FROM sqlite_schema");
@@ -4035,7 +4383,7 @@ function getShardClient(projectName) {
4035
4383
  while (_shards.size >= MAX_OPEN_SHARDS) {
4036
4384
  evictLRU();
4037
4385
  }
4038
- const dbPath = path7.join(SHARDS_DIR, `${safeName}.db`);
4386
+ const dbPath = path8.join(SHARDS_DIR, `${safeName}.db`);
4039
4387
  const client = createClient2({
4040
4388
  url: `file:${dbPath}`,
4041
4389
  encryptionKey: _encryptionKey
@@ -4046,13 +4394,13 @@ function getShardClient(projectName) {
4046
4394
  }
4047
4395
  function shardExists(projectName) {
4048
4396
  const safeName = safeShardName(projectName);
4049
- return existsSync8(path7.join(SHARDS_DIR, `${safeName}.db`));
4397
+ return existsSync9(path8.join(SHARDS_DIR, `${safeName}.db`));
4050
4398
  }
4051
4399
  function safeShardName(projectName) {
4052
4400
  return projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
4053
4401
  }
4054
4402
  function listShards() {
4055
- if (!existsSync8(SHARDS_DIR)) return [];
4403
+ if (!existsSync9(SHARDS_DIR)) return [];
4056
4404
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
4057
4405
  }
4058
4406
  async function auditShardHealth(options = {}) {
@@ -4064,7 +4412,7 @@ async function auditShardHealth(options = {}) {
4064
4412
  const names = listShards();
4065
4413
  const shards = [];
4066
4414
  for (const name of names) {
4067
- const dbPath = path7.join(SHARDS_DIR, `${name}.db`);
4415
+ const dbPath = path8.join(SHARDS_DIR, `${name}.db`);
4068
4416
  const stat = statSync4(dbPath);
4069
4417
  const item = {
4070
4418
  name,
@@ -4099,7 +4447,7 @@ async function auditShardHealth(options = {}) {
4099
4447
  _shards.delete(name);
4100
4448
  _shardLastAccess.delete(name);
4101
4449
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
4102
- const archivedPath = path7.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
4450
+ const archivedPath = path8.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
4103
4451
  renameSync3(dbPath, archivedPath);
4104
4452
  item.archivedPath = archivedPath;
4105
4453
  }
@@ -4327,11 +4675,11 @@ async function getReadyShardClient(projectName) {
4327
4675
  client.close();
4328
4676
  _shards.delete(safeName);
4329
4677
  _shardLastAccess.delete(safeName);
4330
- const dbPath = path7.join(SHARDS_DIR, `${safeName}.db`);
4331
- if (existsSync8(dbPath)) {
4678
+ const dbPath = path8.join(SHARDS_DIR, `${safeName}.db`);
4679
+ if (existsSync9(dbPath)) {
4332
4680
  const stat = statSync4(dbPath);
4333
4681
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
4334
- const archivedPath = path7.join(SHARDS_DIR, `${safeName}.db.broken-${stamp}`);
4682
+ const archivedPath = path8.join(SHARDS_DIR, `${safeName}.db.broken-${stamp}`);
4335
4683
  renameSync3(dbPath, archivedPath);
4336
4684
  process.stderr.write(
4337
4685
  `[shard-manager] Archived unreadable shard ${safeName}: ${archivedPath} (${stat.size} bytes, mtime ${stat.mtime.toISOString()})
@@ -4399,7 +4747,7 @@ var init_shard_manager = __esm({
4399
4747
  "src/lib/shard-manager.ts"() {
4400
4748
  "use strict";
4401
4749
  init_config();
4402
- SHARDS_DIR = path7.join(EXE_AI_DIR, "shards");
4750
+ SHARDS_DIR = path8.join(EXE_AI_DIR, "shards");
4403
4751
  SHARD_IDLE_MS = 5 * 60 * 1e3;
4404
4752
  MAX_OPEN_SHARDS = 10;
4405
4753
  EVICTION_INTERVAL_MS = 60 * 1e3;
@@ -4465,11 +4813,17 @@ var init_platform_procedures = __esm({
4465
4813
  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."
4466
4814
  },
4467
4815
  {
4468
- title: "Customer orchestration maturity \u2014 recommend, never trap",
4816
+ title: "Orchestration phase guidance \u2014 recommend, never trap",
4469
4817
  domain: "workflow",
4470
4818
  priority: "p1",
4471
4819
  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."
4472
4820
  },
4821
+ {
4822
+ title: "Routing slot vs display title \u2014 internal 'coo' is plumbing, not your name",
4823
+ domain: "identity",
4824
+ priority: "p0",
4825
+ 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."
4826
+ },
4473
4827
  {
4474
4828
  title: "Single dispatch path \u2014 create_task only",
4475
4829
  domain: "workflow",
@@ -4503,6 +4857,12 @@ var init_platform_procedures = __esm({
4503
4857
  priority: "p0",
4504
4858
  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."
4505
4859
  },
4860
+ {
4861
+ title: "Destructive operations \u2014 mandatory reviewer gate",
4862
+ domain: "security",
4863
+ priority: "p0",
4864
+ 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."
4865
+ },
4506
4866
  {
4507
4867
  title: "Customer patch triage \u2014 upstream bug vs customization",
4508
4868
  domain: "support",
@@ -4788,10 +5148,24 @@ function stableId(memoryId, type, content) {
4788
5148
  return createHash2("sha256").update(`${memoryId}:${type}:${content}`).digest("hex").slice(0, 32);
4789
5149
  }
4790
5150
  function cleanText(text) {
4791
- return text.replace(/```[\s\S]*?```/g, " ").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
5151
+ let cleaned = text.replace(
5152
+ /```(\w*)\n(.*?)(?:\n[\s\S]*?)```/g,
5153
+ (_m, lang, firstLine) => `[code${lang ? `:${lang}` : ""}] ${firstLine.trim()}`
5154
+ );
5155
+ cleaned = cleaned.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
5156
+ return cleaned;
4792
5157
  }
4793
- function splitSentences(text) {
4794
- return cleanText(text).split(/(?<=[.!?])\s+|\n+/).map((s) => s.trim()).filter((s) => s.length >= 24 && s.length <= MAX_SENTENCE_CHARS);
5158
+ function splitSegments(text) {
5159
+ const cleaned = cleanText(text);
5160
+ 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);
5161
+ if (segments.length === 0 && cleaned.length >= MIN_SEGMENT_CHARS) {
5162
+ const lines = cleaned.split(/\n+/).map((l) => l.trim()).filter((l) => l.length >= MIN_SEGMENT_CHARS && l.length <= MAX_SEGMENT_CHARS);
5163
+ if (lines.length > 0) return lines;
5164
+ if (cleaned.length >= MIN_SEGMENT_CHARS) {
5165
+ return [cleaned.slice(0, MAX_SEGMENT_CHARS)];
5166
+ }
5167
+ }
5168
+ return segments;
4795
5169
  }
4796
5170
  function inferCardType(sentence, toolName) {
4797
5171
  const lower = sentence.toLowerCase();
@@ -4823,12 +5197,12 @@ function predicateFor(type) {
4823
5197
  }
4824
5198
  }
4825
5199
  function extractMemoryCards(row) {
4826
- const sentences = splitSentences(row.raw_text);
5200
+ const segments = splitSegments(row.raw_text);
4827
5201
  const cards = [];
4828
- for (const sentence of sentences) {
5202
+ for (const sentence of segments) {
4829
5203
  const type = inferCardType(sentence, row.tool_name);
4830
5204
  const subject = extractSubject(sentence, row.agent_id);
4831
- const content = sentence.length > MAX_SENTENCE_CHARS ? `${sentence.slice(0, MAX_SENTENCE_CHARS - 1)}\u2026` : sentence;
5205
+ const content = sentence.length > MAX_SEGMENT_CHARS ? `${sentence.slice(0, MAX_SEGMENT_CHARS - 1)}\u2026` : sentence;
4832
5206
  cards.push({
4833
5207
  id: stableId(row.id, type, content),
4834
5208
  memory_id: row.id,
@@ -4924,13 +5298,14 @@ Source memory: ${String(row.source_ref ?? row.memory_id)}`,
4924
5298
  last_accessed: String(row.timestamp)
4925
5299
  }));
4926
5300
  }
4927
- var MAX_CARDS_PER_MEMORY, MAX_SENTENCE_CHARS;
5301
+ var MAX_CARDS_PER_MEMORY, MAX_SEGMENT_CHARS, MIN_SEGMENT_CHARS;
4928
5302
  var init_memory_cards = __esm({
4929
5303
  "src/lib/memory-cards.ts"() {
4930
5304
  "use strict";
4931
5305
  init_database();
4932
- MAX_CARDS_PER_MEMORY = 6;
4933
- MAX_SENTENCE_CHARS = 360;
5306
+ MAX_CARDS_PER_MEMORY = 8;
5307
+ MAX_SEGMENT_CHARS = 500;
5308
+ MIN_SEGMENT_CHARS = 20;
4934
5309
  }
4935
5310
  });
4936
5311
 
@@ -5866,13 +6241,13 @@ var init_store = __esm({
5866
6241
  });
5867
6242
 
5868
6243
  // src/lib/session-registry.ts
5869
- import path8 from "path";
6244
+ import path9 from "path";
5870
6245
  import os6 from "os";
5871
6246
  var REGISTRY_PATH;
5872
6247
  var init_session_registry = __esm({
5873
6248
  "src/lib/session-registry.ts"() {
5874
6249
  "use strict";
5875
- REGISTRY_PATH = path8.join(os6.homedir(), ".exe-os", "session-registry.json");
6250
+ REGISTRY_PATH = path9.join(os6.homedir(), ".exe-os", "session-registry.json");
5876
6251
  }
5877
6252
  });
5878
6253
 
@@ -6090,68 +6465,6 @@ var init_provider_table = __esm({
6090
6465
  }
6091
6466
  });
6092
6467
 
6093
- // src/lib/runtime-table.ts
6094
- var RUNTIME_TABLE, DEFAULT_RUNTIME;
6095
- var init_runtime_table = __esm({
6096
- "src/lib/runtime-table.ts"() {
6097
- "use strict";
6098
- RUNTIME_TABLE = {
6099
- codex: {
6100
- binary: "codex",
6101
- launchMode: "interactive",
6102
- autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
6103
- inlineFlag: "--no-alt-screen",
6104
- apiKeyEnv: "OPENAI_API_KEY",
6105
- defaultModel: "gpt-5.5"
6106
- },
6107
- opencode: {
6108
- binary: "opencode",
6109
- launchMode: "exec",
6110
- autoApproveFlag: "--dangerously-skip-permissions",
6111
- inlineFlag: "",
6112
- apiKeyEnv: "ANTHROPIC_API_KEY",
6113
- defaultModel: "anthropic/claude-sonnet-4-6"
6114
- }
6115
- };
6116
- DEFAULT_RUNTIME = "claude";
6117
- }
6118
- });
6119
-
6120
- // src/lib/agent-config.ts
6121
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync9 } from "fs";
6122
- import path9 from "path";
6123
- function loadAgentConfig() {
6124
- if (!existsSync9(AGENT_CONFIG_PATH)) return {};
6125
- try {
6126
- return JSON.parse(readFileSync5(AGENT_CONFIG_PATH, "utf-8"));
6127
- } catch {
6128
- return {};
6129
- }
6130
- }
6131
- function getAgentRuntime(agentId) {
6132
- const config = loadAgentConfig();
6133
- const entry = config[agentId];
6134
- if (entry) return entry;
6135
- const orgDefault = config["default"];
6136
- if (orgDefault) return orgDefault;
6137
- return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
6138
- }
6139
- var AGENT_CONFIG_PATH, DEFAULT_MODELS;
6140
- var init_agent_config = __esm({
6141
- "src/lib/agent-config.ts"() {
6142
- "use strict";
6143
- init_config();
6144
- init_runtime_table();
6145
- init_secure_files();
6146
- AGENT_CONFIG_PATH = path9.join(EXE_AI_DIR, "agent-config.json");
6147
- DEFAULT_MODELS = {
6148
- claude: "claude-opus-4.6",
6149
- codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
6150
- opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
6151
- };
6152
- }
6153
- });
6154
-
6155
6468
  // src/lib/intercom-queue.ts
6156
6469
  var intercom_queue_exports = {};
6157
6470
  __export(intercom_queue_exports, {
@@ -6646,7 +6959,7 @@ async function assertVpsLicense(opts) {
6646
6959
  }
6647
6960
  if (!transientFailure) {
6648
6961
  throw new Error(
6649
- "License validation failed: unknown backend state. Restore network connectivity to https://askexe.com/cloud and retry."
6962
+ "License validation failed: unknown backend state. Restore network connectivity to https://cloud.askexe.com and retry."
6650
6963
  );
6651
6964
  }
6652
6965
  const fresh = await getCachedLicense();
@@ -6683,7 +6996,7 @@ async function assertVpsLicense(opts) {
6683
6996
  } catch {
6684
6997
  }
6685
6998
  throw new Error(
6686
- `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.`
6999
+ `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.`
6687
7000
  );
6688
7001
  }
6689
7002
  function startLicenseRevalidation(intervalMs = 36e5) {
@@ -6715,7 +7028,7 @@ var init_license = __esm({
6715
7028
  LICENSE_PATH = path11.join(EXE_AI_DIR, "license.key");
6716
7029
  CACHE_PATH = path11.join(EXE_AI_DIR, "license-cache.json");
6717
7030
  DEVICE_ID_PATH = path11.join(EXE_AI_DIR, "device-id");
6718
- API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com/cloud";
7031
+ API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://cloud.askexe.com";
6719
7032
  RETRY_DELAY_MS = 500;
6720
7033
  LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
6721
7034
  MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
@@ -6794,6 +7107,18 @@ function extractRootExe(name) {
6794
7107
  const parts = name.split("-").filter(Boolean);
6795
7108
  return parts.length > 0 ? parts[parts.length - 1] : null;
6796
7109
  }
7110
+ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
7111
+ if (!existsSync14(SESSION_CACHE)) {
7112
+ mkdirSync7(SESSION_CACHE, { recursive: true });
7113
+ }
7114
+ const rootExe = extractRootExe(parentExe) ?? parentExe;
7115
+ const filePath = path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
7116
+ writeFileSync6(filePath, JSON.stringify({
7117
+ parentExe: rootExe,
7118
+ dispatchedBy: dispatchedBy || rootExe,
7119
+ registeredAt: (/* @__PURE__ */ new Date()).toISOString()
7120
+ }));
7121
+ }
6797
7122
  function getParentExe(sessionKey) {
6798
7123
  try {
6799
7124
  const data = JSON.parse(readFileSync9(path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
@@ -6814,11 +7139,12 @@ function getDispatchedBy(sessionKey) {
6814
7139
  }
6815
7140
  }
6816
7141
  function resolveExeSession() {
7142
+ if (process.env.EXE_SESSION_NAME) {
7143
+ const fromEnv = extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
7144
+ if (fromEnv) return fromEnv;
7145
+ }
6817
7146
  const mySession = getMySession();
6818
7147
  if (!mySession) {
6819
- if (process.env.EXE_SESSION_NAME) {
6820
- return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
6821
- }
6822
7148
  return null;
6823
7149
  }
6824
7150
  const fromSessionName = extractRootExe(mySession);
@@ -6833,6 +7159,10 @@ function resolveExeSession() {
6833
7159
  `[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
6834
7160
  `
6835
7161
  );
7162
+ try {
7163
+ registerParentExe(key, fromSessionName);
7164
+ } catch {
7165
+ }
6836
7166
  candidate = fromSessionName;
6837
7167
  } else {
6838
7168
  candidate = fromCache;
@@ -7235,6 +7565,19 @@ async function resolveTask(client, identifier, scopeSession) {
7235
7565
  args: [identifier, ...scope.args]
7236
7566
  });
7237
7567
  if (result.rows.length === 1) return result.rows[0];
7568
+ if (/^[a-f0-9]{7,12}$/i.test(identifier)) {
7569
+ result = await client.execute({
7570
+ sql: `SELECT * FROM tasks WHERE id LIKE ?`,
7571
+ args: [`${identifier}%`]
7572
+ });
7573
+ if (result.rows.length === 1) return result.rows[0];
7574
+ if (result.rows.length > 1) {
7575
+ const matches = result.rows.map((r) => `${String(r.id)} "${String(r.title)}" (${String(r.status)})`).join(", ");
7576
+ throw new Error(
7577
+ `Multiple tasks match short-ID "${identifier}": ${matches}. Use a longer prefix to disambiguate.`
7578
+ );
7579
+ }
7580
+ }
7238
7581
  result = await client.execute({
7239
7582
  sql: `SELECT * FROM tasks WHERE task_file LIKE ?${scope.sql}`,
7240
7583
  args: [`%${identifier}%`, ...scope.args]
@@ -7615,12 +7958,13 @@ async function cascadeUnblock(taskId, baseDir, now) {
7615
7958
  WHERE blocked_by = ? AND status = 'blocked'`,
7616
7959
  args: [now, taskId]
7617
7960
  });
7618
- if (baseDir && unblocked.rowsAffected > 0) {
7619
- const ubScope = sessionScopeFilter();
7620
- const unblockedRows = await client.execute({
7621
- sql: `SELECT task_file FROM tasks WHERE blocked_by IS NULL AND updated_at = ?${ubScope.sql}`,
7622
- args: [now, ...ubScope.args]
7623
- });
7961
+ if (unblocked.rowsAffected === 0) return;
7962
+ const ubScope = sessionScopeFilter();
7963
+ const unblockedRows = await client.execute({
7964
+ sql: `SELECT id, title, assigned_to, priority, task_file FROM tasks WHERE blocked_by IS NULL AND updated_at = ?${ubScope.sql}`,
7965
+ args: [now, ...ubScope.args]
7966
+ });
7967
+ if (baseDir) {
7624
7968
  for (const ur of unblockedRows.rows) {
7625
7969
  try {
7626
7970
  const ubFile = path18.join(baseDir, String(ur.task_file));
@@ -7632,6 +7976,19 @@ async function cascadeUnblock(taskId, baseDir, now) {
7632
7976
  }
7633
7977
  }
7634
7978
  }
7979
+ if (unblockedRows.rows.length > 0 && !process.env.VITEST) {
7980
+ try {
7981
+ const { queueIntercom: queueIntercom2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
7982
+ const dispatched = /* @__PURE__ */ new Set();
7983
+ for (const ur of unblockedRows.rows) {
7984
+ const assignee = String(ur.assigned_to);
7985
+ if (dispatched.has(assignee)) continue;
7986
+ dispatched.add(assignee);
7987
+ queueIntercom2(`${assignee}`, `unblocked: "${String(ur.title)}" is now ready`);
7988
+ }
7989
+ } catch {
7990
+ }
7991
+ }
7635
7992
  }
7636
7993
  async function findNextTask(assignedTo) {
7637
7994
  const client = getClient();
@@ -7792,6 +8149,15 @@ var init_embedder = __esm({
7792
8149
  // src/lib/behaviors.ts
7793
8150
  import crypto4 from "crypto";
7794
8151
  async function storeBehavior(opts) {
8152
+ try {
8153
+ const { loadEmployeesSync: loadEmployeesSync2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
8154
+ const roster = loadEmployeesSync2();
8155
+ if (roster.length > 0 && !roster.some((e) => e.name === opts.agentId)) {
8156
+ throw new Error(`Agent "${opts.agentId}" not found in roster. Cannot store behavior for unregistered agent.`);
8157
+ }
8158
+ } catch (e) {
8159
+ if (e instanceof Error && e.message.includes("not found in roster")) throw e;
8160
+ }
7795
8161
  const client = getClient();
7796
8162
  const id = crypto4.randomUUID();
7797
8163
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -8211,6 +8577,12 @@ async function updateTask(input) {
8211
8577
  }
8212
8578
  }
8213
8579
  }
8580
+ if (input.status === "cancelled") {
8581
+ try {
8582
+ await cascadeUnblock(taskId, input.baseDir, now);
8583
+ } catch {
8584
+ }
8585
+ }
8214
8586
  if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
8215
8587
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
8216
8588
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({