claude-code-company
Version:
Multi-agent tmux coordination system for Claude Code with perfect Unicode support
172 lines (142 loc) • 7.12 kB
JavaScript
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
};