@askexenow/exe-os 0.9.113 → 0.9.115

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 (86) hide show
  1. package/dist/bin/agentic-ontology-backfill.js +36 -12
  2. package/dist/bin/agentic-reflection-backfill.js +36 -12
  3. package/dist/bin/agentic-semantic-label.js +36 -12
  4. package/dist/bin/backfill-conversations.js +36 -12
  5. package/dist/bin/backfill-responses.js +36 -12
  6. package/dist/bin/backfill-vectors.js +36 -12
  7. package/dist/bin/bulk-sync-postgres.js +36 -12
  8. package/dist/bin/cleanup-stale-review-tasks.js +470 -113
  9. package/dist/bin/cli.js +413 -62
  10. package/dist/bin/exe-agent.js +27 -0
  11. package/dist/bin/exe-assign.js +36 -12
  12. package/dist/bin/exe-boot.js +246 -54
  13. package/dist/bin/exe-call.js +8 -0
  14. package/dist/bin/exe-cloud.js +47 -12
  15. package/dist/bin/exe-dispatch.js +348 -53
  16. package/dist/bin/exe-doctor.js +51 -13
  17. package/dist/bin/exe-export-behaviors.js +37 -12
  18. package/dist/bin/exe-forget.js +36 -12
  19. package/dist/bin/exe-gateway.js +348 -53
  20. package/dist/bin/exe-heartbeat.js +471 -113
  21. package/dist/bin/exe-kill.js +36 -12
  22. package/dist/bin/exe-launch-agent.js +117 -18
  23. package/dist/bin/exe-new-employee.js +9 -1
  24. package/dist/bin/exe-pending-messages.js +452 -95
  25. package/dist/bin/exe-pending-notifications.js +452 -95
  26. package/dist/bin/exe-pending-reviews.js +452 -95
  27. package/dist/bin/exe-rename.js +36 -12
  28. package/dist/bin/exe-review.js +36 -12
  29. package/dist/bin/exe-search.js +37 -12
  30. package/dist/bin/exe-session-cleanup.js +348 -53
  31. package/dist/bin/exe-settings.js +12 -0
  32. package/dist/bin/exe-start-codex.js +46 -13
  33. package/dist/bin/exe-start-opencode.js +46 -13
  34. package/dist/bin/exe-status.js +460 -114
  35. package/dist/bin/exe-support.js +12 -0
  36. package/dist/bin/exe-team.js +36 -12
  37. package/dist/bin/git-sweep.js +348 -53
  38. package/dist/bin/graph-backfill.js +36 -12
  39. package/dist/bin/graph-export.js +36 -12
  40. package/dist/bin/install.js +9 -1
  41. package/dist/bin/intercom-check.js +255 -53
  42. package/dist/bin/scan-tasks.js +348 -53
  43. package/dist/bin/setup.js +74 -12
  44. package/dist/bin/shard-migrate.js +36 -12
  45. package/dist/gateway/index.js +348 -53
  46. package/dist/hooks/bug-report-worker.js +348 -53
  47. package/dist/hooks/codex-stop-task-finalizer.js +308 -37
  48. package/dist/hooks/commit-complete.js +348 -53
  49. package/dist/hooks/error-recall.js +37 -12
  50. package/dist/hooks/ingest.js +363 -54
  51. package/dist/hooks/instructions-loaded.js +36 -12
  52. package/dist/hooks/notification.js +36 -12
  53. package/dist/hooks/post-compact.js +426 -72
  54. package/dist/hooks/post-tool-combined.js +501 -146
  55. package/dist/hooks/pre-compact.js +348 -53
  56. package/dist/hooks/pre-tool-use.js +92 -13
  57. package/dist/hooks/prompt-submit.js +348 -53
  58. package/dist/hooks/session-end.js +158 -53
  59. package/dist/hooks/session-start.js +66 -13
  60. package/dist/hooks/stop.js +420 -72
  61. package/dist/hooks/subagent-stop.js +419 -72
  62. package/dist/hooks/summary-worker.js +442 -121
  63. package/dist/index.js +375 -53
  64. package/dist/lib/agent-config.js +8 -0
  65. package/dist/lib/cloud-sync.js +35 -12
  66. package/dist/lib/config.js +13 -0
  67. package/dist/lib/consolidation.js +9 -1
  68. package/dist/lib/embedder.js +13 -0
  69. package/dist/lib/employees.js +8 -0
  70. package/dist/lib/exe-daemon.js +524 -60
  71. package/dist/lib/hybrid-search.js +37 -12
  72. package/dist/lib/keychain.js +25 -13
  73. package/dist/lib/messaging.js +395 -74
  74. package/dist/lib/schedules.js +36 -12
  75. package/dist/lib/skill-learning.js +21 -0
  76. package/dist/lib/store.js +36 -12
  77. package/dist/lib/tasks.js +324 -41
  78. package/dist/lib/tmux-routing.js +324 -41
  79. package/dist/mcp/server.js +374 -54
  80. package/dist/mcp/tools/create-task.js +324 -41
  81. package/dist/mcp/tools/list-tasks.js +406 -57
  82. package/dist/mcp/tools/send-message.js +395 -74
  83. package/dist/mcp/tools/update-task.js +324 -41
  84. package/dist/runtime/index.js +375 -53
  85. package/dist/tui/App.js +377 -55
  86. package/package.json +1 -1
@@ -139,6 +139,17 @@ function normalizeOrchestration(raw) {
139
139
  const userOrg = raw.orchestration ?? {};
140
140
  raw.orchestration = { ...defaultOrg, ...userOrg };
141
141
  }
142
+ function normalizeCloudEndpoint(raw) {
143
+ const cloud = raw.cloud;
144
+ if (!cloud?.endpoint) return;
145
+ const ep = String(cloud.endpoint);
146
+ if (ep === "https://askexe.com/cloud" || ep === "https://askexe.com/cloud/") {
147
+ cloud.endpoint = "https://cloud.askexe.com";
148
+ process.stderr.write(
149
+ "[config] Auto-migrated cloud endpoint: askexe.com/cloud \u2192 cloud.askexe.com\n"
150
+ );
151
+ }
152
+ }
142
153
  async function loadConfig() {
143
154
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
144
155
  await ensurePrivateDir(dir);
@@ -164,6 +175,7 @@ async function loadConfig() {
164
175
  normalizeSessionLifecycle(migratedCfg);
165
176
  normalizeAutoUpdate(migratedCfg);
166
177
  normalizeOrchestration(migratedCfg);
178
+ normalizeCloudEndpoint(migratedCfg);
167
179
  const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
168
180
  if (config.dbPath.startsWith("~")) {
169
181
  config.dbPath = config.dbPath.replace(/^~/, os.homedir());
@@ -192,6 +204,7 @@ function loadConfigSync() {
192
204
  normalizeSessionLifecycle(migratedCfg);
193
205
  normalizeAutoUpdate(migratedCfg);
194
206
  normalizeOrchestration(migratedCfg);
207
+ normalizeCloudEndpoint(migratedCfg);
195
208
  const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
196
209
  if (config.dbPath.startsWith("~")) {
197
210
  config.dbPath = config.dbPath.replace(/^~/, os.homedir());
@@ -361,11 +374,176 @@ var init_session_key = __esm({
361
374
  }
362
375
  });
363
376
 
377
+ // src/lib/runtime-table.ts
378
+ var RUNTIME_TABLE, DEFAULT_RUNTIME;
379
+ var init_runtime_table = __esm({
380
+ "src/lib/runtime-table.ts"() {
381
+ "use strict";
382
+ RUNTIME_TABLE = {
383
+ codex: {
384
+ binary: "codex",
385
+ launchMode: "interactive",
386
+ autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
387
+ inlineFlag: "--no-alt-screen",
388
+ apiKeyEnv: "OPENAI_API_KEY",
389
+ defaultModel: "gpt-5.5"
390
+ },
391
+ opencode: {
392
+ binary: "opencode",
393
+ launchMode: "exec",
394
+ autoApproveFlag: "--dangerously-skip-permissions",
395
+ inlineFlag: "",
396
+ apiKeyEnv: "ANTHROPIC_API_KEY",
397
+ defaultModel: "anthropic/claude-sonnet-4-6"
398
+ }
399
+ };
400
+ DEFAULT_RUNTIME = "claude";
401
+ }
402
+ });
403
+
404
+ // src/lib/agent-config.ts
405
+ var agent_config_exports = {};
406
+ __export(agent_config_exports, {
407
+ AGENT_CONFIG_PATH: () => AGENT_CONFIG_PATH,
408
+ DEFAULT_MODELS: () => DEFAULT_MODELS,
409
+ KNOWN_RUNTIMES: () => KNOWN_RUNTIMES,
410
+ RUNTIME_LABELS: () => RUNTIME_LABELS,
411
+ clearAgentRuntime: () => clearAgentRuntime,
412
+ getAgentRuntime: () => getAgentRuntime,
413
+ loadAgentConfig: () => loadAgentConfig,
414
+ normalizeCcModelName: () => normalizeCcModelName,
415
+ saveAgentConfig: () => saveAgentConfig,
416
+ setAgentMcps: () => setAgentMcps,
417
+ setAgentRuntime: () => setAgentRuntime
418
+ });
419
+ import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
420
+ import path2 from "path";
421
+ function loadAgentConfig() {
422
+ if (!existsSync3(AGENT_CONFIG_PATH)) return {};
423
+ try {
424
+ return JSON.parse(readFileSync2(AGENT_CONFIG_PATH, "utf-8"));
425
+ } catch {
426
+ return {};
427
+ }
428
+ }
429
+ function saveAgentConfig(config) {
430
+ const dir = path2.dirname(AGENT_CONFIG_PATH);
431
+ ensurePrivateDirSync(dir);
432
+ writeFileSync(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
433
+ enforcePrivateFileSync(AGENT_CONFIG_PATH);
434
+ }
435
+ function getAgentRuntime(agentId) {
436
+ const config = loadAgentConfig();
437
+ const entry = config[agentId];
438
+ if (entry) return entry;
439
+ const orgDefault = config["default"];
440
+ if (orgDefault) return orgDefault;
441
+ return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
442
+ }
443
+ function normalizeCcModelName(model) {
444
+ let ccModel = model.replace(/(\d+)\.(\d+)/g, "$1-$2");
445
+ if (/claude-(opus|sonnet)-4-[6-9]/.test(ccModel) && !ccModel.includes("[1m]")) {
446
+ ccModel += "[1m]";
447
+ }
448
+ return ccModel;
449
+ }
450
+ function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
451
+ const knownModels = KNOWN_RUNTIMES[runtime];
452
+ if (!knownModels) {
453
+ return {
454
+ ok: false,
455
+ error: `Unknown runtime "${runtime}". Valid: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`
456
+ };
457
+ }
458
+ if (!knownModels.includes(model)) {
459
+ return {
460
+ ok: false,
461
+ error: `Unknown model "${model}" for runtime "${runtime}". Valid: ${knownModels.join(", ")}`
462
+ };
463
+ }
464
+ const config = loadAgentConfig();
465
+ const existing = config[agentId];
466
+ const entry = { runtime, model };
467
+ if (reasoning_effort) entry.reasoning_effort = reasoning_effort;
468
+ if (mcps !== void 0) {
469
+ entry.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
470
+ } else if (existing?.mcps) {
471
+ entry.mcps = existing.mcps;
472
+ }
473
+ config[agentId] = entry;
474
+ saveAgentConfig(config);
475
+ return { ok: true };
476
+ }
477
+ function setAgentMcps(agentId, mcps) {
478
+ const config = loadAgentConfig();
479
+ const existing = config[agentId] ?? getAgentRuntime(agentId);
480
+ existing.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
481
+ config[agentId] = existing;
482
+ saveAgentConfig(config);
483
+ return { ok: true };
484
+ }
485
+ function clearAgentRuntime(agentId) {
486
+ const config = loadAgentConfig();
487
+ delete config[agentId];
488
+ saveAgentConfig(config);
489
+ }
490
+ var AGENT_CONFIG_PATH, KNOWN_RUNTIMES, RUNTIME_LABELS, DEFAULT_MODELS;
491
+ var init_agent_config = __esm({
492
+ "src/lib/agent-config.ts"() {
493
+ "use strict";
494
+ init_config();
495
+ init_runtime_table();
496
+ init_secure_files();
497
+ AGENT_CONFIG_PATH = path2.join(EXE_AI_DIR, "agent-config.json");
498
+ KNOWN_RUNTIMES = {
499
+ claude: ["claude-opus-4.6", "claude-opus-4", "claude-sonnet-4.6", "claude-sonnet-4", "claude-haiku-4.5"],
500
+ codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
501
+ opencode: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4", "google/gemini-2.5-pro", "deepseek/deepseek-r3", "minimax/minimax-m2.5"]
502
+ };
503
+ RUNTIME_LABELS = {
504
+ claude: "Claude Code (Anthropic)",
505
+ codex: "Codex (OpenAI)",
506
+ opencode: "OpenCode (open source)"
507
+ };
508
+ DEFAULT_MODELS = {
509
+ claude: "claude-opus-4.6",
510
+ codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
511
+ opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
512
+ };
513
+ }
514
+ });
515
+
364
516
  // src/lib/employees.ts
517
+ var employees_exports = {};
518
+ __export(employees_exports, {
519
+ COORDINATOR_ROLE: () => COORDINATOR_ROLE,
520
+ DEFAULT_COORDINATOR_TEMPLATE_NAME: () => DEFAULT_COORDINATOR_TEMPLATE_NAME,
521
+ EMPLOYEES_PATH: () => EMPLOYEES_PATH,
522
+ addEmployee: () => addEmployee,
523
+ baseAgentName: () => baseAgentName,
524
+ canCoordinate: () => canCoordinate,
525
+ getCoordinatorEmployee: () => getCoordinatorEmployee,
526
+ getCoordinatorName: () => getCoordinatorName,
527
+ getEmployee: () => getEmployee,
528
+ getEmployeeByRole: () => getEmployeeByRole,
529
+ getEmployeeNamesByRole: () => getEmployeeNamesByRole,
530
+ hasRole: () => hasRole,
531
+ hireEmployee: () => hireEmployee,
532
+ isCoordinatorName: () => isCoordinatorName,
533
+ isCoordinatorRole: () => isCoordinatorRole,
534
+ isMultiInstance: () => isMultiInstance,
535
+ loadEmployees: () => loadEmployees,
536
+ loadEmployeesSync: () => loadEmployeesSync,
537
+ normalizeRole: () => normalizeRole,
538
+ normalizeRosterCase: () => normalizeRosterCase,
539
+ registerBinSymlinks: () => registerBinSymlinks,
540
+ saveEmployees: () => saveEmployees,
541
+ validateEmployeeName: () => validateEmployeeName
542
+ });
365
543
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
366
- import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
544
+ import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
367
545
  import { execSync as execSync2 } from "child_process";
368
- import path2 from "path";
546
+ import path3 from "path";
369
547
  import os2 from "os";
370
548
  function normalizeRole(role) {
371
549
  return (role ?? "").trim().toLowerCase();
@@ -386,10 +564,40 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
386
564
  function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
387
565
  return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
388
566
  }
567
+ function validateEmployeeName(name) {
568
+ if (!name) {
569
+ return { valid: false, error: "Name is required" };
570
+ }
571
+ if (name.length > 32) {
572
+ return { valid: false, error: "Name must be 32 characters or fewer" };
573
+ }
574
+ if (!/^[a-z][a-z0-9]*$/.test(name)) {
575
+ return {
576
+ valid: false,
577
+ error: "Name must start with a letter and contain only lowercase alphanumeric characters"
578
+ };
579
+ }
580
+ return { valid: true };
581
+ }
582
+ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
583
+ if (!existsSync4(employeesPath)) {
584
+ return [];
585
+ }
586
+ const raw = await readFile2(employeesPath, "utf-8");
587
+ try {
588
+ return JSON.parse(raw);
589
+ } catch {
590
+ return [];
591
+ }
592
+ }
593
+ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
594
+ await mkdir2(path3.dirname(employeesPath), { recursive: true });
595
+ await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
596
+ }
389
597
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
390
- if (!existsSync3(employeesPath)) return [];
598
+ if (!existsSync4(employeesPath)) return [];
391
599
  try {
392
- return JSON.parse(readFileSync2(employeesPath, "utf-8"));
600
+ return JSON.parse(readFileSync3(employeesPath, "utf-8"));
393
601
  } catch {
394
602
  return [];
395
603
  }
@@ -397,26 +605,179 @@ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
397
605
  function getEmployee(employees, name) {
398
606
  return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
399
607
  }
400
- var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, IDENTITY_DIR;
608
+ function getEmployeeByRole(employees, role) {
609
+ const lower = role.toLowerCase();
610
+ return employees.find((e) => e.role.toLowerCase() === lower);
611
+ }
612
+ function getEmployeeNamesByRole(employees, role) {
613
+ const lower = role.toLowerCase();
614
+ return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
615
+ }
616
+ function hasRole(agentName, role) {
617
+ const employees = loadEmployeesSync();
618
+ const emp = getEmployee(employees, agentName);
619
+ return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
620
+ }
621
+ function baseAgentName(name, employees) {
622
+ const match = name.match(/^([a-zA-Z]+)\d+$/);
623
+ if (!match) return name;
624
+ const base = match[1];
625
+ const roster = employees ?? loadEmployeesSync();
626
+ if (getEmployee(roster, base)) return base;
627
+ return name;
628
+ }
629
+ function isMultiInstance(agentName, employees) {
630
+ const roster = employees ?? loadEmployeesSync();
631
+ const emp = getEmployee(roster, agentName);
632
+ if (!emp) return false;
633
+ return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
634
+ }
635
+ function addEmployee(employees, employee) {
636
+ const { systemPrompt: _legacyPrompt, ...rest } = employee;
637
+ const normalized = { ...rest, name: employee.name.toLowerCase() };
638
+ if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
639
+ throw new Error(`Employee '${normalized.name}' already exists`);
640
+ }
641
+ return [...employees, normalized];
642
+ }
643
+ function appendToCoordinatorTeam(employee) {
644
+ const coordinator = getCoordinatorEmployee(loadEmployeesSync());
645
+ if (!coordinator) return;
646
+ const idPath = path3.join(IDENTITY_DIR, `${coordinator.name}.md`);
647
+ if (!existsSync4(idPath)) return;
648
+ const content = readFileSync3(idPath, "utf-8");
649
+ if (content.includes(`**${capitalize(employee.name)}`)) return;
650
+ const teamMatch = content.match(TEAM_SECTION_RE);
651
+ if (!teamMatch || teamMatch.index === void 0) return;
652
+ const afterTeam = content.slice(teamMatch.index + teamMatch[0].length);
653
+ const nextHeading = afterTeam.match(/\n## /);
654
+ const entry = `
655
+ **${capitalize(employee.name)} (${employee.role}):** Newly hired. Update this description as the role develops.
656
+ `;
657
+ let updated;
658
+ if (nextHeading && nextHeading.index !== void 0) {
659
+ const insertAt = teamMatch.index + teamMatch[0].length + nextHeading.index;
660
+ updated = content.slice(0, insertAt) + entry + content.slice(insertAt);
661
+ } else {
662
+ updated = content.trimEnd() + "\n" + entry;
663
+ }
664
+ writeFileSync2(idPath, updated, "utf-8");
665
+ }
666
+ function capitalize(s) {
667
+ return s.charAt(0).toUpperCase() + s.slice(1);
668
+ }
669
+ async function hireEmployee(employee) {
670
+ const employees = await loadEmployees();
671
+ const updated = addEmployee(employees, employee);
672
+ await saveEmployees(updated);
673
+ try {
674
+ appendToCoordinatorTeam(employee);
675
+ } catch {
676
+ }
677
+ try {
678
+ const { loadAgentConfig: loadAgentConfig2, saveAgentConfig: saveAgentConfig2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
679
+ const config = loadAgentConfig2();
680
+ const name = employee.name.toLowerCase();
681
+ if (!config[name] && config["default"]) {
682
+ config[name] = { ...config["default"] };
683
+ saveAgentConfig2(config);
684
+ }
685
+ } catch {
686
+ }
687
+ return updated;
688
+ }
689
+ async function normalizeRosterCase(rosterPath) {
690
+ const employees = await loadEmployees(rosterPath);
691
+ let changed = false;
692
+ for (const emp of employees) {
693
+ if (emp.name !== emp.name.toLowerCase()) {
694
+ const oldName = emp.name;
695
+ emp.name = emp.name.toLowerCase();
696
+ changed = true;
697
+ try {
698
+ const identityDir = path3.join(os2.homedir(), ".exe-os", "identity");
699
+ const oldPath = path3.join(identityDir, `${oldName}.md`);
700
+ const newPath = path3.join(identityDir, `${emp.name}.md`);
701
+ if (existsSync4(oldPath) && !existsSync4(newPath)) {
702
+ renameSync2(oldPath, newPath);
703
+ } else if (existsSync4(oldPath) && oldPath !== newPath) {
704
+ const content = readFileSync3(oldPath, "utf-8");
705
+ writeFileSync2(newPath, content, "utf-8");
706
+ if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
707
+ unlinkSync(oldPath);
708
+ }
709
+ }
710
+ } catch {
711
+ }
712
+ }
713
+ }
714
+ if (changed) {
715
+ await saveEmployees(employees, rosterPath);
716
+ }
717
+ return changed;
718
+ }
719
+ function findExeBin() {
720
+ try {
721
+ return execSync2(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
722
+ } catch {
723
+ return null;
724
+ }
725
+ }
726
+ function registerBinSymlinks(name) {
727
+ const created = [];
728
+ const skipped = [];
729
+ const errors = [];
730
+ const exeBinPath = findExeBin();
731
+ if (!exeBinPath) {
732
+ errors.push("Could not find 'exe-os' in PATH");
733
+ return { created, skipped, errors };
734
+ }
735
+ const binDir = path3.dirname(exeBinPath);
736
+ let target;
737
+ try {
738
+ target = readlinkSync(exeBinPath);
739
+ } catch {
740
+ errors.push("Could not read 'exe' symlink");
741
+ return { created, skipped, errors };
742
+ }
743
+ for (const suffix of ["", "-opencode"]) {
744
+ const linkName = `${name}${suffix}`;
745
+ const linkPath = path3.join(binDir, linkName);
746
+ if (existsSync4(linkPath)) {
747
+ skipped.push(linkName);
748
+ continue;
749
+ }
750
+ try {
751
+ symlinkSync(target, linkPath);
752
+ created.push(linkName);
753
+ } catch (err) {
754
+ errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
755
+ }
756
+ }
757
+ return { created, skipped, errors };
758
+ }
759
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR, TEAM_SECTION_RE;
401
760
  var init_employees = __esm({
402
761
  "src/lib/employees.ts"() {
403
762
  "use strict";
404
763
  init_config();
405
- EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
764
+ EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
406
765
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
407
766
  COORDINATOR_ROLE = "COO";
408
- IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
767
+ MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
768
+ IDENTITY_DIR = path3.join(EXE_AI_DIR, "identity");
769
+ TEAM_SECTION_RE = /^## Team\b.*$/m;
409
770
  }
410
771
  });
411
772
 
412
773
  // src/lib/session-registry.ts
413
- import path4 from "path";
774
+ import path5 from "path";
414
775
  import os3 from "os";
415
776
  var REGISTRY_PATH;
416
777
  var init_session_registry = __esm({
417
778
  "src/lib/session-registry.ts"() {
418
779
  "use strict";
419
- REGISTRY_PATH = path4.join(os3.homedir(), ".exe-os", "session-registry.json");
780
+ REGISTRY_PATH = path5.join(os3.homedir(), ".exe-os", "session-registry.json");
420
781
  }
421
782
  });
422
783
 
@@ -566,51 +927,6 @@ var init_provider_table = __esm({
566
927
  }
567
928
  });
568
929
 
569
- // src/lib/runtime-table.ts
570
- var RUNTIME_TABLE;
571
- var init_runtime_table = __esm({
572
- "src/lib/runtime-table.ts"() {
573
- "use strict";
574
- RUNTIME_TABLE = {
575
- codex: {
576
- binary: "codex",
577
- launchMode: "interactive",
578
- autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
579
- inlineFlag: "--no-alt-screen",
580
- apiKeyEnv: "OPENAI_API_KEY",
581
- defaultModel: "gpt-5.5"
582
- },
583
- opencode: {
584
- binary: "opencode",
585
- launchMode: "exec",
586
- autoApproveFlag: "--dangerously-skip-permissions",
587
- inlineFlag: "",
588
- apiKeyEnv: "ANTHROPIC_API_KEY",
589
- defaultModel: "anthropic/claude-sonnet-4-6"
590
- }
591
- };
592
- }
593
- });
594
-
595
- // src/lib/agent-config.ts
596
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync4 } from "fs";
597
- import path5 from "path";
598
- var AGENT_CONFIG_PATH, DEFAULT_MODELS;
599
- var init_agent_config = __esm({
600
- "src/lib/agent-config.ts"() {
601
- "use strict";
602
- init_config();
603
- init_runtime_table();
604
- init_secure_files();
605
- AGENT_CONFIG_PATH = path5.join(EXE_AI_DIR, "agent-config.json");
606
- DEFAULT_MODELS = {
607
- claude: "claude-opus-4.6",
608
- codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
609
- opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
610
- };
611
- }
612
- });
613
-
614
930
  // src/lib/intercom-queue.ts
615
931
  import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
616
932
  import path6 from "path";
@@ -3713,6 +4029,21 @@ function isRootSession(name) {
3713
4029
  function extractRootExe(name) {
3714
4030
  if (!name) return null;
3715
4031
  if (!name.includes("-")) return name;
4032
+ try {
4033
+ const roster = (init_employees(), __toCommonJS(employees_exports)).loadEmployeesSync();
4034
+ if (roster.length > 0) {
4035
+ const sortedNames = roster.map((e) => e.name).sort((a, b) => b.length - a.length);
4036
+ for (const agentName of sortedNames) {
4037
+ const escaped = agentName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4038
+ const regex = new RegExp(`^${escaped}\\d*-(.+)$`);
4039
+ const match = name.match(regex);
4040
+ if (match) {
4041
+ return extractRootExe(match[1]);
4042
+ }
4043
+ }
4044
+ }
4045
+ } catch {
4046
+ }
3716
4047
  const parts = name.split("-").filter(Boolean);
3717
4048
  return parts.length > 0 ? parts[parts.length - 1] : null;
3718
4049
  }
@@ -3731,6 +4062,10 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
3731
4062
  function getParentExe(sessionKey) {
3732
4063
  try {
3733
4064
  const data = JSON.parse(readFileSync10(path13.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
4065
+ if (data.registeredAt) {
4066
+ const age = Date.now() - new Date(data.registeredAt).getTime();
4067
+ if (age > PARENT_EXE_CACHE_TTL_MS) return null;
4068
+ }
3734
4069
  return data.parentExe || null;
3735
4070
  } catch {
3736
4071
  return null;
@@ -3803,7 +4138,7 @@ function resolveExeSession() {
3803
4138
  }
3804
4139
  return candidate;
3805
4140
  }
3806
- var SPAWN_LOCK_DIR, SESSION_CACHE, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS;
4141
+ var SPAWN_LOCK_DIR, SESSION_CACHE, PARENT_EXE_CACHE_TTL_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS;
3807
4142
  var init_tmux_routing = __esm({
3808
4143
  "src/lib/tmux-routing.ts"() {
3809
4144
  "use strict";
@@ -3821,6 +4156,7 @@ var init_tmux_routing = __esm({
3821
4156
  init_agent_symlinks();
3822
4157
  SPAWN_LOCK_DIR = path13.join(os9.homedir(), ".exe-os", "spawn-locks");
3823
4158
  SESSION_CACHE = path13.join(os9.homedir(), ".exe-os", "session-cache");
4159
+ PARENT_EXE_CACHE_TTL_MS = 4 * 60 * 60 * 1e3;
3824
4160
  INTERCOM_LOG2 = path13.join(os9.homedir(), ".exe-os", "intercom.log");
3825
4161
  DEBOUNCE_FILE = path13.join(SESSION_CACHE, "intercom-debounce.json");
3826
4162
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
@@ -3861,7 +4197,7 @@ var init_task_scope = __esm({
3861
4197
  });
3862
4198
 
3863
4199
  // src/lib/keychain.ts
3864
- import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
4200
+ import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2, rename, copyFile } from "fs/promises";
3865
4201
  import { existsSync as existsSync13, statSync as statSync3 } from "fs";
3866
4202
  import { execSync as execSync6 } from "child_process";
3867
4203
  import path14 from "path";
@@ -3896,12 +4232,14 @@ function linuxSecretAvailable() {
3896
4232
  function isRootOnlyTrustedServerKeyFile(keyPath) {
3897
4233
  if (process.platform !== "linux") return false;
3898
4234
  try {
3899
- const uid = typeof os10.userInfo().uid === "number" ? os10.userInfo().uid : -1;
3900
4235
  const st = statSync3(keyPath);
3901
4236
  if (!st.isFile() || (st.mode & 63) !== 0) return false;
4237
+ const uid = typeof os10.userInfo().uid === "number" ? os10.userInfo().uid : -1;
3902
4238
  if (uid === 0) return true;
3903
4239
  const exeOsDir = process.env.EXE_OS_DIR;
3904
- return Boolean(exeOsDir && path14.resolve(keyPath).startsWith(path14.resolve(exeOsDir) + path14.sep));
4240
+ if (exeOsDir && path14.resolve(keyPath).startsWith(path14.resolve(exeOsDir) + path14.sep)) return true;
4241
+ if (!linuxSecretAvailable()) return true;
4242
+ return false;
3905
4243
  } catch {
3906
4244
  return false;
3907
4245
  }
@@ -4051,15 +4389,25 @@ async function writeMachineBoundFileFallback(b64) {
4051
4389
  await mkdir3(dir, { recursive: true });
4052
4390
  const keyPath = getKeyPath();
4053
4391
  const machineKey = deriveMachineKey();
4054
- if (machineKey) {
4055
- const encrypted = encryptWithMachineKey(b64, machineKey);
4056
- await writeFile3(keyPath, encrypted + "\n", "utf-8");
4057
- await chmod2(keyPath, 384);
4058
- return "encrypted";
4392
+ const content = machineKey ? encryptWithMachineKey(b64, machineKey) + "\n" : b64 + "\n";
4393
+ const result = machineKey ? "encrypted" : "plaintext";
4394
+ const tmpPath = keyPath + ".tmp";
4395
+ try {
4396
+ if (existsSync13(keyPath)) {
4397
+ await copyFile(keyPath, keyPath + ".bak").catch(() => {
4398
+ });
4399
+ }
4400
+ await writeFile3(tmpPath, content, "utf-8");
4401
+ await chmod2(tmpPath, 384);
4402
+ await rename(tmpPath, keyPath);
4403
+ } catch (err) {
4404
+ try {
4405
+ await unlink(tmpPath);
4406
+ } catch {
4407
+ }
4408
+ throw err;
4059
4409
  }
4060
- await writeFile3(keyPath, b64 + "\n", "utf-8");
4061
- await chmod2(keyPath, 384);
4062
- return "plaintext";
4410
+ return result;
4063
4411
  }
4064
4412
  async function getMasterKey() {
4065
4413
  let nativeValue = macKeychainGet() ?? linuxSecretGet();
@@ -4126,7 +4474,7 @@ async function getMasterKey() {
4126
4474
  b64Value = content;
4127
4475
  }
4128
4476
  const key = Buffer.from(b64Value, "base64");
4129
- if (!content.startsWith(ENCRYPTED_PREFIX) && isRootOnlyTrustedServerKeyFile(keyPath)) {
4477
+ if (isRootOnlyTrustedServerKeyFile(keyPath)) {
4130
4478
  return key;
4131
4479
  }
4132
4480
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
@@ -6685,9 +7033,9 @@ import { fileURLToPath as fileURLToPath3 } from "url";
6685
7033
  // src/lib/active-agent.ts
6686
7034
  init_config();
6687
7035
  init_session_key();
6688
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2, readdirSync } from "fs";
7036
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2, readdirSync } from "fs";
6689
7037
  import { execSync as execSync3 } from "child_process";
6690
- import path3 from "path";
7038
+ import path4 from "path";
6691
7039
 
6692
7040
  // src/mcp/agent-context.ts
6693
7041
  import { AsyncLocalStorage } from "async_hooks";
@@ -6698,7 +7046,7 @@ function getAgentContext() {
6698
7046
 
6699
7047
  // src/lib/active-agent.ts
6700
7048
  init_employees();
6701
- var CACHE_DIR = path3.join(EXE_AI_DIR, "session-cache");
7049
+ var CACHE_DIR = path4.join(EXE_AI_DIR, "session-cache");
6702
7050
  var STALE_MS = 24 * 60 * 60 * 1e3;
6703
7051
  function isNameWithOptionalInstance(candidate, baseName) {
6704
7052
  if (candidate === baseName) return true;
@@ -6743,14 +7091,14 @@ function resolveActiveAgentFromTmuxSession(sessionName) {
6743
7091
  return null;
6744
7092
  }
6745
7093
  function getMarkerPath() {
6746
- return path3.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
7094
+ return path4.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
6747
7095
  }
6748
7096
  function getActiveAgent() {
6749
7097
  const httpCtx = getAgentContext();
6750
7098
  if (httpCtx) return httpCtx;
6751
7099
  try {
6752
7100
  const markerPath = getMarkerPath();
6753
- const raw = readFileSync3(markerPath, "utf8");
7101
+ const raw = readFileSync4(markerPath, "utf8");
6754
7102
  const data = JSON.parse(raw);
6755
7103
  if (data.agentId) {
6756
7104
  if (data.startedAt) {