factory.ts 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. /**
  2. * Multiplexer factory - creates the appropriate multiplexer instance
  3. */
  4. import type { MultiplexerConfig, MultiplexerType } from '../config/schema';
  5. import { log } from '../utils/logger';
  6. import { CmuxMultiplexer } from './cmux';
  7. import { HerdrMultiplexer } from './herdr';
  8. import { KittyMultiplexer } from './kitty';
  9. import { TmuxMultiplexer } from './tmux';
  10. import type { Multiplexer } from './types';
  11. import { ZellijMultiplexer } from './zellij';
  12. /**
  13. * Create a multiplexer instance based on config.
  14. *
  15. * Do not cache instances: tmux/zellij/herdr integrations may depend on
  16. * per-process environment like TMUX_PANE/ZELLIJ/HERDR_PANE_ID, which should
  17. * be captured fresh for each plugin context.
  18. */
  19. export function getMultiplexer(config: MultiplexerConfig): Multiplexer | null {
  20. const { type } = config;
  21. if (type === 'none') {
  22. return null;
  23. }
  24. // Create new instance
  25. let multiplexer: Multiplexer;
  26. let actualType: MultiplexerType;
  27. switch (type) {
  28. case 'tmux':
  29. multiplexer = new TmuxMultiplexer(config.layout, config.main_pane_size);
  30. actualType = 'tmux';
  31. break;
  32. case 'zellij':
  33. multiplexer = new ZellijMultiplexer(
  34. config.layout,
  35. config.main_pane_size,
  36. config.zellij_pane_mode,
  37. );
  38. actualType = 'zellij';
  39. break;
  40. case 'herdr':
  41. multiplexer = new HerdrMultiplexer(config.layout, config.main_pane_size);
  42. actualType = 'herdr';
  43. break;
  44. case 'cmux':
  45. multiplexer = new CmuxMultiplexer();
  46. actualType = 'cmux';
  47. break;
  48. case 'kitty':
  49. multiplexer = new KittyMultiplexer(config.layout, config.main_pane_size);
  50. actualType = 'kitty';
  51. break;
  52. case 'auto': {
  53. // Auto-detect based on environment variables only
  54. // Note: Does NOT fall back to binary availability checks
  55. if (
  56. process.env.CMUX_SOCKET_PATH &&
  57. process.env.CMUX_WORKSPACE_ID &&
  58. process.env.CMUX_SURFACE_ID
  59. ) {
  60. multiplexer = new CmuxMultiplexer();
  61. actualType = 'cmux';
  62. } else if (process.env.TMUX) {
  63. multiplexer = new TmuxMultiplexer(config.layout, config.main_pane_size);
  64. actualType = 'tmux';
  65. } else if (process.env.ZELLIJ) {
  66. multiplexer = new ZellijMultiplexer(
  67. config.layout,
  68. config.main_pane_size,
  69. config.zellij_pane_mode,
  70. );
  71. actualType = 'zellij';
  72. } else if (process.env.HERDR_ENV || process.env.HERDR_PANE_ID) {
  73. // Check Herdr before kitty: kitty exports KITTY_PID to every child
  74. // process, so a user running OpenCode inside kitty with Herdr active
  75. // would otherwise silently resolve to kitty and fail every spawn
  76. // (no KITTY_LISTEN_ON). Herdr's env vars are only set when Herdr is
  77. // actually active, so this is safe to prefer.
  78. multiplexer = new HerdrMultiplexer(
  79. config.layout,
  80. config.main_pane_size,
  81. );
  82. actualType = 'herdr';
  83. } else if (process.env.KITTY_PID || process.env.KITTY_WINDOW_ID) {
  84. multiplexer = new KittyMultiplexer(
  85. config.layout,
  86. config.main_pane_size,
  87. );
  88. actualType = 'kitty';
  89. } else {
  90. // Not inside any session, disable multiplexer
  91. log('[multiplexer] auto: not inside any session, disabling');
  92. return null;
  93. }
  94. break;
  95. }
  96. default:
  97. log(`[multiplexer] Unknown type: ${type}`);
  98. return null;
  99. }
  100. log(`[multiplexer] Created ${actualType} instance`);
  101. return multiplexer;
  102. }
  103. /**
  104. * Start background availability check for a multiplexer
  105. */
  106. export function startAvailabilityCheck(config: MultiplexerConfig): void {
  107. const multiplexer = getMultiplexer(config);
  108. if (multiplexer) {
  109. // Fire and forget - don't await
  110. multiplexer.isAvailable().catch(() => {});
  111. }
  112. }