@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();
@@ -351,10 +528,47 @@ function getCoordinatorEmployee(employees) {
351
528
  function getCoordinatorName(employees = loadEmployeesSync()) {
352
529
  return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
353
530
  }
531
+ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
532
+ if (!agentName) return false;
533
+ return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
534
+ }
535
+ function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
536
+ return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
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
+ }
354
568
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
355
- if (!existsSync3(employeesPath)) return [];
569
+ if (!existsSync4(employeesPath)) return [];
356
570
  try {
357
- return JSON.parse(readFileSync2(employeesPath, "utf-8"));
571
+ return JSON.parse(readFileSync3(employeesPath, "utf-8"));
358
572
  } catch {
359
573
  return [];
360
574
  }
@@ -362,26 +576,179 @@ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
362
576
  function getEmployee(employees, name) {
363
577
  return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
364
578
  }
365
- 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;
366
731
  var init_employees = __esm({
367
732
  "src/lib/employees.ts"() {
368
733
  "use strict";
369
734
  init_config();
370
- EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
735
+ EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
371
736
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
372
737
  COORDINATOR_ROLE = "COO";
373
- 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;
374
741
  }
375
742
  });
376
743
 
377
744
  // src/lib/session-registry.ts
378
- import path4 from "path";
745
+ import path5 from "path";
379
746
  import os3 from "os";
380
747
  var REGISTRY_PATH;
381
748
  var init_session_registry = __esm({
382
749
  "src/lib/session-registry.ts"() {
383
750
  "use strict";
384
- REGISTRY_PATH = path4.join(os3.homedir(), ".exe-os", "session-registry.json");
751
+ REGISTRY_PATH = path5.join(os3.homedir(), ".exe-os", "session-registry.json");
385
752
  }
386
753
  });
387
754
 
@@ -531,51 +898,6 @@ var init_provider_table = __esm({
531
898
  }
532
899
  });
533
900
 
534
- // src/lib/runtime-table.ts
535
- var RUNTIME_TABLE;
536
- var init_runtime_table = __esm({
537
- "src/lib/runtime-table.ts"() {
538
- "use strict";
539
- RUNTIME_TABLE = {
540
- codex: {
541
- binary: "codex",
542
- launchMode: "interactive",
543
- autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
544
- inlineFlag: "--no-alt-screen",
545
- apiKeyEnv: "OPENAI_API_KEY",
546
- defaultModel: "gpt-5.5"
547
- },
548
- opencode: {
549
- binary: "opencode",
550
- launchMode: "exec",
551
- autoApproveFlag: "--dangerously-skip-permissions",
552
- inlineFlag: "",
553
- apiKeyEnv: "ANTHROPIC_API_KEY",
554
- defaultModel: "anthropic/claude-sonnet-4-6"
555
- }
556
- };
557
- }
558
- });
559
-
560
- // src/lib/agent-config.ts
561
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync4 } from "fs";
562
- import path5 from "path";
563
- var AGENT_CONFIG_PATH, DEFAULT_MODELS;
564
- var init_agent_config = __esm({
565
- "src/lib/agent-config.ts"() {
566
- "use strict";
567
- init_config();
568
- init_runtime_table();
569
- init_secure_files();
570
- AGENT_CONFIG_PATH = path5.join(EXE_AI_DIR, "agent-config.json");
571
- DEFAULT_MODELS = {
572
- claude: "claude-opus-4.6",
573
- codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
574
- opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
575
- };
576
- }
577
- });
578
-
579
901
  // src/lib/intercom-queue.ts
580
902
  import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
581
903
  import path6 from "path";
@@ -3678,6 +4000,21 @@ function isRootSession(name) {
3678
4000
  function extractRootExe(name) {
3679
4001
  if (!name) return null;
3680
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
+ }
3681
4018
  const parts = name.split("-").filter(Boolean);
3682
4019
  return parts.length > 0 ? parts[parts.length - 1] : null;
3683
4020
  }
@@ -3696,6 +4033,10 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
3696
4033
  function getParentExe(sessionKey) {
3697
4034
  try {
3698
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
+ }
3699
4040
  return data.parentExe || null;
3700
4041
  } catch {
3701
4042
  return null;
@@ -3768,7 +4109,7 @@ function resolveExeSession() {
3768
4109
  }
3769
4110
  return candidate;
3770
4111
  }
3771
- 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;
3772
4113
  var init_tmux_routing = __esm({
3773
4114
  "src/lib/tmux-routing.ts"() {
3774
4115
  "use strict";
@@ -3786,6 +4127,7 @@ var init_tmux_routing = __esm({
3786
4127
  init_agent_symlinks();
3787
4128
  SPAWN_LOCK_DIR = path13.join(os9.homedir(), ".exe-os", "spawn-locks");
3788
4129
  SESSION_CACHE = path13.join(os9.homedir(), ".exe-os", "session-cache");
4130
+ PARENT_EXE_CACHE_TTL_MS = 4 * 60 * 60 * 1e3;
3789
4131
  INTERCOM_LOG2 = path13.join(os9.homedir(), ".exe-os", "intercom.log");
3790
4132
  DEBOUNCE_FILE = path13.join(SESSION_CACHE, "intercom-debounce.json");
3791
4133
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
@@ -3957,7 +4299,7 @@ var init_identity = __esm({
3957
4299
  });
3958
4300
 
3959
4301
  // src/lib/keychain.ts
3960
- import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
4302
+ import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2, rename, copyFile } from "fs/promises";
3961
4303
  import { existsSync as existsSync14, statSync as statSync3 } from "fs";
3962
4304
  import { execSync as execSync6 } from "child_process";
3963
4305
  import path15 from "path";
@@ -3992,12 +4334,14 @@ function linuxSecretAvailable() {
3992
4334
  function isRootOnlyTrustedServerKeyFile(keyPath) {
3993
4335
  if (process.platform !== "linux") return false;
3994
4336
  try {
3995
- const uid = typeof os10.userInfo().uid === "number" ? os10.userInfo().uid : -1;
3996
4337
  const st = statSync3(keyPath);
3997
4338
  if (!st.isFile() || (st.mode & 63) !== 0) return false;
4339
+ const uid = typeof os10.userInfo().uid === "number" ? os10.userInfo().uid : -1;
3998
4340
  if (uid === 0) return true;
3999
4341
  const exeOsDir = process.env.EXE_OS_DIR;
4000
- return Boolean(exeOsDir && path15.resolve(keyPath).startsWith(path15.resolve(exeOsDir) + path15.sep));
4342
+ if (exeOsDir && path15.resolve(keyPath).startsWith(path15.resolve(exeOsDir) + path15.sep)) return true;
4343
+ if (!linuxSecretAvailable()) return true;
4344
+ return false;
4001
4345
  } catch {
4002
4346
  return false;
4003
4347
  }
@@ -4147,15 +4491,25 @@ async function writeMachineBoundFileFallback(b64) {
4147
4491
  await mkdir3(dir, { recursive: true });
4148
4492
  const keyPath = getKeyPath();
4149
4493
  const machineKey = deriveMachineKey();
4150
- if (machineKey) {
4151
- const encrypted = encryptWithMachineKey(b64, machineKey);
4152
- await writeFile3(keyPath, encrypted + "\n", "utf-8");
4153
- await chmod2(keyPath, 384);
4154
- return "encrypted";
4494
+ const content = machineKey ? encryptWithMachineKey(b64, machineKey) + "\n" : b64 + "\n";
4495
+ const result = machineKey ? "encrypted" : "plaintext";
4496
+ const tmpPath = keyPath + ".tmp";
4497
+ try {
4498
+ if (existsSync14(keyPath)) {
4499
+ await copyFile(keyPath, keyPath + ".bak").catch(() => {
4500
+ });
4501
+ }
4502
+ await writeFile3(tmpPath, content, "utf-8");
4503
+ await chmod2(tmpPath, 384);
4504
+ await rename(tmpPath, keyPath);
4505
+ } catch (err) {
4506
+ try {
4507
+ await unlink(tmpPath);
4508
+ } catch {
4509
+ }
4510
+ throw err;
4155
4511
  }
4156
- await writeFile3(keyPath, b64 + "\n", "utf-8");
4157
- await chmod2(keyPath, 384);
4158
- return "plaintext";
4512
+ return result;
4159
4513
  }
4160
4514
  async function getMasterKey() {
4161
4515
  let nativeValue = macKeychainGet() ?? linuxSecretGet();
@@ -4222,7 +4576,7 @@ async function getMasterKey() {
4222
4576
  b64Value = content;
4223
4577
  }
4224
4578
  const key = Buffer.from(b64Value, "base64");
4225
- if (!content.startsWith(ENCRYPTED_PREFIX) && isRootOnlyTrustedServerKeyFile(keyPath)) {
4579
+ if (isRootOnlyTrustedServerKeyFile(keyPath)) {
4226
4580
  return key;
4227
4581
  }
4228
4582
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
@@ -6531,9 +6885,9 @@ var init_fast_db_init = __esm({
6531
6885
  // src/lib/active-agent.ts
6532
6886
  init_config();
6533
6887
  init_session_key();
6534
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2, readdirSync } from "fs";
6888
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2, readdirSync } from "fs";
6535
6889
  import { execSync as execSync3 } from "child_process";
6536
- import path3 from "path";
6890
+ import path4 from "path";
6537
6891
 
6538
6892
  // src/mcp/agent-context.ts
6539
6893
  import { AsyncLocalStorage } from "async_hooks";
@@ -6544,7 +6898,7 @@ function getAgentContext() {
6544
6898
 
6545
6899
  // src/lib/active-agent.ts
6546
6900
  init_employees();
6547
- var CACHE_DIR = path3.join(EXE_AI_DIR, "session-cache");
6901
+ var CACHE_DIR = path4.join(EXE_AI_DIR, "session-cache");
6548
6902
  var STALE_MS = 24 * 60 * 60 * 1e3;
6549
6903
  function isNameWithOptionalInstance(candidate, baseName) {
6550
6904
  if (candidate === baseName) return true;
@@ -6589,14 +6943,14 @@ function resolveActiveAgentFromTmuxSession(sessionName) {
6589
6943
  return null;
6590
6944
  }
6591
6945
  function getMarkerPath() {
6592
- return path3.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
6946
+ return path4.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
6593
6947
  }
6594
6948
  function getActiveAgent() {
6595
6949
  const httpCtx = getAgentContext();
6596
6950
  if (httpCtx) return httpCtx;
6597
6951
  try {
6598
6952
  const markerPath = getMarkerPath();
6599
- const raw = readFileSync3(markerPath, "utf8");
6953
+ const raw = readFileSync4(markerPath, "utf8");
6600
6954
  const data = JSON.parse(raw);
6601
6955
  if (data.agentId) {
6602
6956
  if (data.startedAt) {