UNPKG

claude-code-company

Version:

Multi-agent tmux coordination system for Claude Code with perfect Unicode support

172 lines (142 loc) 7.12 kB
const { colors, agents, panes, rateLimitConfig, createAgentConfig, setWindowConfig } = require('./config'); const { tmuxCommand, getCurrentDir, createWorkerBranch, getCallerWindow, setCachedWindowId, clearWindowIdCache } = require('./tmux'); const { addSession } = require('./utils/persistence'); // Setup system with configurable boss and worker count async function setupSystem(workerCount = 3, projectBranch = null, bossCount = 1) { // 開始時に一度だけwindow IDを取得して固定 const fixedWindowId = await getCallerWindow(); setCachedWindowId(fixedWindowId, true); // setup実行中はロック console.log(`${colors.purple}🔒 Window ID固定: ${fixedWindowId} - setup実行中は他windowへの操作を防止${colors.nc}`); try { // Reconfigure agents with specified boss and worker count const config = createAgentConfig(workerCount, bossCount); agents.length = 0; agents.push(...config.agents); panes.length = 0; panes.push(...config.panes); const totalPanes = agents.length; console.log(`🚀 Creating ${totalPanes} panes (president + ${bossCount} bosses + ${workerCount} workers)...`); // Create Worker-specific branches for parallel development const workerBranches = {}; if (projectBranch || process.argv.includes('--with-branches')) { console.log(`${colors.blue}🌳 Creating Worker-specific branches for parallel development...${colors.nc}`); for (let i = 1; i <= workerCount; i++) { const workerName = `worker${i}`; try { const branchName = await createWorkerBranch(workerName); workerBranches[workerName] = branchName; console.log(`${colors.green}✅ Branch created for ${workerName}: ${branchName}${colors.nc}`); } catch (e) { console.log(`${colors.yellow}⚠️ Failed to create branch for ${workerName}: ${e.message}${colors.nc}`); } } } // 固定されたwindow IDを使用 const currentWindow = fixedWindowId; console.log(`${colors.blue}📍 対象window: ${currentWindow} (固定)${colors.nc}`); // Create tmux panes dynamically in current window // We need totalPanes agent panes + 1 UI pane (pane 0) // So we need to create totalPanes additional panes (since we start with pane 0) for (let i = 0; i < totalPanes; i++) { const splitFlag = '-h'; // 全て水平分割 try { // 現在のwindowを明示的に指定してペインを作成 await tmuxCommand('split-window', '-t', currentWindow, splitFlag); // Add delay between pane creations for stability await new Promise(resolve => setTimeout(resolve, 100)); } catch (e) { console.log(`${colors.yellow}⚠️ Pane creation ${i+1} failed: ${e.message}${colors.nc}`); } } try { // 水平分割に最適化したレイアウトを適用 await tmuxCommand('select-layout', '-t', currentWindow, 'even-horizontal'); } catch (e) { console.log(`${colors.yellow}⚠️ Layout selection failed: ${e.message}${colors.nc}`); } console.log('🎨 Setting up agents...'); // Setup agents dynamically (skip pane 0 which is for UI) for (let i = 0; i < totalPanes; i++) { const pane = panes[i]; // Use configured pane numbers const agent = agents[i]; console.log(`${colors.blue}Setting up ${agent} (pane ${pane})...${colors.nc}`); try { const tmuxTarget = `${currentWindow}.${pane}`; // Set pane title await tmuxCommand('select-pane', '-t', tmuxTarget, '-T', agent); // Change to project directory await tmuxCommand('send-keys', '-t', tmuxTarget, `cd "${getCurrentDir()}"`, 'C-m'); // Switch to Worker-specific branch if available if (workerBranches[agent] && agent.startsWith('worker')) { await new Promise(resolve => setTimeout(resolve, 300)); await tmuxCommand('send-keys', '-t', tmuxTarget, `git checkout ${workerBranches[agent]}`, 'C-m'); await new Promise(resolve => setTimeout(resolve, 500)); } await new Promise(resolve => setTimeout(resolve, 300)); // Clear pane await tmuxCommand('send-keys', '-t', tmuxTarget, 'clear', 'C-m'); await new Promise(resolve => setTimeout(resolve, 200)); // Start Claude Code in each pane with specific delay console.log(`${colors.blue}Starting Claude Code in ${agent}...${colors.nc}`); await new Promise(resolve => setTimeout(resolve, rateLimitConfig.setupDelay)); // Ensure clean environment before starting claude await tmuxCommand('send-keys', '-t', tmuxTarget, 'C-c'); await new Promise(resolve => setTimeout(resolve, 200)); await tmuxCommand('send-keys', '-t', tmuxTarget, 'C-u'); await new Promise(resolve => setTimeout(resolve, 200)); // プロンプト設定(最後のclaudeのみ表示) const promptCmd = `export PS1="${agent} > "; export PROMPT="${agent} > "`; await tmuxCommand('send-keys', '-t', tmuxTarget, promptCmd, 'C-m'); await new Promise(resolve => setTimeout(resolve, 200)); // 画面クリアしてプロンプト設定コマンドを隠す await tmuxCommand('send-keys', '-t', tmuxTarget, 'clear', 'C-m'); await new Promise(resolve => setTimeout(resolve, 200)); // Claude Code起動 await tmuxCommand('send-keys', '-t', tmuxTarget, 'claude', 'C-m'); } catch (e) { console.log(`${colors.red}❌ Failed to setup ${agent}: ${e.message}${colors.nc}`); // エラー時も継続してセットアップ try { console.log(`${colors.yellow}⚠️ Attempting minimal setup for ${agent}...${colors.nc}`); } catch (recoveryError) { console.log(`${colors.red}❌ Recovery failed for ${agent}: ${recoveryError.message}${colors.nc}`); } } } // window設定を保存 setWindowConfig(currentWindow, { agents: [...agents], panes: [...panes], setupComplete: true }); console.log(`${colors.green}✅ System ready! Each agent is now in its dedicated pane.${colors.nc}`); console.log(`${colors.blue}💾 Window ${currentWindow} の設定を保存しました${colors.nc}`); // セッション情報をファイルに永続化 const sessionInfo = { windowId: currentWindow, projectPath: getCurrentDir(), agents: agents, panes: panes, agentPaneMap: {} }; // エージェントとペインのマッピングを作成 for (let i = 0; i < agents.length; i++) { sessionInfo.agentPaneMap[agents[i]] = { pane: panes[i], windowId: currentWindow }; } if (addSession(sessionInfo)) { console.log(`${colors.green}💾 セッション情報を永続化しました${colors.nc}`); } else { console.log(`${colors.yellow}⚠️ セッション情報の永続化に失敗しました${colors.nc}`); } } finally { // setup終了時にキャッシュをクリア(ロック解除) clearWindowIdCache(); console.log(`${colors.purple}🔓 Window ID固定解除: setup完了${colors.nc}`); } } module.exports = { setupSystem };