claude-code-company
Version:
Multi-agent tmux coordination system for Claude Code with perfect Unicode support
730 lines (624 loc) • 26.6 kB
JavaScript
const { spawn } = require('child_process');
const fs = require('fs');
const path = require('path');
const { colors, rateLimitConfig } = require('./config');
// シンプルなエラー追跡
const errorStats = {
timeoutErrors: 0,
connectionErrors: 0,
commandErrors: 0
};
// 負荷状況の追跡
const loadMetrics = {
messageCount: 0,
lastMessageTime: Date.now(),
avgResponseTime: 0,
responseTimeHistory: []
};
// Window IDキャッシュ(削除済み - 常に現在のウィンドウIDを使用)
// キャッシュ関連の関数はダミー実装として残す(互換性のため)
// キャッシュされたWindow IDを取得(常にnullを返す)
function getCachedWindowId() {
// キャッシュは使用しない - 常に現在のウィンドウIDを再取得
return null;
}
// Window IDをキャッシュに設定(何もしない)
function setCachedWindowId(windowId, lock = false) {
// キャッシュは使用しない
if (process.env.DEBUG_TMUX || process.env.DEBUG_WINDOW) {
console.log(`[DEBUG] Window ID cache disabled - ignoring set request for: ${windowId}`);
}
}
// Window IDのロック状態を確認(常にfalseを返す)
function isWindowIdCacheLocked() {
// ロック機能は無効
return false;
}
// キャッシュをクリア(何もしない)
function clearWindowIdCache() {
// キャッシュは使用しない
if (process.env.DEBUG_TMUX || process.env.DEBUG_WINDOW) {
console.log(`[DEBUG] Window ID cache disabled - ignoring clear request`);
}
}
// 動的遅延の計算
function calculateDynamicDelay(baseDelay = 100) {
const now = Date.now();
const timeSinceLastMessage = now - loadMetrics.lastMessageTime;
// 負荷判定
let loadFactor = 1;
if (loadMetrics.messageCount > 10 && timeSinceLastMessage < 500) {
// 高負荷: 10メッセージ以上かつ500ms以内
loadFactor = 0.5;
} else if (loadMetrics.messageCount > 5 && timeSinceLastMessage < 1000) {
// 中負荷
loadFactor = 0.3;
} else {
// 低負荷
loadFactor = 0;
}
// 0-50msの範囲で調整
const dynamicDelay = Math.min(50, Math.max(0, baseDelay * loadFactor));
// メトリクス更新
loadMetrics.messageCount++;
loadMetrics.lastMessageTime = now;
return dynamicDelay;
}
// Execute tmux command with enhanced error handling
function tmuxCommand(...args) {
return new Promise(async (resolve, reject) => {
// セッション存在確認(has-sessionコマンド以外)
if (args[0] !== 'has-session' && args[0] !== 'list-sessions') {
try {
await validateTmuxSession();
} catch (sessionError) {
errorStats.connectionErrors++;
reject(new Error(`tmux session validation failed: ${sessionError.message}`));
return;
}
}
const proc = spawn('tmux', args, { stdio: 'pipe' });
// 設定可能なタイムアウト
const timeoutMs = rateLimitConfig.tmuxTimeout || 10000;
const timeout = setTimeout(() => {
errorStats.timeoutErrors++;
proc.kill('SIGKILL');
reject(new Error(`tmux command timeout (${timeoutMs}ms): ${args.join(' ')}`));
}, timeoutMs);
let output = '';
let errorOutput = '';
if (proc.stdout) {
proc.stdout.on('data', (data) => {
output += data.toString();
});
}
if (proc.stderr) {
proc.stderr.on('data', (data) => {
errorOutput += data.toString();
});
}
proc.on('close', (code) => {
clearTimeout(timeout);
if (code === 0) {
resolve(output);
} else {
errorStats.commandErrors++;
const errorMsg = errorOutput || `tmux command failed with code ${code}`;
reject(new Error(`${errorMsg} (command: ${args.join(' ')})`))
}
});
proc.on('error', (error) => {
clearTimeout(timeout);
errorStats.connectionErrors++;
reject(new Error(`tmux spawn error: ${error.message} (command: ${args.join(' ')})`))
});
});
}
// tmuxセッション検証(現在のセッションを確認)
async function validateTmuxSession() {
try {
// 直接tmuxコマンドでセッション情報を取得(再帰を避ける)
const sessionProc = spawn('tmux', ['display-message', '-p', '#{session_name}'], { stdio: 'pipe' });
return new Promise((resolve, reject) => {
let output = '';
if (sessionProc.stdout) {
sessionProc.stdout.on('data', (data) => {
output += data.toString();
});
}
sessionProc.on('close', (code) => {
if (code === 0) {
const sessionName = output.trim();
if (sessionName) {
resolve(true);
} else {
reject(new Error('No active tmux session found'));
}
} else {
reject(new Error('tmux is not available or no session active'));
}
});
sessionProc.on('error', () => {
reject(new Error('tmux is not available'));
});
});
} catch (error) {
throw new Error(`Session validation error: ${error.message}`);
}
}
// 呼び出し元(コマンド実行場所)のwindow番号を取得
// 注意: これはエージェントがいるウィンドウではなく、コマンドを実行したプロセスのウィンドウ
async function getCallerWindow() {
const startTime = Date.now();
try {
// キャッシュは使用しない - 常に最新の呼び出し元ウィンドウIDを取得
// デバッグログ - 取得開始
if (process.env.DEBUG_TMUX || process.env.DEBUG_WINDOW) {
console.log(`[DEBUG] getCallerWindow: Starting caller window ID retrieval...`);
}
// 現在のwindow IDを直接取得(セッション名に依存しない)
let windowId = null;
let attempts = 0;
const maxAttempts = 3;
// リトライロジック
while (attempts < maxAttempts && !windowId) {
attempts++;
try {
const result = await tmuxCommand('display-message', '-p', '#{window_id}');
windowId = result.trim();
// window IDの検証
if (!windowId || !windowId.startsWith('@')) {
if (process.env.DEBUG_TMUX || process.env.DEBUG_WINDOW) {
console.log(`[DEBUG] getCallerWindow: Invalid window ID format: '${windowId}' (attempt ${attempts}/${maxAttempts})`);
}
windowId = null;
if (attempts < maxAttempts) {
await new Promise(resolve => setTimeout(resolve, 100 * attempts)); // 段階的待機
}
}
} catch (attemptError) {
if (process.env.DEBUG_TMUX || process.env.DEBUG_WINDOW) {
console.error(`[DEBUG] getCallerWindow: Attempt ${attempts}/${maxAttempts} failed: ${attemptError.message}`);
}
if (attempts < maxAttempts) {
await new Promise(resolve => setTimeout(resolve, 100 * attempts));
}
}
}
// 取得失敗時のフォールバック
if (!windowId) {
// 別の方法でwindow IDを取得を試みる
try {
const listResult = await tmuxCommand('list-windows', '-F', '#{window_id},#{window_active}');
const activeWindow = listResult.split('\n').find(line => line.endsWith(',1'));
if (activeWindow) {
windowId = activeWindow.split(',')[0];
if (process.env.DEBUG_TMUX || process.env.DEBUG_WINDOW) {
console.log(`[DEBUG] getCallerWindow: Retrieved window ID from list-windows: ${windowId}`);
}
}
} catch (listError) {
if (process.env.DEBUG_TMUX || process.env.DEBUG_WINDOW) {
console.error(`[DEBUG] getCallerWindow: list-windows fallback failed: ${listError.message}`);
}
}
}
// 最終的なフォールバック
if (!windowId || !windowId.startsWith('@')) {
windowId = '@1';
if (process.env.DEBUG_TMUX || process.env.DEBUG_WINDOW) {
console.warn(`[WARN] getCallerWindow: Using default window ID: ${windowId}`);
}
}
// デバッグログ - 成功
if (process.env.DEBUG_TMUX || process.env.DEBUG_WINDOW) {
const elapsed = Date.now() - startTime;
const sessionName = await tmuxCommand('display-message', '-p', '#{session_name}').catch(() => 'unknown');
console.log(`[DEBUG] getCallerWindow: Successfully retrieved caller window ID: ${windowId} in session: ${sessionName.trim()} (took ${elapsed}ms, ${attempts} attempts)`);
}
return windowId;
} catch (error) {
// エラー時の詳細ログ
const elapsed = Date.now() - startTime;
if (process.env.DEBUG_TMUX || process.env.DEBUG_WINDOW) {
console.error(`[ERROR] getCallerWindow: Failed to get window ID after ${elapsed}ms: ${error.message}`);
console.error(`[ERROR] getCallerWindow: Error stack: ${error.stack}`);
}
// エラー統計を更新
errorStats.commandErrors++;
return '@1';
}
}
// Get tmux target for pane (window.pane format)(エラーハンドリング強化版)
async function getTmuxTarget(pane, agent) {
const startTime = Date.now();
try {
// pane検証
if (pane === null || pane === undefined) {
const error = new Error('Pane parameter is null or undefined');
if (process.env.DEBUG_TMUX || process.env.DEBUG_TARGET) {
console.error(`[ERROR] getTmuxTarget: ${error.message}`);
}
throw error;
}
if (typeof pane !== 'number' && typeof pane !== 'string') {
const error = new Error(`Invalid pane type: ${typeof pane}`);
if (process.env.DEBUG_TMUX || process.env.DEBUG_TARGET) {
console.error(`[ERROR] getTmuxTarget: ${error.message}`);
}
throw error;
}
const paneNum = parseInt(pane);
if (isNaN(paneNum) || paneNum < 0 || paneNum > 50) {
const error = new Error(`Invalid pane number: ${pane} (must be 0-50)`);
if (process.env.DEBUG_TMUX || process.env.DEBUG_TARGET) {
console.error(`[ERROR] getTmuxTarget: ${error.message}`);
}
throw error;
}
// デバッグログ
if (process.env.DEBUG_TMUX || process.env.DEBUG_TARGET) {
console.log(`[DEBUG] getTmuxTarget: Getting target for pane ${paneNum}${agent ? ` (agent: ${agent})` : ''}`);
}
// 現在のwindowでターゲットを構成(セッション名に依存しない)
let currentWindow = null;
let target = null;
try {
// キャッシュされたwindow IDがあればそれを使用
currentWindow = getCachedWindowId();
if (!currentWindow) {
currentWindow = await getCallerWindow();
}
target = `${currentWindow}.${paneNum}`;
// ターゲットの存在確認
try {
await tmuxCommand('display-message', '-t', target, '-p', '#{pane_id}');
// デバッグログ - 成功
if (process.env.DEBUG_TMUX || process.env.DEBUG_TARGET) {
const elapsed = Date.now() - startTime;
console.log(`[DEBUG] getTmuxTarget: Successfully validated target ${target} (took ${elapsed}ms)`);
}
} catch (validateError) {
// ペインが存在しない場合
if (process.env.DEBUG_TMUX || process.env.DEBUG_TARGET) {
console.warn(`[WARN] getTmuxTarget: Target ${target} does not exist: ${validateError.message}`);
}
// ターゲットは返すが、警告を記録
}
return target;
} catch (windowError) {
// window ID取得エラー
if (process.env.DEBUG_TMUX || process.env.DEBUG_TARGET) {
console.error(`[ERROR] getTmuxTarget: Failed to get window ID: ${windowError.message}`);
}
// フォールバックターゲット
target = `@1.${paneNum}`;
if (process.env.DEBUG_TMUX || process.env.DEBUG_TARGET) {
console.warn(`[WARN] getTmuxTarget: Using fallback target ${target}`);
}
return target;
}
} catch (error) {
// 予期しないエラー
const elapsed = Date.now() - startTime;
if (process.env.DEBUG_TMUX || process.env.DEBUG_TARGET) {
console.error(`[ERROR] getTmuxTarget: Unexpected error after ${elapsed}ms: ${error.message}`);
console.error(`[ERROR] getTmuxTarget: Error stack: ${error.stack}`);
}
// エラー統計を更新
errorStats.commandErrors++;
// 最終フォールバック
const fallbackTarget = `@1.${pane || 0}`;
if (process.env.DEBUG_TMUX || process.env.DEBUG_TARGET) {
console.warn(`[WARN] getTmuxTarget: Returning final fallback target ${fallbackTarget}`);
}
return fallbackTarget;
}
}
// Send keys to tmux pane
async function tmuxSendKeys(pane, keys, agent = null) {
const target = agent ? await getTmuxTarget(pane, agent) : await getTmuxTarget(pane, null);
return tmuxCommand('send-keys', '-t', target, '-l', keys, 'C-m');
}
// Send raw keys to tmux pane
async function tmuxSendKeysRaw(pane, keys, agent = null) {
const target = agent ? await getTmuxTarget(pane, agent) : await getTmuxTarget(pane, null);
return tmuxCommand('send-keys', '-t', target, keys);
}
// Get pane content
async function tmuxGetPaneContent(pane, agent = null) {
return new Promise(async (resolve, reject) => {
const target = agent ? await getTmuxTarget(pane, agent) : await getTmuxTarget(pane, null);
const proc = spawn('tmux', ['capture-pane', '-t', target, '-p'], { stdio: 'pipe' });
let output = '';
if (proc.stdout) {
proc.stdout.on('data', (data) => {
output += data.toString();
});
}
proc.on('close', (code) => {
if (code === 0) {
resolve(output);
} else {
reject(new Error(`tmux capture-pane failed with code ${code}`));
}
});
proc.on('error', reject);
});
}
// Get current directory
function getCurrentDir() {
return process.cwd();
}
// 現在のペインから役割を判定
async function getCurrentRole() {
try {
// 現在のペイン番号を取得
const paneProc = spawn('tmux', ['display-message', '-p', '#{pane_index}'], { stdio: 'pipe' });
let paneOutput = '';
if (paneProc.stdout) {
paneProc.stdout.on('data', (data) => {
paneOutput += data.toString().trim();
});
}
const currentPaneIndex = await new Promise((resolve, reject) => {
paneProc.on('close', (code) => {
if (code === 0) {
resolve(parseInt(paneOutput) || 0);
} else {
resolve(null);
}
});
paneProc.on('error', () => resolve(null));
});
// pane 0はui/control用として特別扱い
if (currentPaneIndex === 0) {
return 'ui_control';
}
// まずはペインタイトルで判定
const titleProc = spawn('tmux', ['display-message', '-p', '#{pane_title}'], { stdio: 'pipe' });
let titleOutput = '';
if (titleProc.stdout) {
titleProc.stdout.on('data', (data) => {
titleOutput += data.toString().trim();
});
}
const titleResult = await new Promise((resolve, reject) => {
titleProc.on('close', (code) => {
if (code === 0) {
const title = titleOutput.toLowerCase();
if (title.includes('president') || title.includes('プロジェクト管理')) {
resolve('president');
} else if (title.includes('boss') || title.includes('delegation') || title.includes('work delegation')) {
resolve('boss1');
} else if (title.includes('worker')) {
const match = title.match(/worker(\d+)/);
resolve(match ? `worker${match[1]}` : 'worker1');
} else {
resolve(null);
}
} else {
resolve(null);
}
});
titleProc.on('error', () => resolve(null));
});
if (titleResult) {
return titleResult;
}
// ペインタイトルで判定できない場合、全ペインをスキャンして推測
const allPanesProc = spawn('tmux', ['list-panes', '-a', '-F', '#{pane_title},#{pane_id},#{pane_current}'], { stdio: 'pipe' });
let allPanesOutput = '';
if (allPanesProc.stdout) {
allPanesProc.stdout.on('data', (data) => {
allPanesOutput += data.toString();
});
}
return new Promise((resolve, reject) => {
allPanesProc.on('close', (code) => {
if (code === 0) {
const lines = allPanesOutput.trim().split('\n');
const currentPane = lines.find(line => line.endsWith(',1'));
if (currentPane) {
const [title, paneId, isCurrent] = currentPane.split(',');
const lowerTitle = title.toLowerCase();
if (lowerTitle.includes('president') || lowerTitle.includes('プロジェクト管理')) {
resolve('president');
} else if (lowerTitle.includes('boss') || lowerTitle.includes('delegation') || lowerTitle.includes('work delegation')) {
resolve('boss1');
} else if (lowerTitle.includes('worker')) {
const match = lowerTitle.match(/worker(\d+)/);
resolve(match ? `worker${match[1]}` : 'worker1');
} else {
// セッションが存在するが役割不明
resolve('in_session');
}
} else {
// tmuxセッション外で実行
resolve('outside_session');
}
} else {
// tmuxが利用できない
resolve('no_tmux');
}
});
allPanesProc.on('error', () => resolve('no_tmux'));
});
} catch (error) {
console.error('Error getting current role:', error);
return 'no_tmux';
}
}
// Clean message sender to avoid input artifacts
async function sendCleanMessage(pane, message, targetWindowId = null) {
// windowIdを決定する優先順位:
// 1. 引数で指定されたtargetWindowId
// 2. 現在のwindow ID(常に最新を取得)
let windowId = targetWindowId;
let target;
if (!windowId) {
// 常に現在のウィンドウIDを取得(キャッシュは使用しない)
try {
windowId = await getCallerWindow();
// デバッグログ
if (process.env.DEBUG_TMUX) {
console.log(`[DEBUG] sendCleanMessage: Retrieved current window ID ${windowId}`);
}
} catch (error) {
// エラー時はデフォルトのwindow IDとターゲット
windowId = '@1';
console.warn(`[WARN] Failed to get window ID, using default: ${error.message}`);
}
} else {
// デバッグログ
if (process.env.DEBUG_TMUX) {
console.log(`[DEBUG] sendCleanMessage: Using specified window ID ${windowId}`);
}
}
target = `${windowId}.${pane}`;
try {
// pane検証
if (pane === null || pane === undefined) {
throw new Error('Pane number is null or undefined');
}
if (typeof pane !== 'number' || pane < 0 || pane > 20) {
throw new Error(`Invalid pane number: ${pane} (must be 0-20)`);
}
// message検証
if (!message || typeof message !== 'string') {
throw new Error(`Invalid message: ${message}`);
}
// メッセージ長検証(tmuxバッファ制限)
if (message.length > 10000) {
throw new Error(`Message too long: ${message.length} characters (max: 10000)`);
}
// 固定されたwindow IDとペイン番号を使用してselect-pane
await tmuxCommand('select-pane', '-t', target);
// すべてのtmuxコマンドで固定されたtargetを使用
await tmuxCommand('send-keys', '-t', target, 'C-c');
await new Promise(resolve => setTimeout(resolve, calculateDynamicDelay(500)));
await tmuxCommand('send-keys', '-t', target, 'C-u');
await new Promise(resolve => setTimeout(resolve, calculateDynamicDelay(200)));
await tmuxCommand('send-keys', '-t', target, 'C-k');
await new Promise(resolve => setTimeout(resolve, calculateDynamicDelay(200)));
const messageLines = message.split('\n');
for (const line of messageLines) {
try {
if (line.trim() === '') {
await tmuxCommand('send-keys', '-t', target, 'Enter');
} else {
const escapedLine = line.replace(/[\$\`\\"]/g, '\\$&');
await tmuxCommand('send-keys', '-t', target, '-l', escapedLine);
await new Promise(resolve => setTimeout(resolve, calculateDynamicDelay(50)));
await tmuxCommand('send-keys', '-t', target, 'Enter');
}
await new Promise(resolve => setTimeout(resolve, calculateDynamicDelay(100)));
} catch (lineError) {
console.error(`Error sending line "${line}": ${lineError.message}`);
continue;
}
}
await new Promise(resolve => setTimeout(resolve, calculateDynamicDelay(300)));
await tmuxCommand('send-keys', '-t', target, 'Enter');
await new Promise(resolve => setTimeout(resolve, calculateDynamicDelay(200)));
await tmuxCommand('send-keys', '-t', target, 'Enter');
// 送信完了時のデバッグログ
if (process.env.DEBUG_TMUX) {
console.log(`[DEBUG] Message sent successfully to ${target}`);
}
} catch (error) {
console.error(`${colors.red}Error in sendCleanMessage to pane ${pane}: ${error.message}${colors.nc}`);
throw new Error(`Failed to send message to pane ${pane}: ${error.message}`);
}
}
// \u30a8\u30fc\u30b8\u30a7\u30f3\u30c8\u304c\u5b9f\u969b\u306b\u3044\u308b\u30a6\u30a3\u30f3\u30c9\u30a6ID\u3092\u53d6\u5f97
// \u3059\u3079\u3066\u306e\u30a6\u30a3\u30f3\u30c9\u30a6\u3092\u691c\u7d22\u3057\u3066\u3001\u6307\u5b9a\u3055\u308c\u305f\u30a8\u30fc\u30b8\u30a7\u30f3\u30c8\u540d\u3092\u542b\u3080\u30da\u30a4\u30f3\u3092\u63a2\u3059
async function getAgentWindow(agentName) {
try {
// \u30c7\u30d0\u30c3\u30b0\u30ed\u30b0
if (process.env.DEBUG_TMUX || process.env.DEBUG_WINDOW) {
console.log(`[DEBUG] getAgentWindow: Searching for agent '${agentName}'...`);
}
// \u3059\u3079\u3066\u306e\u30a6\u30a3\u30f3\u30c9\u30a6\u3092\u30ea\u30b9\u30c8\u30a2\u30c3\u30d7
const windowsOutput = await tmuxCommand('list-windows', '-F', '#{window_id}');
const windowIds = windowsOutput.trim().split('\n').filter(id => id);
if (process.env.DEBUG_TMUX || process.env.DEBUG_WINDOW) {
console.log(`[DEBUG] getAgentWindow: Found ${windowIds.length} windows to search`);
}
// \u5404\u30a6\u30a3\u30f3\u30c9\u30a6\u3067\u30a8\u30fc\u30b8\u30a7\u30f3\u30c8\u3092\u691c\u7d22
for (const windowId of windowIds) {
try {
// \u30a6\u30a3\u30f3\u30c9\u30a6\u5185\u306e\u3059\u3079\u3066\u306e\u30da\u30a4\u30f3\u3092\u53d6\u5f97
const panesOutput = await tmuxCommand('list-panes', '-t', windowId, '-F', '#{pane_index}:#{pane_title}');
const panes = panesOutput.trim().split('\n');
// \u30a8\u30fc\u30b8\u30a7\u30f3\u30c8\u540d\u3092\u542b\u3080\u30da\u30a4\u30f3\u3092\u63a2\u3059
for (const paneInfo of panes) {
if (paneInfo.includes(agentName)) {
if (process.env.DEBUG_TMUX || process.env.DEBUG_WINDOW) {
console.log(`[DEBUG] getAgentWindow: Found agent '${agentName}' in window ${windowId}`);
}
return windowId;
}
}
// \u30da\u30a4\u30f3\u306e\u5185\u5bb9\u3082\u30c1\u30a7\u30c3\u30af\uff08\u30bf\u30a4\u30c8\u30eb\u3067\u898b\u3064\u304b\u3089\u306a\u3044\u5834\u5408\uff09
const panesContentOutput = await tmuxCommand('list-panes', '-t', windowId, '-F', '#{pane_index}');
const paneIndices = panesContentOutput.trim().split('\n');
for (const paneIndex of paneIndices) {
try {
const paneContent = await tmuxCommand('capture-pane', '-t', `${windowId}.${paneIndex}`, '-p');
// \u30a8\u30fc\u30b8\u30a7\u30f3\u30c8\u8b58\u5225\u30d1\u30bf\u30fc\u30f3\u3092\u691c\u7d22
if (paneContent.includes(`=== ${agentName}`) ||
paneContent.includes(`${agentName} (pane`)) {
if (process.env.DEBUG_TMUX || process.env.DEBUG_WINDOW) {
console.log(`[DEBUG] getAgentWindow: Found agent '${agentName}' in window ${windowId} (by content)`);
}
return windowId;
}
} catch (captureError) {
// \u30da\u30a4\u30f3\u306e\u30ad\u30e3\u30d7\u30c1\u30e3\u306b\u5931\u6557\u3057\u305f\u5834\u5408\u306f\u30b9\u30ad\u30c3\u30d7
continue;
}
}
} catch (windowError) {
// \u30a6\u30a3\u30f3\u30c9\u30a6\u306e\u691c\u7d22\u306b\u5931\u6557\u3057\u305f\u5834\u5408\u306f\u6b21\u306e\u30a6\u30a3\u30f3\u30c9\u30a6\u3078
if (process.env.DEBUG_TMUX || process.env.DEBUG_WINDOW) {
console.error(`[DEBUG] getAgentWindow: Error searching window ${windowId}: ${windowError.message}`);
}
continue;
}
}
// \u30a8\u30fc\u30b8\u30a7\u30f3\u30c8\u304c\u898b\u3064\u304b\u3089\u306a\u3044\u5834\u5408\u306f\u3001\u547c\u3073\u51fa\u3057\u5143\u306e\u30a6\u30a3\u30f3\u30c9\u30a6ID\u3092\u8fd4\u3059\uff08\u30d5\u30a9\u30fc\u30eb\u30d0\u30c3\u30af\uff09
const callerWindow = await getCallerWindow();
if (process.env.DEBUG_TMUX || process.env.DEBUG_WINDOW) {
console.warn(`[WARN] getAgentWindow: Agent '${agentName}' not found in any window, using caller window ${callerWindow}`);
}
return callerWindow;
} catch (error) {
console.error(`[ERROR] getAgentWindow: Failed to find window for agent '${agentName}': ${error.message}`);
// \u30a8\u30e9\u30fc\u6642\u306f\u547c\u3073\u51fa\u3057\u5143\u306e\u30a6\u30a3\u30f3\u30c9\u30a6ID\u3092\u8fd4\u3059
return await getCallerWindow();
}
}
// Git worktree management placeholder functions
function createWorkerBranch(workerName, baseBranch = 'main') {
// Placeholder - actual implementation would create branch
const branchName = `worker/${workerName}/${Date.now()}`;
return branchName;
}
module.exports = {
tmuxCommand,
getTmuxTarget,
getCallerWindow,
getAgentWindow, // 新規追加: エージェントが実際にいるウィンドウIDを取得
getCachedWindowId,
setCachedWindowId,
clearWindowIdCache,
isWindowIdCacheLocked,
tmuxSendKeys,
tmuxSendKeysRaw,
tmuxGetPaneContent,
getCurrentDir,
createWorkerBranch,
sendCleanMessage,
getCurrentRole
};