【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>
189 lines
4.8 KiB
JavaScript
189 lines
4.8 KiB
JavaScript
// claude-codex-unified-bridge.js
|
||
// 同一hook-serverを使った完璧な双方向ブリッジ!
|
||
|
||
const { spawn } = require('child_process');
|
||
const WebSocket = require('ws');
|
||
const EventEmitter = require('events');
|
||
|
||
class ClaudeCodexUnifiedBridge extends EventEmitter {
|
||
constructor(config = {}) {
|
||
super();
|
||
this.config = {
|
||
hookServer: config.hookServer || 'ws://localhost:8770',
|
||
claudeSession: config.claudeSession || 'claude-8771',
|
||
codexSession: config.codexSession || 'codex-safe',
|
||
watchInterval: config.watchInterval || 500,
|
||
...config
|
||
};
|
||
|
||
this.ws = null;
|
||
this.isRunning = false;
|
||
this.lastClaudeOutput = '';
|
||
this.lastCodexOutput = '';
|
||
}
|
||
|
||
// ブリッジ開始
|
||
async start() {
|
||
console.log('🌉 Starting Claude-Codex Unified Bridge...');
|
||
console.log('📡 Hook Server:', this.config.hookServer);
|
||
|
||
// WebSocket接続
|
||
await this.connectToHookServer();
|
||
|
||
// 監視開始
|
||
this.isRunning = true;
|
||
this.startWatching();
|
||
|
||
console.log('✅ Bridge is running!');
|
||
}
|
||
|
||
// hook-serverに接続
|
||
connectToHookServer() {
|
||
return new Promise((resolve, reject) => {
|
||
this.ws = new WebSocket(this.config.hookServer);
|
||
|
||
this.ws.on('open', () => {
|
||
console.log('✅ Connected to hook-server');
|
||
|
||
// ブリッジとして登録
|
||
this.ws.send(JSON.stringify({
|
||
source: 'bridge',
|
||
type: 'register',
|
||
data: 'claude-codex-bridge'
|
||
}));
|
||
|
||
resolve();
|
||
});
|
||
|
||
this.ws.on('error', (err) => {
|
||
console.error('❌ WebSocket error:', err);
|
||
reject(err);
|
||
});
|
||
|
||
this.ws.on('close', () => {
|
||
console.log('🔌 Disconnected from hook-server');
|
||
this.isRunning = false;
|
||
});
|
||
});
|
||
}
|
||
|
||
// 監視ループ
|
||
startWatching() {
|
||
const watchLoop = setInterval(async () => {
|
||
if (!this.isRunning) {
|
||
clearInterval(watchLoop);
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// Codexの出力をチェック
|
||
await this.checkCodexOutput();
|
||
|
||
// Claudeの出力もチェック(必要に応じて)
|
||
// await this.checkClaudeOutput();
|
||
|
||
} catch (err) {
|
||
console.error('❌ Watch error:', err);
|
||
}
|
||
}, this.config.watchInterval);
|
||
}
|
||
|
||
// Codexの出力をチェック
|
||
async checkCodexOutput() {
|
||
const output = await this.capturePane(this.config.codexSession);
|
||
|
||
// 新しい内容があるかチェック
|
||
if (output !== this.lastCodexOutput) {
|
||
const newContent = this.extractNewContent(output, this.lastCodexOutput);
|
||
|
||
if (newContent && this.isCodexResponse(newContent)) {
|
||
console.log('📨 Codex response detected!');
|
||
|
||
// Claudeに転送
|
||
this.sendToClaude(newContent);
|
||
|
||
this.lastCodexOutput = output;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Claudeにメッセージを送信(hook-server経由)
|
||
sendToClaude(message) {
|
||
console.log('📤 Sending to Claude via hook-server...');
|
||
|
||
const payload = {
|
||
source: 'codex',
|
||
type: 'inject-input',
|
||
data: `[Codex Response]\n${message}`
|
||
};
|
||
|
||
this.ws.send(JSON.stringify(payload));
|
||
|
||
this.emit('codex-to-claude', message);
|
||
}
|
||
|
||
// tmuxペインをキャプチャ
|
||
capturePane(sessionName) {
|
||
return new Promise((resolve, reject) => {
|
||
const proc = spawn('tmux', ['capture-pane', '-t', sessionName, '-p']);
|
||
let output = '';
|
||
|
||
proc.stdout.on('data', (data) => output += data);
|
||
proc.on('close', (code) => {
|
||
if (code === 0) {
|
||
resolve(output);
|
||
} else {
|
||
reject(new Error(`tmux capture failed with code ${code}`));
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
// 新しいコンテンツを抽出
|
||
extractNewContent(current, previous) {
|
||
if (current.length > previous.length) {
|
||
return current.substring(previous.length).trim();
|
||
}
|
||
return null;
|
||
}
|
||
|
||
// Codexの応答かどうか判定
|
||
isCodexResponse(text) {
|
||
// Working状態でない、プロンプトでない、十分な長さ
|
||
return !text.includes('Working') &&
|
||
!text.includes('▌') &&
|
||
text.length > 20 &&
|
||
!text.includes('⏎ send');
|
||
}
|
||
|
||
// 停止
|
||
stop() {
|
||
this.isRunning = false;
|
||
if (this.ws) {
|
||
this.ws.close();
|
||
}
|
||
console.log('🛑 Bridge stopped');
|
||
}
|
||
}
|
||
|
||
// メイン実行
|
||
if (require.main === module) {
|
||
const bridge = new ClaudeCodexUnifiedBridge();
|
||
|
||
// イベントリスナー
|
||
bridge.on('codex-to-claude', (content) => {
|
||
console.log('📊 Transferred to Claude:', content.substring(0, 50) + '...');
|
||
});
|
||
|
||
// 開始
|
||
bridge.start().catch(console.error);
|
||
|
||
// 終了処理
|
||
process.on('SIGINT', () => {
|
||
console.log('\n👋 Shutting down...');
|
||
bridge.stop();
|
||
process.exit(0);
|
||
});
|
||
}
|
||
|
||
module.exports = ClaudeCodexUnifiedBridge; |