AI協調開発研究ドキュメントの完成と Phase 10.9-β 進捗
【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>
This commit is contained in:
438
tools/codex-tmux-driver/codex-claude-bridge.js
Normal file
438
tools/codex-tmux-driver/codex-claude-bridge.js
Normal file
@ -0,0 +1,438 @@
|
||||
// codex-claude-bridge.js
|
||||
// Codex と Claude を自動的に橋渡しするシステム
|
||||
// 安全装置と制御機能付き
|
||||
|
||||
const WebSocket = require('ws');
|
||||
const fs = require('fs').promises;
|
||||
const path = require('path');
|
||||
|
||||
// 設定
|
||||
const CONFIG = {
|
||||
codexWs: 'ws://localhost:8766',
|
||||
claudeApiUrl: process.env.CLAUDE_API_URL || 'http://localhost:8080/claude', // 要実装
|
||||
bridgePort: 8768,
|
||||
|
||||
// 安全設定
|
||||
maxBridgesPerHour: 50,
|
||||
cooldownMs: 5000,
|
||||
idleTimeoutMs: 30000,
|
||||
contextWindowSize: 5, // 最後のN個のメッセージを含める
|
||||
|
||||
// ログ設定
|
||||
logDir: './bridge-logs',
|
||||
enableLogging: true
|
||||
};
|
||||
|
||||
// 検出ボックス
|
||||
class DetectionBox {
|
||||
constructor() {
|
||||
this.patterns = {
|
||||
question: /\?$|どうしますか|どう思いますか|教えて|どうすれば|何が/,
|
||||
waiting: /waiting|待機中|入力待ち|▌/i,
|
||||
stuck: /エラー|失敗|できません|わかりません|困った/,
|
||||
needHelp: /助けて|ヘルプ|相談|アドバイス/,
|
||||
planning: /次は|つぎは|計画|予定/
|
||||
};
|
||||
|
||||
this.lastActivity = Date.now();
|
||||
this.quietPeriods = [];
|
||||
}
|
||||
|
||||
analyze(output) {
|
||||
const now = Date.now();
|
||||
const idleTime = now - this.lastActivity;
|
||||
|
||||
// 複数のパターンをチェックしてスコアリング
|
||||
let score = 0;
|
||||
let reasons = [];
|
||||
|
||||
for (const [type, pattern] of Object.entries(this.patterns)) {
|
||||
if (pattern.test(output)) {
|
||||
score += 0.3;
|
||||
reasons.push(type);
|
||||
}
|
||||
}
|
||||
|
||||
if (idleTime > CONFIG.idleTimeoutMs) {
|
||||
score += 0.5;
|
||||
reasons.push('idle');
|
||||
}
|
||||
|
||||
// 最後のアクティビティを更新
|
||||
this.lastActivity = now;
|
||||
|
||||
return {
|
||||
shouldBridge: score >= 0.5,
|
||||
confidence: Math.min(score, 1.0),
|
||||
reasons,
|
||||
idleTime
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// フィルターボックス
|
||||
class FilterBox {
|
||||
constructor() {
|
||||
this.safetyRules = {
|
||||
blocked: [
|
||||
/password|secret|token|key|credential/i,
|
||||
/rm -rf|delete all|destroy|drop database/i,
|
||||
/private|confidential|機密|秘密/i
|
||||
],
|
||||
|
||||
requireConfirm: [
|
||||
/production|本番|live environment/i,
|
||||
/payment|billing|課金|money/i,
|
||||
/critical|breaking change|重要な変更/i
|
||||
],
|
||||
|
||||
allowed: [
|
||||
/実装|implement|設計|design|architecture/,
|
||||
/error|bug|fix|修正|デバッグ/,
|
||||
/suggest|proposal|提案|アイデア/,
|
||||
/explain|説明|なぜ|どうして/
|
||||
]
|
||||
};
|
||||
|
||||
this.contextPatterns = {
|
||||
jit: /JIT|cranelift|compile|lower/i,
|
||||
box: /Box|箱|カプセル|Everything is Box/i,
|
||||
architecture: /設計|アーキテクチャ|構造|structure/i
|
||||
};
|
||||
}
|
||||
|
||||
filter(content, context = []) {
|
||||
// 危険なコンテンツチェック
|
||||
for (const pattern of this.safetyRules.blocked) {
|
||||
if (pattern.test(content)) {
|
||||
return {
|
||||
allow: false,
|
||||
reason: 'blocked-content',
|
||||
action: 'reject'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 確認が必要なコンテンツ
|
||||
for (const pattern of this.safetyRules.requireConfirm) {
|
||||
if (pattern.test(content)) {
|
||||
return {
|
||||
allow: false,
|
||||
reason: 'requires-confirmation',
|
||||
action: 'queue'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// コンテキストスコアリング
|
||||
let contextScore = 0;
|
||||
for (const [type, pattern] of Object.entries(this.contextPatterns)) {
|
||||
if (pattern.test(content)) {
|
||||
contextScore += 0.3;
|
||||
}
|
||||
}
|
||||
|
||||
// 許可されたパターン
|
||||
for (const pattern of this.safetyRules.allowed) {
|
||||
if (pattern.test(content)) {
|
||||
return {
|
||||
allow: true,
|
||||
confidence: Math.min(0.5 + contextScore, 1.0),
|
||||
action: 'forward'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// デフォルトは確認待ち
|
||||
return {
|
||||
allow: false,
|
||||
reason: 'no-pattern-match',
|
||||
action: 'queue'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// ブリッジボックス
|
||||
class BridgeBox {
|
||||
constructor() {
|
||||
this.detection = new DetectionBox();
|
||||
this.filter = new FilterBox();
|
||||
|
||||
this.state = {
|
||||
active: false,
|
||||
bridgeCount: 0,
|
||||
lastBridge: 0,
|
||||
queue: [],
|
||||
history: []
|
||||
};
|
||||
|
||||
this.stats = {
|
||||
total: 0,
|
||||
forwarded: 0,
|
||||
blocked: 0,
|
||||
queued: 0
|
||||
};
|
||||
}
|
||||
|
||||
async start() {
|
||||
console.log('🌉 Starting Codex-Claude Bridge...');
|
||||
|
||||
// ログディレクトリ作成
|
||||
if (CONFIG.enableLogging) {
|
||||
await fs.mkdir(CONFIG.logDir, { recursive: true });
|
||||
}
|
||||
|
||||
// Codexに接続
|
||||
this.connectToCodex();
|
||||
|
||||
// 管理用WebSocketサーバー
|
||||
this.startControlServer();
|
||||
|
||||
this.state.active = true;
|
||||
console.log('✅ Bridge is active');
|
||||
}
|
||||
|
||||
connectToCodex() {
|
||||
this.codexWs = new WebSocket(CONFIG.codexWs);
|
||||
|
||||
this.codexWs.on('open', () => {
|
||||
console.log('📡 Connected to Codex');
|
||||
});
|
||||
|
||||
this.codexWs.on('message', async (data) => {
|
||||
const msg = JSON.parse(data);
|
||||
|
||||
if (msg.type === 'codex-event' || msg.type === 'codex-output') {
|
||||
await this.handleCodexOutput(msg);
|
||||
}
|
||||
});
|
||||
|
||||
this.codexWs.on('error', (err) => {
|
||||
console.error('❌ Codex connection error:', err);
|
||||
});
|
||||
}
|
||||
|
||||
async handleCodexOutput(msg) {
|
||||
this.stats.total++;
|
||||
|
||||
// 停止検出
|
||||
const detection = this.detection.analyze(msg.data);
|
||||
|
||||
if (!detection.shouldBridge) {
|
||||
return;
|
||||
}
|
||||
|
||||
// フィルタリング
|
||||
const filterResult = this.filter.filter(msg.data, this.state.history);
|
||||
|
||||
if (!filterResult.allow) {
|
||||
if (filterResult.action === 'queue') {
|
||||
this.queueForReview(msg, filterResult);
|
||||
} else {
|
||||
this.stats.blocked++;
|
||||
console.log(`🚫 Blocked: ${filterResult.reason}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// クールダウンチェック
|
||||
if (!this.canBridge()) {
|
||||
this.queueForReview(msg, { reason: 'cooldown' });
|
||||
return;
|
||||
}
|
||||
|
||||
// ブリッジ実行
|
||||
await this.bridge(msg);
|
||||
}
|
||||
|
||||
canBridge() {
|
||||
const now = Date.now();
|
||||
|
||||
// クールダウン
|
||||
if (now - this.state.lastBridge < CONFIG.cooldownMs) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// レート制限
|
||||
const hourAgo = now - 3600000;
|
||||
const recentBridges = this.state.history.filter(h => h.timestamp > hourAgo);
|
||||
if (recentBridges.length >= CONFIG.maxBridgesPerHour) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async bridge(msg) {
|
||||
console.log('🌉 Bridging to Claude...');
|
||||
|
||||
try {
|
||||
// コンテキスト構築
|
||||
const context = this.buildContext(msg);
|
||||
|
||||
// Claude API呼び出し(要実装)
|
||||
const claudeResponse = await this.callClaudeAPI(context);
|
||||
|
||||
// Codexに返信
|
||||
this.sendToCodex(claudeResponse);
|
||||
|
||||
// 記録
|
||||
this.recordBridge(msg, claudeResponse);
|
||||
|
||||
this.stats.forwarded++;
|
||||
this.state.lastBridge = Date.now();
|
||||
|
||||
} catch (err) {
|
||||
console.error('❌ Bridge error:', err);
|
||||
}
|
||||
}
|
||||
|
||||
buildContext(currentMsg) {
|
||||
// 最近の履歴を含める
|
||||
const recentHistory = this.state.history.slice(-CONFIG.contextWindowSize);
|
||||
|
||||
return {
|
||||
current: currentMsg.data,
|
||||
history: recentHistory.map(h => ({
|
||||
from: h.from,
|
||||
content: h.content,
|
||||
timestamp: h.timestamp
|
||||
})),
|
||||
context: {
|
||||
project: 'Nyash JIT Development',
|
||||
focus: 'Phase 10.7 - JIT Branch Wiring',
|
||||
recentTopics: this.extractTopics(recentHistory)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async callClaudeAPI(context) {
|
||||
// TODO: 実際のClaude API実装
|
||||
// ここはプレースホルダー
|
||||
return {
|
||||
response: "Claude's response would go here",
|
||||
confidence: 0.9
|
||||
};
|
||||
}
|
||||
|
||||
sendToCodex(response) {
|
||||
this.codexWs.send(JSON.stringify({
|
||||
op: 'send',
|
||||
data: response.response
|
||||
}));
|
||||
}
|
||||
|
||||
queueForReview(msg, reason) {
|
||||
this.state.queue.push({
|
||||
message: msg,
|
||||
reason,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
|
||||
this.stats.queued++;
|
||||
console.log(`📋 Queued for review: ${reason.reason}`);
|
||||
}
|
||||
|
||||
recordBridge(input, output) {
|
||||
const record = {
|
||||
timestamp: Date.now(),
|
||||
from: 'codex',
|
||||
to: 'claude',
|
||||
input: input.data,
|
||||
output: output.response,
|
||||
confidence: output.confidence
|
||||
};
|
||||
|
||||
this.state.history.push(record);
|
||||
this.state.bridgeCount++;
|
||||
|
||||
// ログ保存
|
||||
if (CONFIG.enableLogging) {
|
||||
this.saveLog(record);
|
||||
}
|
||||
}
|
||||
|
||||
async saveLog(record) {
|
||||
const filename = `bridge-${new Date().toISOString().split('T')[0]}.jsonl`;
|
||||
const filepath = path.join(CONFIG.logDir, filename);
|
||||
|
||||
await fs.appendFile(
|
||||
filepath,
|
||||
JSON.stringify(record) + '\n'
|
||||
);
|
||||
}
|
||||
|
||||
extractTopics(history) {
|
||||
// 最近の話題を抽出
|
||||
const topics = new Set();
|
||||
|
||||
history.forEach(h => {
|
||||
if (/JIT|cranelift/i.test(h.content)) topics.add('JIT');
|
||||
if (/box|箱/i.test(h.content)) topics.add('Box Philosophy');
|
||||
if (/PHI|branch/i.test(h.content)) topics.add('Control Flow');
|
||||
});
|
||||
|
||||
return Array.from(topics);
|
||||
}
|
||||
|
||||
startControlServer() {
|
||||
// 管理用WebSocketサーバー
|
||||
const wss = new WebSocket.Server({ port: CONFIG.bridgePort });
|
||||
|
||||
wss.on('connection', (ws) => {
|
||||
ws.on('message', (data) => {
|
||||
const cmd = JSON.parse(data);
|
||||
|
||||
switch (cmd.op) {
|
||||
case 'status':
|
||||
ws.send(JSON.stringify({
|
||||
type: 'status',
|
||||
state: this.state,
|
||||
stats: this.stats
|
||||
}));
|
||||
break;
|
||||
|
||||
case 'queue':
|
||||
ws.send(JSON.stringify({
|
||||
type: 'queue',
|
||||
items: this.state.queue
|
||||
}));
|
||||
break;
|
||||
|
||||
case 'approve':
|
||||
// キューから承認して転送
|
||||
if (cmd.id && this.state.queue[cmd.id]) {
|
||||
const item = this.state.queue[cmd.id];
|
||||
this.bridge(item.message);
|
||||
this.state.queue.splice(cmd.id, 1);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'toggle':
|
||||
this.state.active = !this.state.active;
|
||||
ws.send(JSON.stringify({
|
||||
type: 'toggled',
|
||||
active: this.state.active
|
||||
}));
|
||||
break;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
console.log(`🎮 Control server on ws://localhost:${CONFIG.bridgePort}`);
|
||||
}
|
||||
}
|
||||
|
||||
// メイン
|
||||
if (require.main === module) {
|
||||
const bridge = new BridgeBox();
|
||||
bridge.start();
|
||||
|
||||
// グレースフルシャットダウン
|
||||
process.on('SIGINT', () => {
|
||||
console.log('\n👋 Shutting down bridge...');
|
||||
process.exit(0);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { BridgeBox, DetectionBox, FilterBox };
|
||||
Reference in New Issue
Block a user