【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>
439 lines
10 KiB
JavaScript
439 lines
10 KiB
JavaScript
// 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 };
|