【AI協調開発研究】 - AI二重化モデルの学術論文draft完成(workshop_paper_draft.md) - 「隠れた危機」分析とbirthの原則哲学化 - TyEnv「唯一の真実」協調会話を保存・研究資料に統合 - papers管理構造の整備(wip/under-review/published分離) 【Phase 10.9-β HostCall進捗】 - JitConfigBox: relax_numeric フラグ追加(i64→f64コアーション制御) - HostcallRegistryBox: 署名検証・白黒リスト・コアーション対応 - JitHostcallRegistryBox: Nyash側レジストリ操作API - Lower統合: env直読 → jit::config::current() 参照に統一 - 数値緩和設定: NYASH_JIT_HOSTCALL_RELAX_NUMERIC/Config.set_flag 【検証サンプル拡充】 - math.sin/cos/abs/min/max 関数スタイル(examples/jit_math_function_style_*.nyash) - 境界ケース: 署名不一致・コアーション許可・mutating拒否サンプル - E2E実証: String.length→allow, Array.push→fallback, math関数の署名一致観測 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
217 lines
6.2 KiB
JavaScript
217 lines
6.2 KiB
JavaScript
// tmux-perfect-bridge.js
|
||
// tmux × tmux = 完璧な双方向自動ブリッジ!
|
||
|
||
const { spawn } = require('child_process');
|
||
const EventEmitter = require('events');
|
||
|
||
class TmuxPerfectBridge extends EventEmitter {
|
||
constructor() {
|
||
super();
|
||
this.codexSession = 'codex-bridge';
|
||
this.claudeSession = 'claude-bridge';
|
||
this.isRunning = false;
|
||
this.lastCodexOutput = '';
|
||
this.lastClaudeOutput = '';
|
||
}
|
||
|
||
// 両方のAIをtmuxで起動
|
||
async start() {
|
||
console.log('🚀 Starting Perfect Bridge...');
|
||
|
||
// Codexを起動
|
||
await this.startSession(this.codexSession, '/home/tomoaki/.volta/bin/codex');
|
||
|
||
// Claudeを起動(仮のコマンド)
|
||
// await this.startSession(this.claudeSession, 'claude');
|
||
|
||
console.log('✅ Both AIs are ready in tmux!');
|
||
this.isRunning = true;
|
||
}
|
||
|
||
// tmuxセッションを起動
|
||
async startSession(sessionName, command) {
|
||
// 既存セッションを削除
|
||
await this.exec('tmux', ['kill-session', '-t', sessionName]).catch(() => {});
|
||
|
||
// 新規セッション作成
|
||
await this.exec('tmux', ['new-session', '-d', '-s', sessionName, command]);
|
||
console.log(`📺 Started ${sessionName}`);
|
||
|
||
// 起動待ち
|
||
await this.sleep(2000);
|
||
}
|
||
|
||
// Codex → Claude 転送
|
||
async forwardCodexToClaude() {
|
||
const codexOutput = await this.capturePane(this.codexSession);
|
||
const newContent = this.extractNewContent(codexOutput, this.lastCodexOutput);
|
||
|
||
if (newContent && this.isCodexResponse(newContent)) {
|
||
console.log('📨 Codex → Claude:', newContent.substring(0, 50) + '...');
|
||
|
||
// tmux send-keysで直接送信!Enterも完璧!
|
||
await this.sendToSession(this.claudeSession, newContent);
|
||
|
||
this.lastCodexOutput = codexOutput;
|
||
this.emit('codex-to-claude', newContent);
|
||
}
|
||
}
|
||
|
||
// Claude → Codex 転送
|
||
async forwardClaudeToCodex() {
|
||
const claudeOutput = await this.capturePane(this.claudeSession);
|
||
const newContent = this.extractNewContent(claudeOutput, this.lastClaudeOutput);
|
||
|
||
if (newContent && this.isClaudeResponse(newContent)) {
|
||
console.log('📨 Claude → Codex:', newContent.substring(0, 50) + '...');
|
||
|
||
// tmux send-keysで直接送信!Enterも完璧!
|
||
await this.sendToSession(this.codexSession, newContent);
|
||
|
||
this.lastClaudeOutput = claudeOutput;
|
||
this.emit('claude-to-codex', newContent);
|
||
}
|
||
}
|
||
|
||
// 双方向監視ループ
|
||
async startWatching(intervalMs = 1000) {
|
||
console.log('👁️ Starting bidirectional watch...');
|
||
|
||
const watchLoop = setInterval(async () => {
|
||
if (!this.isRunning) {
|
||
clearInterval(watchLoop);
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// 両方向をチェック
|
||
await this.forwardCodexToClaude();
|
||
await this.forwardClaudeToCodex();
|
||
} catch (err) {
|
||
console.error('❌ Watch error:', err);
|
||
}
|
||
}, intervalMs);
|
||
}
|
||
|
||
// tmuxペインをキャプチャ
|
||
async capturePane(sessionName) {
|
||
const result = await this.exec('tmux', ['capture-pane', '-t', sessionName, '-p']);
|
||
return result.stdout;
|
||
}
|
||
|
||
// tmuxセッションに送信(Enterも!)
|
||
async sendToSession(sessionName, text) {
|
||
await this.exec('tmux', ['send-keys', '-t', sessionName, text, 'Enter']);
|
||
}
|
||
|
||
// 新しいコンテンツを抽出
|
||
extractNewContent(current, previous) {
|
||
// 簡単な差分検出(実際はもっと高度にする)
|
||
if (current.length > previous.length) {
|
||
return current.substring(previous.length).trim();
|
||
}
|
||
return null;
|
||
}
|
||
|
||
// Codexの応答かどうか判定
|
||
isCodexResponse(text) {
|
||
return !text.includes('Working') &&
|
||
!text.includes('▌') &&
|
||
text.length > 10;
|
||
}
|
||
|
||
// Claudeの応答かどうか判定
|
||
isClaudeResponse(text) {
|
||
// Claudeの出力パターンに応じて調整
|
||
return text.length > 10;
|
||
}
|
||
|
||
// 初期メッセージを送信
|
||
async sendInitialMessage(message) {
|
||
console.log('🎯 Sending initial message to Codex...');
|
||
await this.sendToSession(this.codexSession, message);
|
||
}
|
||
|
||
// 両セッションを表示(デバッグ用)
|
||
showSessions() {
|
||
console.log('\n📺 Showing both sessions side by side...');
|
||
spawn('tmux', [
|
||
'new-window', '-n', 'AI-Bridge',
|
||
`tmux select-pane -t 0 \\; \
|
||
attach-session -t ${this.codexSession} \\; \
|
||
split-window -h \\; \
|
||
attach-session -t ${this.claudeSession}`
|
||
], { stdio: 'inherit' });
|
||
}
|
||
|
||
// 停止
|
||
async stop() {
|
||
this.isRunning = false;
|
||
await this.exec('tmux', ['kill-session', '-t', this.codexSession]).catch(() => {});
|
||
await this.exec('tmux', ['kill-session', '-t', this.claudeSession]).catch(() => {});
|
||
console.log('👋 Bridge stopped');
|
||
}
|
||
|
||
// ヘルパー関数
|
||
exec(command, args) {
|
||
return new Promise((resolve, reject) => {
|
||
const proc = spawn(command, args);
|
||
let stdout = '';
|
||
let stderr = '';
|
||
|
||
proc.stdout.on('data', (data) => stdout += data);
|
||
proc.stderr.on('data', (data) => stderr += data);
|
||
|
||
proc.on('close', (code) => {
|
||
if (code !== 0 && !stderr.includes('no server running')) {
|
||
reject(new Error(`${command} exited with code ${code}: ${stderr}`));
|
||
} else {
|
||
resolve({ stdout, stderr });
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
sleep(ms) {
|
||
return new Promise(resolve => setTimeout(resolve, ms));
|
||
}
|
||
}
|
||
|
||
// デモ実行
|
||
async function demo() {
|
||
const bridge = new TmuxPerfectBridge();
|
||
|
||
// イベントリスナー
|
||
bridge.on('codex-to-claude', (content) => {
|
||
console.log('🔄 Transferred from Codex to Claude');
|
||
});
|
||
|
||
bridge.on('claude-to-codex', (content) => {
|
||
console.log('🔄 Transferred from Claude to Codex');
|
||
});
|
||
|
||
try {
|
||
// ブリッジ開始
|
||
await bridge.start();
|
||
|
||
// 初期メッセージ
|
||
await bridge.sendInitialMessage('Nyashプロジェクトについて、お互いに意見を交換してください');
|
||
|
||
// 監視開始
|
||
await bridge.startWatching(500);
|
||
|
||
// デバッグ用に画面表示
|
||
// bridge.showSessions();
|
||
|
||
} catch (err) {
|
||
console.error('❌ Error:', err);
|
||
}
|
||
}
|
||
|
||
// エクスポート
|
||
module.exports = TmuxPerfectBridge;
|
||
|
||
// 直接実行
|
||
if (require.main === module) {
|
||
demo();
|
||
} |