@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());
@@ -333,11 +345,176 @@ var init_session_key = __esm({
333
345
  }
334
346
  });
335
347
 
348
+ // src/lib/runtime-table.ts
349
+ var RUNTIME_TABLE, DEFAULT_RUNTIME;
350
+ var init_runtime_table = __esm({
351
+ "src/lib/runtime-table.ts"() {
352
+ "use strict";
353
+ RUNTIME_TABLE = {
354
+ codex: {
355
+ binary: "codex",
356
+ launchMode: "interactive",
357
+ autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
358
+ inlineFlag: "--no-alt-screen",
359
+ apiKeyEnv: "OPENAI_API_KEY",
360
+ defaultModel: "gpt-5.5"
361
+ },
362
+ opencode: {
363
+ binary: "opencode",
364
+ launchMode: "exec",
365
+ autoApproveFlag: "--dangerously-skip-permissions",
366
+ inlineFlag: "",
367
+ apiKeyEnv: "ANTHROPIC_API_KEY",
368
+ defaultModel: "anthropic/claude-sonnet-4-6"
369
+ }
370
+ };
371
+ DEFAULT_RUNTIME = "claude";
372
+ }
373
+ });
374
+
375
+ // src/lib/agent-config.ts
376
+ var agent_config_exports = {};
377
+ __export(agent_config_exports, {
378
+ AGENT_CONFIG_PATH: () => AGENT_CONFIG_PATH,
379
+ DEFAULT_MODELS: () => DEFAULT_MODELS,
380
+ KNOWN_RUNTIMES: () => KNOWN_RUNTIMES,
381
+ RUNTIME_LABELS: () => RUNTIME_LABELS,
382
+ clearAgentRuntime: () => clearAgentRuntime,
383
+ getAgentRuntime: () => getAgentRuntime,
384
+ loadAgentConfig: () => loadAgentConfig,
385
+ normalizeCcModelName: () => normalizeCcModelName,
386
+ saveAgentConfig: () => saveAgentConfig,
387
+ setAgentMcps: () => setAgentMcps,
388
+ setAgentRuntime: () => setAgentRuntime
389
+ });
390
+ import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
391
+ import path2 from "path";
392
+ function loadAgentConfig() {
393
+ if (!existsSync3(AGENT_CONFIG_PATH)) return {};
394
+ try {
395
+ return JSON.parse(readFileSync2(AGENT_CONFIG_PATH, "utf-8"));
396
+ } catch {
397
+ return {};
398
+ }
399
+ }
400
+ function saveAgentConfig(config) {
401
+ const dir = path2.dirname(AGENT_CONFIG_PATH);
402
+ ensurePrivateDirSync(dir);
403
+ writeFileSync(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
404
+ enforcePrivateFileSync(AGENT_CONFIG_PATH);
405
+ }
406
+ function getAgentRuntime(agentId) {
407
+ const config = loadAgentConfig();
408
+ const entry = config[agentId];
409
+ if (entry) return entry;
410
+ const orgDefault = config["default"];
411
+ if (orgDefault) return orgDefault;
412
+ return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
413
+ }
414
+ function normalizeCcModelName(model) {
415
+ let ccModel = model.replace(/(\d+)\.(\d+)/g, "$1-$2");
416
+ if (/claude-(opus|sonnet)-4-[6-9]/.test(ccModel) && !ccModel.includes("[1m]")) {
417
+ ccModel += "[1m]";
418
+ }
419
+ return ccModel;
420
+ }
421
+ function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
422
+ const knownModels = KNOWN_RUNTIMES[runtime];
423
+ if (!knownModels) {
424
+ return {
425
+ ok: false,
426
+ error: `Unknown runtime "${runtime}". Valid: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`
427
+ };
428
+ }
429
+ if (!knownModels.includes(model)) {
430
+ return {
431
+ ok: false,
432
+ error: `Unknown model "${model}" for runtime "${runtime}". Valid: ${knownModels.join(", ")}`
433
+ };
434
+ }
435
+ const config = loadAgentConfig();
436
+ const existing = config[agentId];
437
+ const entry = { runtime, model };
438
+ if (reasoning_effort) entry.reasoning_effort = reasoning_effort;
439
+ if (mcps !== void 0) {
440
+ entry.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
441
+ } else if (existing?.mcps) {
442
+ entry.mcps = existing.mcps;
443
+ }
444
+ config[agentId] = entry;
445
+ saveAgentConfig(config);
446
+ return { ok: true };
447
+ }
448
+ function setAgentMcps(agentId, mcps) {
449
+ const config = loadAgentConfig();
450
+ const existing = config[agentId] ?? getAgentRuntime(agentId);
451
+ existing.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
452
+ config[agentId] = existing;
453
+ saveAgentConfig(config);
454
+ return { ok: true };
455
+ }
456
+ function clearAgentRuntime(agentId) {
457
+ const config = loadAgentConfig();
458
+ delete config[agentId];
459
+ saveAgentConfig(config);
460
+ }
461
+ var AGENT_CONFIG_PATH, KNOWN_RUNTIMES, RUNTIME_LABELS, DEFAULT_MODELS;
462
+ var init_agent_config = __esm({
463
+ "src/lib/agent-config.ts"() {
464
+ "use strict";
465
+ init_config();
466
+ init_runtime_table();
467
+ init_secure_files();
468
+ AGENT_CONFIG_PATH = path2.join(EXE_AI_DIR, "agent-config.json");
469
+ KNOWN_RUNTIMES = {
470
+ claude: ["claude-opus-4.6", "claude-opus-4", "claude-sonnet-4.6", "claude-sonnet-4", "claude-haiku-4.5"],
471
+ codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
472
+ opencode: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4", "google/gemini-2.5-pro", "deepseek/deepseek-r3", "minimax/minimax-m2.5"]
473
+ };
474
+ RUNTIME_LABELS = {
475
+ claude: "Claude Code (Anthropic)",
476
+ codex: "Codex (OpenAI)",
477
+ opencode: "OpenCode (open source)"
478
+ };
479
+ DEFAULT_MODELS = {
480
+ claude: "claude-opus-4.6",
481
+ codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
482
+ opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
483
+ };
484
+ }
485
+ });
486
+
336
487
  // src/lib/employees.ts
488
+ var employees_exports = {};
489
+ __export(employees_exports, {
490
+ COORDINATOR_ROLE: () => COORDINATOR_ROLE,
491
+ DEFAULT_COORDINATOR_TEMPLATE_NAME: () => DEFAULT_COORDINATOR_TEMPLATE_NAME,
492
+ EMPLOYEES_PATH: () => EMPLOYEES_PATH,
493
+ addEmployee: () => addEmployee,
494
+ baseAgentName: () => baseAgentName,
495
+ canCoordinate: () => canCoordinate,
496
+ getCoordinatorEmployee: () => getCoordinatorEmployee,
497
+ getCoordinatorName: () => getCoordinatorName,
498
+ getEmployee: () => getEmployee,
499
+ getEmployeeByRole: () => getEmployeeByRole,
500
+ getEmployeeNamesByRole: () => getEmployeeNamesByRole,
501
+ hasRole: () => hasRole,
502
+ hireEmployee: () => hireEmployee,
503
+ isCoordinatorName: () => isCoordinatorName,
504
+ isCoordinatorRole: () => isCoordinatorRole,
505
+ isMultiInstance: () => isMultiInstance,
506
+ loadEmployees: () => loadEmployees,
507
+ loadEmployeesSync: () => loadEmployeesSync,
508
+ normalizeRole: () => normalizeRole,
509
+ normalizeRosterCase: () => normalizeRosterCase,
510
+ registerBinSymlinks: () => registerBinSymlinks,
511
+ saveEmployees: () => saveEmployees,
512
+ validateEmployeeName: () => validateEmployeeName
513
+ });
337
514
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
338
- import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
515
+ import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
339
516
  import { execSync as execSync2 } from "child_process";
340
- import path2 from "path";
517
+ import path3 from "path";
341
518
  import os2 from "os";
342
519
  function normalizeRole(role) {
343
520
  return (role ?? "").trim().toLowerCase();
@@ -358,10 +535,40 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
358
535
  function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
359
536
  return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
360
537
  }
538
+ function validateEmployeeName(name) {
539
+ if (!name) {
540
+ return { valid: false, error: "Name is required" };
541
+ }
542
+ if (name.length > 32) {
543
+ return { valid: false, error: "Name must be 32 characters or fewer" };
544
+ }
545
+ if (!/^[a-z][a-z0-9]*$/.test(name)) {
546
+ return {
547
+ valid: false,
548
+ error: "Name must start with a letter and contain only lowercase alphanumeric characters"
549
+ };
550
+ }
551
+ return { valid: true };
552
+ }
553
+ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
554
+ if (!existsSync4(employeesPath)) {
555
+ return [];
556
+ }
557
+ const raw = await readFile2(employeesPath, "utf-8");
558
+ try {
559
+ return JSON.parse(raw);
560
+ } catch {
561
+ return [];
562
+ }
563
+ }
564
+ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
565
+ await mkdir2(path3.dirname(employeesPath), { recursive: true });
566
+ await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
567
+ }
361
568
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
362
- if (!existsSync3(employeesPath)) return [];
569
+ if (!existsSync4(employeesPath)) return [];
363
570
  try {
364
- return JSON.parse(readFileSync2(employeesPath, "utf-8"));
571
+ return JSON.parse(readFileSync3(employeesPath, "utf-8"));
365
572
  } catch {
366
573
  return [];
367
574
  }
@@ -369,26 +576,179 @@ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
369
576
  function getEmployee(employees, name) {
370
577
  return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
371
578
  }
372
- var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, IDENTITY_DIR;
579
+ function getEmployeeByRole(employees, role) {
580
+ const lower = role.toLowerCase();
581
+ return employees.find((e) => e.role.toLowerCase() === lower);
582
+ }
583
+ function getEmployeeNamesByRole(employees, role) {
584
+ const lower = role.toLowerCase();
585
+ return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
586
+ }
587
+ function hasRole(agentName, role) {
588
+ const employees = loadEmployeesSync();
589
+ const emp = getEmployee(employees, agentName);
590
+ return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
591
+ }
592
+ function baseAgentName(name, employees) {
593
+ const match = name.match(/^([a-zA-Z]+)\d+$/);
594
+ if (!match) return name;
595
+ const base = match[1];
596
+ const roster = employees ?? loadEmployeesSync();
597
+ if (getEmployee(roster, base)) return base;
598
+ return name;
599
+ }
600
+ function isMultiInstance(agentName, employees) {
601
+ const roster = employees ?? loadEmployeesSync();
602
+ const emp = getEmployee(roster, agentName);
603
+ if (!emp) return false;
604
+ return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
605
+ }
606
+ function addEmployee(employees, employee) {
607
+ const { systemPrompt: _legacyPrompt, ...rest } = employee;
608
+ const normalized = { ...rest, name: employee.name.toLowerCase() };
609
+ if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
610
+ throw new Error(`Employee '${normalized.name}' already exists`);
611
+ }
612
+ return [...employees, normalized];
613
+ }
614
+ function appendToCoordinatorTeam(employee) {
615
+ const coordinator = getCoordinatorEmployee(loadEmployeesSync());
616
+ if (!coordinator) return;
617
+ const idPath = path3.join(IDENTITY_DIR, `${coordinator.name}.md`);
618
+ if (!existsSync4(idPath)) return;
619
+ const content = readFileSync3(idPath, "utf-8");
620
+ if (content.includes(`**${capitalize(employee.name)}`)) return;
621
+ const teamMatch = content.match(TEAM_SECTION_RE);
622
+ if (!teamMatch || teamMatch.index === void 0) return;
623
+ const afterTeam = content.slice(teamMatch.index + teamMatch[0].length);
624
+ const nextHeading = afterTeam.match(/\n## /);
625
+ const entry = `
626
+ **${capitalize(employee.name)} (${employee.role}):** Newly hired. Update this description as the role develops.
627
+ `;
628
+ let updated;
629
+ if (nextHeading && nextHeading.index !== void 0) {
630
+ const insertAt = teamMatch.index + teamMatch[0].length + nextHeading.index;
631
+ updated = content.slice(0, insertAt) + entry + content.slice(insertAt);
632
+ } else {
633
+ updated = content.trimEnd() + "\n" + entry;
634
+ }
635
+ writeFileSync2(idPath, updated, "utf-8");
636
+ }
637
+ function capitalize(s) {
638
+ return s.charAt(0).toUpperCase() + s.slice(1);
639
+ }
640
+ async function hireEmployee(employee) {
641
+ const employees = await loadEmployees();
642
+ const updated = addEmployee(employees, employee);
643
+ await saveEmployees(updated);
644
+ try {
645
+ appendToCoordinatorTeam(employee);
646
+ } catch {
647
+ }
648
+ try {
649
+ const { loadAgentConfig: loadAgentConfig2, saveAgentConfig: saveAgentConfig2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
650
+ const config = loadAgentConfig2();
651
+ const name = employee.name.toLowerCase();
652
+ if (!config[name] && config["default"]) {
653
+ config[name] = { ...config["default"] };
654
+ saveAgentConfig2(config);
655
+ }
656
+ } catch {
657
+ }
658
+ return updated;
659
+ }
660
+ async function normalizeRosterCase(rosterPath) {
661
+ const employees = await loadEmployees(rosterPath);
662
+ let changed = false;
663
+ for (const emp of employees) {
664
+ if (emp.name !== emp.name.toLowerCase()) {
665
+ const oldName = emp.name;
666
+ emp.name = emp.name.toLowerCase();
667
+ changed = true;
668
+ try {
669
+ const identityDir = path3.join(os2.homedir(), ".exe-os", "identity");
670
+ const oldPath = path3.join(identityDir, `${oldName}.md`);
671
+ const newPath = path3.join(identityDir, `${emp.name}.md`);
672
+ if (existsSync4(oldPath) && !existsSync4(newPath)) {
673
+ renameSync2(oldPath, newPath);
674
+ } else if (existsSync4(oldPath) && oldPath !== newPath) {
675
+ const content = readFileSync3(oldPath, "utf-8");
676
+ writeFileSync2(newPath, content, "utf-8");
677
+ if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
678
+ unlinkSync(oldPath);
679
+ }
680
+ }
681
+ } catch {
682
+ }
683
+ }
684
+ }
685
+ if (changed) {
686
+ await saveEmployees(employees, rosterPath);
687
+ }
688
+ return changed;
689
+ }
690
+ function findExeBin() {
691
+ try {
692
+ return execSync2(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
693
+ } catch {
694
+ return null;
695
+ }
696
+ }
697
+ function registerBinSymlinks(name) {
698
+ const created = [];
699
+ const skipped = [];
700
+ const errors = [];
701
+ const exeBinPath = findExeBin();
702
+ if (!exeBinPath) {
703
+ errors.push("Could not find 'exe-os' in PATH");
704
+ return { created, skipped, errors };
705
+ }
706
+ const binDir = path3.dirname(exeBinPath);
707
+ let target;
708
+ try {
709
+ target = readlinkSync(exeBinPath);
710
+ } catch {
711
+ errors.push("Could not read 'exe' symlink");
712
+ return { created, skipped, errors };
713
+ }
714
+ for (const suffix of ["", "-opencode"]) {
715
+ const linkName = `${name}${suffix}`;
716
+ const linkPath = path3.join(binDir, linkName);
717
+ if (existsSync4(linkPath)) {
718
+ skipped.push(linkName);
719
+ continue;
720
+ }
721
+ try {
722
+ symlinkSync(target, linkPath);
723
+ created.push(linkName);
724
+ } catch (err) {
725
+ errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
726
+ }
727
+ }
728
+ return { created, skipped, errors };
729
+ }
730
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR, TEAM_SECTION_RE;
373
731
  var init_employees = __esm({
374
732
  "src/lib/employees.ts"() {
375
733
  "use strict";
376
734
  init_config();
377
- EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
735
+ EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
378
736
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
379
737
  COORDINATOR_ROLE = "COO";
380
- IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
738
+ MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
739
+ IDENTITY_DIR = path3.join(EXE_AI_DIR, "identity");
740
+ TEAM_SECTION_RE = /^## Team\b.*$/m;
381
741
  }
382
742
  });
383
743
 
384
744
  // src/lib/session-registry.ts
385
- import path4 from "path";
745
+ import path5 from "path";
386
746
  import os3 from "os";
387
747
  var REGISTRY_PATH;
388
748
  var init_session_registry = __esm({
389
749
  "src/lib/session-registry.ts"() {
390
750
  "use strict";
391
- REGISTRY_PATH = path4.join(os3.homedir(), ".exe-os", "session-registry.json");
751
+ REGISTRY_PATH = path5.join(os3.homedir(), ".exe-os", "session-registry.json");
392
752
  }
393
753
  });
394
754
 
@@ -538,51 +898,6 @@ var init_provider_table = __esm({
538
898
  }
539
899
  });
540
900
 
541
- // src/lib/runtime-table.ts
542
- var RUNTIME_TABLE;
543
- var init_runtime_table = __esm({
544
- "src/lib/runtime-table.ts"() {
545
- "use strict";
546
- RUNTIME_TABLE = {
547
- codex: {
548
- binary: "codex",
549
- launchMode: "interactive",
550
- autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
551
- inlineFlag: "--no-alt-screen",
552
- apiKeyEnv: "OPENAI_API_KEY",
553
- defaultModel: "gpt-5.5"
554
- },
555
- opencode: {
556
- binary: "opencode",
557
- launchMode: "exec",
558
- autoApproveFlag: "--dangerously-skip-permissions",
559
- inlineFlag: "",
560
- apiKeyEnv: "ANTHROPIC_API_KEY",
561
- defaultModel: "anthropic/claude-sonnet-4-6"
562
- }
563
- };
564
- }
565
- });
566
-
567
- // src/lib/agent-config.ts
568
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync4 } from "fs";
569
- import path5 from "path";
570
- var AGENT_CONFIG_PATH, DEFAULT_MODELS;
571
- var init_agent_config = __esm({
572
- "src/lib/agent-config.ts"() {
573
- "use strict";
574
- init_config();
575
- init_runtime_table();
576
- init_secure_files();
577
- AGENT_CONFIG_PATH = path5.join(EXE_AI_DIR, "agent-config.json");
578
- DEFAULT_MODELS = {
579
- claude: "claude-opus-4.6",
580
- codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
581
- opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
582
- };
583
- }
584
- });
585
-
586
901
  // src/lib/intercom-queue.ts
587
902
  import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
588
903
  import path6 from "path";
@@ -3685,6 +4000,21 @@ function isRootSession(name) {
3685
4000
  function extractRootExe(name) {
3686
4001
  if (!name) return null;
3687
4002
  if (!name.includes("-")) return name;
4003
+ try {
4004
+ const roster = (init_employees(), __toCommonJS(employees_exports)).loadEmployeesSync();
4005
+ if (roster.length > 0) {
4006
+ const sortedNames = roster.map((e) => e.name).sort((a, b) => b.length - a.length);
4007
+ for (const agentName of sortedNames) {
4008
+ const escaped = agentName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4009
+ const regex = new RegExp(`^${escaped}\\d*-(.+)$`);
4010
+ const match = name.match(regex);
4011
+ if (match) {
4012
+ return extractRootExe(match[1]);
4013
+ }
4014
+ }
4015
+ }
4016
+ } catch {
4017
+ }
3688
4018
  const parts = name.split("-").filter(Boolean);
3689
4019
  return parts.length > 0 ? parts[parts.length - 1] : null;
3690
4020
  }
@@ -3703,6 +4033,10 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
3703
4033
  function getParentExe(sessionKey) {
3704
4034
  try {
3705
4035
  const data = JSON.parse(readFileSync10(path13.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
4036
+ if (data.registeredAt) {
4037
+ const age = Date.now() - new Date(data.registeredAt).getTime();
4038
+ if (age > PARENT_EXE_CACHE_TTL_MS) return null;
4039
+ }
3706
4040
  return data.parentExe || null;
3707
4041
  } catch {
3708
4042
  return null;
@@ -3775,7 +4109,7 @@ function resolveExeSession() {
3775
4109
  }
3776
4110
  return candidate;
3777
4111
  }
3778
- var SPAWN_LOCK_DIR, SESSION_CACHE, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS;
4112
+ var SPAWN_LOCK_DIR, SESSION_CACHE, PARENT_EXE_CACHE_TTL_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS;
3779
4113
  var init_tmux_routing = __esm({
3780
4114
  "src/lib/tmux-routing.ts"() {
3781
4115
  "use strict";
@@ -3793,6 +4127,7 @@ var init_tmux_routing = __esm({
3793
4127
  init_agent_symlinks();
3794
4128
  SPAWN_LOCK_DIR = path13.join(os9.homedir(), ".exe-os", "spawn-locks");
3795
4129
  SESSION_CACHE = path13.join(os9.homedir(), ".exe-os", "session-cache");
4130
+ PARENT_EXE_CACHE_TTL_MS = 4 * 60 * 60 * 1e3;
3796
4131
  INTERCOM_LOG2 = path13.join(os9.homedir(), ".exe-os", "intercom.log");
3797
4132
  DEBOUNCE_FILE = path13.join(SESSION_CACHE, "intercom-debounce.json");
3798
4133
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
@@ -3824,7 +4159,7 @@ var init_task_scope = __esm({
3824
4159
  });
3825
4160
 
3826
4161
  // src/lib/keychain.ts
3827
- import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
4162
+ import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2, rename, copyFile } from "fs/promises";
3828
4163
  import { existsSync as existsSync13, statSync as statSync3 } from "fs";
3829
4164
  import { execSync as execSync6 } from "child_process";
3830
4165
  import path14 from "path";
@@ -3859,12 +4194,14 @@ function linuxSecretAvailable() {
3859
4194
  function isRootOnlyTrustedServerKeyFile(keyPath) {
3860
4195
  if (process.platform !== "linux") return false;
3861
4196
  try {
3862
- const uid = typeof os10.userInfo().uid === "number" ? os10.userInfo().uid : -1;
3863
4197
  const st = statSync3(keyPath);
3864
4198
  if (!st.isFile() || (st.mode & 63) !== 0) return false;
4199
+ const uid = typeof os10.userInfo().uid === "number" ? os10.userInfo().uid : -1;
3865
4200
  if (uid === 0) return true;
3866
4201
  const exeOsDir = process.env.EXE_OS_DIR;
3867
- return Boolean(exeOsDir && path14.resolve(keyPath).startsWith(path14.resolve(exeOsDir) + path14.sep));
4202
+ if (exeOsDir && path14.resolve(keyPath).startsWith(path14.resolve(exeOsDir) + path14.sep)) return true;
4203
+ if (!linuxSecretAvailable()) return true;
4204
+ return false;
3868
4205
  } catch {
3869
4206
  return false;
3870
4207
  }
@@ -4014,15 +4351,25 @@ async function writeMachineBoundFileFallback(b64) {
4014
4351
  await mkdir3(dir, { recursive: true });
4015
4352
  const keyPath = getKeyPath();
4016
4353
  const machineKey = deriveMachineKey();
4017
- if (machineKey) {
4018
- const encrypted = encryptWithMachineKey(b64, machineKey);
4019
- await writeFile3(keyPath, encrypted + "\n", "utf-8");
4020
- await chmod2(keyPath, 384);
4021
- return "encrypted";
4354
+ const content = machineKey ? encryptWithMachineKey(b64, machineKey) + "\n" : b64 + "\n";
4355
+ const result = machineKey ? "encrypted" : "plaintext";
4356
+ const tmpPath = keyPath + ".tmp";
4357
+ try {
4358
+ if (existsSync13(keyPath)) {
4359
+ await copyFile(keyPath, keyPath + ".bak").catch(() => {
4360
+ });
4361
+ }
4362
+ await writeFile3(tmpPath, content, "utf-8");
4363
+ await chmod2(tmpPath, 384);
4364
+ await rename(tmpPath, keyPath);
4365
+ } catch (err) {
4366
+ try {
4367
+ await unlink(tmpPath);
4368
+ } catch {
4369
+ }
4370
+ throw err;
4022
4371
  }
4023
- await writeFile3(keyPath, b64 + "\n", "utf-8");
4024
- await chmod2(keyPath, 384);
4025
- return "plaintext";
4372
+ return result;
4026
4373
  }
4027
4374
  async function getMasterKey() {
4028
4375
  let nativeValue = macKeychainGet() ?? linuxSecretGet();
@@ -4089,7 +4436,7 @@ async function getMasterKey() {
4089
4436
  b64Value = content;
4090
4437
  }
4091
4438
  const key = Buffer.from(b64Value, "base64");
4092
- if (!content.startsWith(ENCRYPTED_PREFIX) && isRootOnlyTrustedServerKeyFile(keyPath)) {
4439
+ if (isRootOnlyTrustedServerKeyFile(keyPath)) {
4093
4440
  return key;
4094
4441
  }
4095
4442
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
@@ -6398,9 +6745,9 @@ var init_fast_db_init = __esm({
6398
6745
  // src/lib/active-agent.ts
6399
6746
  init_config();
6400
6747
  init_session_key();
6401
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2, readdirSync } from "fs";
6748
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2, readdirSync } from "fs";
6402
6749
  import { execSync as execSync3 } from "child_process";
6403
- import path3 from "path";
6750
+ import path4 from "path";
6404
6751
 
6405
6752
  // src/mcp/agent-context.ts
6406
6753
  import { AsyncLocalStorage } from "async_hooks";
@@ -6411,7 +6758,7 @@ function getAgentContext() {
6411
6758
 
6412
6759
  // src/lib/active-agent.ts
6413
6760
  init_employees();
6414
- var CACHE_DIR = path3.join(EXE_AI_DIR, "session-cache");
6761
+ var CACHE_DIR = path4.join(EXE_AI_DIR, "session-cache");
6415
6762
  var STALE_MS = 24 * 60 * 60 * 1e3;
6416
6763
  function isNameWithOptionalInstance(candidate, baseName) {
6417
6764
  if (candidate === baseName) return true;
@@ -6456,14 +6803,14 @@ function resolveActiveAgentFromTmuxSession(sessionName) {
6456
6803
  return null;
6457
6804
  }
6458
6805
  function getMarkerPath() {
6459
- return path3.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
6806
+ return path4.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
6460
6807
  }
6461
6808
  function getActiveAgent() {
6462
6809
  const httpCtx = getAgentContext();
6463
6810
  if (httpCtx) return httpCtx;
6464
6811
  try {
6465
6812
  const markerPath = getMarkerPath();
6466
- const raw = readFileSync3(markerPath, "utf8");
6813
+ const raw = readFileSync4(markerPath, "utf8");
6467
6814
  const data = JSON.parse(raw);
6468
6815
  if (data.agentId) {
6469
6816
  if (data.startedAt) {