UNPKG

claude-code-company

Version:

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

254 lines (223 loc) 9.11 kB
const fs = require('fs'); const { colors, agents, rateLimitConfig, getWindowConfig } = require('./config'); const { tmuxCommand, getTmuxTarget, tmuxGetPaneContent, tmuxSendKeys, tmuxSendKeysRaw, getCallerWindow, getAgentWindow, getCachedWindowId } = require('./tmux'); // Get pane number for agent async function getAgentPane(agent) { // Null/undefined チェック if (agent === null || agent === undefined) { throw new Error('Agent parameter is null or undefined'); } // エージェントが実際にいるwindow IDを取得 let windowId; try { // エージェント名からそのエージェントがいるウィンドウを特定 windowId = await getAgentWindow(agent); } catch (error) { // window ID取得失敗時はデフォルト設定を使用 const { agents: currentAgents, panes: currentPanes } = require('./config'); const agentIndex = currentAgents.indexOf(agent); if (agentIndex !== -1 && agentIndex < currentPanes.length) { return currentPanes[agentIndex]; } // 数値でアクセスする場合の対応 const paneNum = parseInt(agent); if (!isNaN(paneNum)) { return paneNum; } throw new Error(`Invalid agent: ${agent}`); } // window IDに対応する設定を取得 const windowConfig = getWindowConfig(windowId); const agentIndex = windowConfig.agents.indexOf(agent); if (agentIndex !== -1 && agentIndex < windowConfig.panes.length) { const pane = windowConfig.panes[agentIndex]; if (pane !== null && pane !== undefined) { return pane; } throw new Error(`Pane not found for agent ${agent} at index ${agentIndex}`); } // 数値でアクセスする場合の対応 const paneNum = parseInt(agent); if (!isNaN(paneNum)) { return paneNum; } throw new Error(`Invalid agent: ${agent}`); } // Ping an agent to check if it's responsive async function pingAgent(pane, agent) { try { // まずpaneが存在するか確認 const target = await getTmuxTarget(pane, agent); await tmuxCommand('list-panes', '-t', target); // paneの内容を取得 const content = await tmuxGetPaneContent(pane, agent); // Claude Codeが起動しているかチェック if (content.includes('Welcome to Claude Code') || content.includes('?')) { return { status: 'ready', responsive: true }; } else if (content.includes('claude') && content.includes('$')) { // claudeコマンドが実行されたが、まだ起動していない return { status: 'starting', responsive: false }; } else if (content.includes('===') && content.includes('pane')) { // エージェントが設定されているが、Claude Codeが起動していない return { status: 'configured', responsive: false }; } else { return { status: 'unknown', responsive: false }; } } catch (e) { return { status: 'offline', responsive: false }; } } // Restart an agent if it's not responsive async function restartAgent(pane, agentName) { console.log(`${colors.yellow}🔄 Attempting to restart ${agentName} (pane ${pane})...${colors.nc}`); try { // まずプロセスを停止 await tmuxSendKeysRaw(pane, 'C-c', agentName); await new Promise(resolve => setTimeout(resolve, 500)); // paneをクリア await tmuxSendKeys(pane, 'clear', agentName); await new Promise(resolve => setTimeout(resolve, 200)); // エージェント識別子を表示 await tmuxSendKeys(pane, `echo '=== ${agentName} (pane ${pane}) ==='`, agentName); await new Promise(resolve => setTimeout(resolve, 300)); // Claude Codeを再起動 await tmuxSendKeys(pane, 'claude', agentName); await new Promise(resolve => setTimeout(resolve, rateLimitConfig.setupDelay)); // 再度pingして確認 const pingResult = await pingAgent(pane, agentName); if (pingResult.status === 'ready' || pingResult.status === 'starting') { console.log(`${colors.green}${agentName} restarted successfully!${colors.nc}`); return true; } else { console.log(`${colors.red}❌ Failed to restart ${agentName}${colors.nc}`); return false; } } catch (e) { console.log(`${colors.red}❌ Error restarting ${agentName}: ${e.message}${colors.nc}`); return false; } } // Show system status async function showSystemStatus() { console.log(`${colors.yellow}📊 System Status${colors.nc}\n`); // tmuxセッション確認 try { await tmuxCommand('list-sessions'); console.log(`${colors.green}✅ tmux session: Active${colors.nc}`); } catch (e) { console.log(`${colors.red}❌ tmux session: Inactive${colors.nc}`); return; } console.log(`\n${colors.yellow}Agent Status:${colors.nc}`); // 各paneの状態確認 const totalAgents = agents.length; for (let i = 0; i < totalAgents; i++) { const agent = agents[i]; const pane = await getAgentPane(agent); if (!agent) continue; // エージェントをpingして状態を確認 const pingResult = await pingAgent(pane, agent); let statusIcon, statusText, statusColor; switch (pingResult.status) { case 'ready': statusIcon = '✅'; statusText = 'Ready'; statusColor = colors.green; break; case 'starting': statusIcon = '⚡'; statusText = 'Starting'; statusColor = colors.yellow; break; case 'configured': statusIcon = '⚙️'; statusText = 'Configured (Claude not running)'; statusColor = colors.yellow; break; case 'offline': statusIcon = '❌'; statusText = 'Offline'; statusColor = colors.red; break; default: statusIcon = '❓'; statusText = 'Unknown'; statusColor = colors.red; } console.log(` ${agent.padEnd(12)} ${statusColor}${statusIcon} ${statusText}${colors.nc}`); } // 一時ファイル確認 console.log(`\n${colors.yellow}📁 Temporary files:${colors.nc}`); try { const files = fs.readdirSync('./tmp'); if (files.length === 0) { console.log(`${colors.green}✅ No temporary files${colors.nc}`); } else { files.forEach(file => { console.log(`${colors.blue}📄 ${file}${colors.nc}`); }); } } catch (e) { console.log(`${colors.red}❌ Cannot read tmp directory${colors.nc}`); } } // アクティブなWorkerを取得する関数(現在のウィンドウコンテキスト対応) async function getActiveWorkers() { // 現在のウィンドウIDを取得 let currentWindowId; let windowConfig; try { // キャッシュされたwindow IDがあればそれを使用 currentWindowId = getCachedWindowId(); if (!currentWindowId) { currentWindowId = await getCurrentWindow(); } console.log(`${colors.blue}🔍 現在のウィンドウ: ${currentWindowId} でWorker検出中${colors.nc}`); windowConfig = getWindowConfig(currentWindowId); } catch (error) { console.log(`${colors.yellow}⚠️ ウィンドウ検出失敗、デフォルトWorkerを使用: ${error.message}${colors.nc}`); const { agents: currentAgents } = require('./config'); return currentAgents.filter(agent => agent && agent.startsWith('worker') && !agent.startsWith('president') ); } const workerAgents = windowConfig.agents.filter(agent => agent && agent.startsWith('worker') && !agent.startsWith('president') ); // tmuxセッション外の場合は設定されたWorkerをそのまま返す try { const { getCurrentRole } = require('./tmux'); const currentRole = await getCurrentRole(); if (currentRole === 'outside_session' || currentRole === 'no_tmux') { console.log(`${colors.yellow}⚠️ tmuxセッション外からWorker検出をスキップし、設定されたWorkerを使用${colors.nc}`); return workerAgents; } } catch (error) { // tmux関連エラーの場合も設定されたWorkerを使用 console.log(`${colors.yellow}⚠️ Worker検出エラーのため、設定されたWorkerを使用: ${error.message}${colors.nc}`); return workerAgents; } const activeWorkers = []; for (const worker of workerAgents) { try { const pane = await getAgentPane(worker); const pingResult = await pingAgent(pane, worker); if (pingResult.responsive || pingResult.status === 'ready' || pingResult.status === 'starting') { activeWorkers.push(worker); } } catch (error) { // エラーの場合でもWorkerを含める(メッセージ送信を試行) console.log(`${colors.yellow}⚠️ ${worker}のping失敗、メッセージ送信を試行: ${error.message}${colors.nc}`); activeWorkers.push(worker); } } return activeWorkers.length > 0 ? activeWorkers : workerAgents; } // 同期版(バックアップ用) function getActiveWorkersSync() { return agents.filter(agent => agent.startsWith('worker')); } module.exports = { getAgentPane, getActiveWorkers };