@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 path3 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 = path3.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 = path3.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 path3 from "path";
494
+ import path4 from "path";
338
495
  import os3 from "os";
339
496
  function normalizeRole(role) {
340
497
  return (role ?? "").trim().toLowerCase();
@@ -352,8 +509,26 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
352
509
  if (!agentName) return false;
353
510
  return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
354
511
  }
512
+ function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
513
+ return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
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
+ }
355
530
  async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
356
- if (!existsSync3(employeesPath)) {
531
+ if (!existsSync4(employeesPath)) {
357
532
  return [];
358
533
  }
359
534
  const raw = await readFile2(employeesPath, "utf-8");
@@ -363,10 +538,14 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
363
538
  return [];
364
539
  }
365
540
  }
541
+ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
542
+ await mkdir2(path4.dirname(employeesPath), { recursive: true });
543
+ await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
544
+ }
366
545
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
367
- if (!existsSync3(employeesPath)) return [];
546
+ if (!existsSync4(employeesPath)) return [];
368
547
  try {
369
- return JSON.parse(readFileSync2(employeesPath, "utf-8"));
548
+ return JSON.parse(readFileSync3(employeesPath, "utf-8"));
370
549
  } catch {
371
550
  return [];
372
551
  }
@@ -374,6 +553,19 @@ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
374
553
  function getEmployee(employees, name) {
375
554
  return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
376
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
+ }
377
569
  function baseAgentName(name, employees) {
378
570
  const match = name.match(/^([a-zA-Z]+)\d+$/);
379
571
  if (!match) return name;
@@ -388,16 +580,141 @@ function isMultiInstance(agentName, employees) {
388
580
  if (!emp) return false;
389
581
  return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
390
582
  }
391
- var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR;
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 = path4.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 = path4.join(os3.homedir(), ".exe-os", "identity");
647
+ const oldPath = path4.join(identityDir, `${oldName}.md`);
648
+ const newPath = path4.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 = path4.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 = path4.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;
392
708
  var init_employees = __esm({
393
709
  "src/lib/employees.ts"() {
394
710
  "use strict";
395
711
  init_config();
396
- EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
712
+ EMPLOYEES_PATH = path4.join(EXE_AI_DIR, "exe-employees.json");
397
713
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
398
714
  COORDINATOR_ROLE = "COO";
399
715
  MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
400
- IDENTITY_DIR = path3.join(EXE_AI_DIR, "identity");
716
+ IDENTITY_DIR = path4.join(EXE_AI_DIR, "identity");
717
+ TEAM_SECTION_RE = /^## Team\b.*$/m;
401
718
  }
402
719
  });
403
720
 
@@ -409,13 +726,13 @@ __export(session_registry_exports, {
409
726
  refreshSessionProject: () => refreshSessionProject,
410
727
  registerSession: () => registerSession
411
728
  });
412
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync4 } from "fs";
729
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as existsSync5 } from "fs";
413
730
  import { execSync as execSync2 } from "child_process";
414
- import path4 from "path";
731
+ import path5 from "path";
415
732
  import os4 from "os";
416
733
  function registerSession(entry) {
417
- const dir = path4.dirname(REGISTRY_PATH);
418
- if (!existsSync4(dir)) {
734
+ const dir = path5.dirname(REGISTRY_PATH);
735
+ if (!existsSync5(dir)) {
419
736
  mkdirSync2(dir, { recursive: true });
420
737
  }
421
738
  const sessions = listSessions();
@@ -425,7 +742,7 @@ function registerSession(entry) {
425
742
  } else {
426
743
  sessions.push(entry);
427
744
  }
428
- writeFileSync2(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
745
+ writeFileSync3(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
429
746
  }
430
747
  function refreshSessionProject(windowName, projectDir) {
431
748
  const sessions = listSessions();
@@ -433,13 +750,13 @@ function refreshSessionProject(windowName, projectDir) {
433
750
  if (!entry || entry.projectDir === projectDir) return;
434
751
  entry.projectDir = projectDir;
435
752
  try {
436
- writeFileSync2(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
753
+ writeFileSync3(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
437
754
  } catch {
438
755
  }
439
756
  }
440
757
  function listSessions() {
441
758
  try {
442
- const raw = readFileSync3(REGISTRY_PATH, "utf8");
759
+ const raw = readFileSync4(REGISTRY_PATH, "utf8");
443
760
  return JSON.parse(raw);
444
761
  } catch {
445
762
  return [];
@@ -460,7 +777,7 @@ function pruneStaleSessions() {
460
777
  const alive = sessions.filter((s) => liveSet.has(s.windowName));
461
778
  const pruned = sessions.length - alive.length;
462
779
  if (pruned > 0) {
463
- writeFileSync2(REGISTRY_PATH, JSON.stringify(alive, null, 2));
780
+ writeFileSync3(REGISTRY_PATH, JSON.stringify(alive, null, 2));
464
781
  }
465
782
  return pruned;
466
783
  }
@@ -468,7 +785,7 @@ var REGISTRY_PATH;
468
785
  var init_session_registry = __esm({
469
786
  "src/lib/session-registry.ts"() {
470
787
  "use strict";
471
- REGISTRY_PATH = path4.join(os4.homedir(), ".exe-os", "session-registry.json");
788
+ REGISTRY_PATH = path5.join(os4.homedir(), ".exe-os", "session-registry.json");
472
789
  }
473
790
  });
474
791
 
@@ -740,68 +1057,6 @@ var init_provider_table = __esm({
740
1057
  }
741
1058
  });
742
1059
 
743
- // src/lib/runtime-table.ts
744
- var RUNTIME_TABLE, DEFAULT_RUNTIME;
745
- var init_runtime_table = __esm({
746
- "src/lib/runtime-table.ts"() {
747
- "use strict";
748
- RUNTIME_TABLE = {
749
- codex: {
750
- binary: "codex",
751
- launchMode: "interactive",
752
- autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
753
- inlineFlag: "--no-alt-screen",
754
- apiKeyEnv: "OPENAI_API_KEY",
755
- defaultModel: "gpt-5.5"
756
- },
757
- opencode: {
758
- binary: "opencode",
759
- launchMode: "exec",
760
- autoApproveFlag: "--dangerously-skip-permissions",
761
- inlineFlag: "",
762
- apiKeyEnv: "ANTHROPIC_API_KEY",
763
- defaultModel: "anthropic/claude-sonnet-4-6"
764
- }
765
- };
766
- DEFAULT_RUNTIME = "claude";
767
- }
768
- });
769
-
770
- // src/lib/agent-config.ts
771
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync5 } from "fs";
772
- import path5 from "path";
773
- function loadAgentConfig() {
774
- if (!existsSync5(AGENT_CONFIG_PATH)) return {};
775
- try {
776
- return JSON.parse(readFileSync4(AGENT_CONFIG_PATH, "utf-8"));
777
- } catch {
778
- return {};
779
- }
780
- }
781
- function getAgentRuntime(agentId) {
782
- const config = loadAgentConfig();
783
- const entry = config[agentId];
784
- if (entry) return entry;
785
- const orgDefault = config["default"];
786
- if (orgDefault) return orgDefault;
787
- return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
788
- }
789
- var AGENT_CONFIG_PATH, DEFAULT_MODELS;
790
- var init_agent_config = __esm({
791
- "src/lib/agent-config.ts"() {
792
- "use strict";
793
- init_config();
794
- init_runtime_table();
795
- init_secure_files();
796
- AGENT_CONFIG_PATH = path5.join(EXE_AI_DIR, "agent-config.json");
797
- DEFAULT_MODELS = {
798
- claude: "claude-opus-4.6",
799
- codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
800
- opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
801
- };
802
- }
803
- });
804
-
805
1060
  // src/lib/intercom-queue.ts
806
1061
  var intercom_queue_exports = {};
807
1062
  __export(intercom_queue_exports, {
@@ -3818,6 +4073,22 @@ async function ensureSchema() {
3818
4073
  } catch (e) {
3819
4074
  logCatchDebug("migration", e);
3820
4075
  }
4076
+ try {
4077
+ await client.execute({
4078
+ sql: `ALTER TABLE memories ADD COLUMN visibility TEXT DEFAULT 'private'`,
4079
+ args: []
4080
+ });
4081
+ } catch (e) {
4082
+ logCatchDebug("migration", e);
4083
+ }
4084
+ try {
4085
+ await client.execute({
4086
+ sql: `ALTER TABLE memories ADD COLUMN strength REAL DEFAULT 1.0`,
4087
+ args: []
4088
+ });
4089
+ } catch (e) {
4090
+ logCatchDebug("migration", e);
4091
+ }
3821
4092
  }
3822
4093
  async function disposeDatabase() {
3823
4094
  if (_walCheckpointTimer) {
@@ -4236,7 +4507,7 @@ async function assertVpsLicense(opts) {
4236
4507
  }
4237
4508
  if (!transientFailure) {
4238
4509
  throw new Error(
4239
- "License validation failed: unknown backend state. Restore network connectivity to https://askexe.com/cloud and retry."
4510
+ "License validation failed: unknown backend state. Restore network connectivity to https://cloud.askexe.com and retry."
4240
4511
  );
4241
4512
  }
4242
4513
  const fresh = await getCachedLicense();
@@ -4273,7 +4544,7 @@ async function assertVpsLicense(opts) {
4273
4544
  } catch {
4274
4545
  }
4275
4546
  throw new Error(
4276
- `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.`
4547
+ `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.`
4277
4548
  );
4278
4549
  }
4279
4550
  function startLicenseRevalidation(intervalMs = 36e5) {
@@ -4305,7 +4576,7 @@ var init_license = __esm({
4305
4576
  LICENSE_PATH = path10.join(EXE_AI_DIR, "license.key");
4306
4577
  CACHE_PATH = path10.join(EXE_AI_DIR, "license-cache.json");
4307
4578
  DEVICE_ID_PATH = path10.join(EXE_AI_DIR, "device-id");
4308
- API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com/cloud";
4579
+ API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://cloud.askexe.com";
4309
4580
  RETRY_DELAY_MS = 500;
4310
4581
  LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
4311
4582
  MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
@@ -4849,6 +5120,19 @@ async function resolveTask(client, identifier, scopeSession) {
4849
5120
  args: [identifier, ...scope.args]
4850
5121
  });
4851
5122
  if (result.rows.length === 1) return result.rows[0];
5123
+ if (/^[a-f0-9]{7,12}$/i.test(identifier)) {
5124
+ result = await client.execute({
5125
+ sql: `SELECT * FROM tasks WHERE id LIKE ?`,
5126
+ args: [`${identifier}%`]
5127
+ });
5128
+ if (result.rows.length === 1) return result.rows[0];
5129
+ if (result.rows.length > 1) {
5130
+ const matches = result.rows.map((r) => `${String(r.id)} "${String(r.title)}" (${String(r.status)})`).join(", ");
5131
+ throw new Error(
5132
+ `Multiple tasks match short-ID "${identifier}": ${matches}. Use a longer prefix to disambiguate.`
5133
+ );
5134
+ }
5135
+ }
4852
5136
  result = await client.execute({
4853
5137
  sql: `SELECT * FROM tasks WHERE task_file LIKE ?${scope.sql}`,
4854
5138
  args: [`%${identifier}%`, ...scope.args]
@@ -5703,12 +5987,13 @@ async function cascadeUnblock(taskId, baseDir, now) {
5703
5987
  WHERE blocked_by = ? AND status = 'blocked'`,
5704
5988
  args: [now, taskId]
5705
5989
  });
5706
- if (baseDir && unblocked.rowsAffected > 0) {
5707
- const ubScope = sessionScopeFilter();
5708
- const unblockedRows = await client.execute({
5709
- sql: `SELECT task_file FROM tasks WHERE blocked_by IS NULL AND updated_at = ?${ubScope.sql}`,
5710
- args: [now, ...ubScope.args]
5711
- });
5990
+ if (unblocked.rowsAffected === 0) return;
5991
+ const ubScope = sessionScopeFilter();
5992
+ const unblockedRows = await client.execute({
5993
+ sql: `SELECT id, title, assigned_to, priority, task_file FROM tasks WHERE blocked_by IS NULL AND updated_at = ?${ubScope.sql}`,
5994
+ args: [now, ...ubScope.args]
5995
+ });
5996
+ if (baseDir) {
5712
5997
  for (const ur of unblockedRows.rows) {
5713
5998
  try {
5714
5999
  const ubFile = path17.join(baseDir, String(ur.task_file));
@@ -5720,6 +6005,19 @@ async function cascadeUnblock(taskId, baseDir, now) {
5720
6005
  }
5721
6006
  }
5722
6007
  }
6008
+ if (unblockedRows.rows.length > 0 && !process.env.VITEST) {
6009
+ try {
6010
+ const { queueIntercom: queueIntercom2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
6011
+ const dispatched = /* @__PURE__ */ new Set();
6012
+ for (const ur of unblockedRows.rows) {
6013
+ const assignee = String(ur.assigned_to);
6014
+ if (dispatched.has(assignee)) continue;
6015
+ dispatched.add(assignee);
6016
+ queueIntercom2(`${assignee}`, `unblocked: "${String(ur.title)}" is now ready`);
6017
+ }
6018
+ } catch {
6019
+ }
6020
+ }
5723
6021
  }
5724
6022
  async function findNextTask(assignedTo) {
5725
6023
  const client = getClient();
@@ -5936,6 +6234,15 @@ __export(behaviors_exports, {
5936
6234
  });
5937
6235
  import crypto5 from "crypto";
5938
6236
  async function storeBehavior(opts) {
6237
+ try {
6238
+ const { loadEmployeesSync: loadEmployeesSync2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
6239
+ const roster = loadEmployeesSync2();
6240
+ if (roster.length > 0 && !roster.some((e) => e.name === opts.agentId)) {
6241
+ throw new Error(`Agent "${opts.agentId}" not found in roster. Cannot store behavior for unregistered agent.`);
6242
+ }
6243
+ } catch (e) {
6244
+ if (e instanceof Error && e.message.includes("not found in roster")) throw e;
6245
+ }
5939
6246
  const client = getClient();
5940
6247
  const id = crypto5.randomUUID();
5941
6248
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -6459,6 +6766,12 @@ async function updateTask(input) {
6459
6766
  }
6460
6767
  }
6461
6768
  }
6769
+ if (input.status === "cancelled") {
6770
+ try {
6771
+ await cascadeUnblock(taskId, input.baseDir, now);
6772
+ } catch {
6773
+ }
6774
+ }
6462
6775
  if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
6463
6776
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
6464
6777
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
@@ -6990,11 +7303,12 @@ function getDispatchedBy(sessionKey) {
6990
7303
  }
6991
7304
  }
6992
7305
  function resolveExeSession() {
7306
+ if (process.env.EXE_SESSION_NAME) {
7307
+ const fromEnv = extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
7308
+ if (fromEnv) return fromEnv;
7309
+ }
6993
7310
  const mySession = getMySession();
6994
7311
  if (!mySession) {
6995
- if (process.env.EXE_SESSION_NAME) {
6996
- return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
6997
- }
6998
7312
  return null;
6999
7313
  }
7000
7314
  const fromSessionName = extractRootExe(mySession);
@@ -7009,6 +7323,10 @@ function resolveExeSession() {
7009
7323
  `[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
7010
7324
  `
7011
7325
  );
7326
+ try {
7327
+ registerParentExe(key, fromSessionName);
7328
+ } catch {
7329
+ }
7012
7330
  candidate = fromSessionName;
7013
7331
  } else {
7014
7332
  candidate = fromCache;
@@ -8703,11 +9021,17 @@ var init_platform_procedures = __esm({
8703
9021
  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."
8704
9022
  },
8705
9023
  {
8706
- title: "Customer orchestration maturity \u2014 recommend, never trap",
9024
+ title: "Orchestration phase guidance \u2014 recommend, never trap",
8707
9025
  domain: "workflow",
8708
9026
  priority: "p1",
8709
9027
  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."
8710
9028
  },
9029
+ {
9030
+ title: "Routing slot vs display title \u2014 internal 'coo' is plumbing, not your name",
9031
+ domain: "identity",
9032
+ priority: "p0",
9033
+ 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."
9034
+ },
8711
9035
  {
8712
9036
  title: "Single dispatch path \u2014 create_task only",
8713
9037
  domain: "workflow",
@@ -8741,6 +9065,12 @@ var init_platform_procedures = __esm({
8741
9065
  priority: "p0",
8742
9066
  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."
8743
9067
  },
9068
+ {
9069
+ title: "Destructive operations \u2014 mandatory reviewer gate",
9070
+ domain: "security",
9071
+ priority: "p0",
9072
+ 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."
9073
+ },
8744
9074
  {
8745
9075
  title: "Customer patch triage \u2014 upstream bug vs customization",
8746
9076
  domain: "support",
@@ -9026,10 +9356,24 @@ function stableId(memoryId, type, content) {
9026
9356
  return createHash2("sha256").update(`${memoryId}:${type}:${content}`).digest("hex").slice(0, 32);
9027
9357
  }
9028
9358
  function cleanText(text) {
9029
- return text.replace(/```[\s\S]*?```/g, " ").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
9359
+ let cleaned = text.replace(
9360
+ /```(\w*)\n(.*?)(?:\n[\s\S]*?)```/g,
9361
+ (_m, lang, firstLine) => `[code${lang ? `:${lang}` : ""}] ${firstLine.trim()}`
9362
+ );
9363
+ cleaned = cleaned.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
9364
+ return cleaned;
9030
9365
  }
9031
- function splitSentences(text) {
9032
- return cleanText(text).split(/(?<=[.!?])\s+|\n+/).map((s) => s.trim()).filter((s) => s.length >= 24 && s.length <= MAX_SENTENCE_CHARS);
9366
+ function splitSegments(text) {
9367
+ const cleaned = cleanText(text);
9368
+ 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);
9369
+ if (segments.length === 0 && cleaned.length >= MIN_SEGMENT_CHARS) {
9370
+ const lines = cleaned.split(/\n+/).map((l) => l.trim()).filter((l) => l.length >= MIN_SEGMENT_CHARS && l.length <= MAX_SEGMENT_CHARS);
9371
+ if (lines.length > 0) return lines;
9372
+ if (cleaned.length >= MIN_SEGMENT_CHARS) {
9373
+ return [cleaned.slice(0, MAX_SEGMENT_CHARS)];
9374
+ }
9375
+ }
9376
+ return segments;
9033
9377
  }
9034
9378
  function inferCardType(sentence, toolName) {
9035
9379
  const lower = sentence.toLowerCase();
@@ -9061,12 +9405,12 @@ function predicateFor(type) {
9061
9405
  }
9062
9406
  }
9063
9407
  function extractMemoryCards(row) {
9064
- const sentences = splitSentences(row.raw_text);
9408
+ const segments = splitSegments(row.raw_text);
9065
9409
  const cards = [];
9066
- for (const sentence of sentences) {
9410
+ for (const sentence of segments) {
9067
9411
  const type = inferCardType(sentence, row.tool_name);
9068
9412
  const subject = extractSubject(sentence, row.agent_id);
9069
- const content = sentence.length > MAX_SENTENCE_CHARS ? `${sentence.slice(0, MAX_SENTENCE_CHARS - 1)}\u2026` : sentence;
9413
+ const content = sentence.length > MAX_SEGMENT_CHARS ? `${sentence.slice(0, MAX_SEGMENT_CHARS - 1)}\u2026` : sentence;
9070
9414
  cards.push({
9071
9415
  id: stableId(row.id, type, content),
9072
9416
  memory_id: row.id,
@@ -9162,13 +9506,14 @@ Source memory: ${String(row.source_ref ?? row.memory_id)}`,
9162
9506
  last_accessed: String(row.timestamp)
9163
9507
  }));
9164
9508
  }
9165
- var MAX_CARDS_PER_MEMORY, MAX_SENTENCE_CHARS;
9509
+ var MAX_CARDS_PER_MEMORY, MAX_SEGMENT_CHARS, MIN_SEGMENT_CHARS;
9166
9510
  var init_memory_cards = __esm({
9167
9511
  "src/lib/memory-cards.ts"() {
9168
9512
  "use strict";
9169
9513
  init_database();
9170
- MAX_CARDS_PER_MEMORY = 6;
9171
- MAX_SENTENCE_CHARS = 360;
9514
+ MAX_CARDS_PER_MEMORY = 8;
9515
+ MAX_SEGMENT_CHARS = 500;
9516
+ MIN_SEGMENT_CHARS = 20;
9172
9517
  }
9173
9518
  });
9174
9519