Files
hakorune/tools/codex-tmux-driver/claude-hook-wrapper.js
Moe Charm 4e1b595796 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>
2025-08-28 12:09:09 +09:00

178 lines
4.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env node
// claude-hook-wrapper.js
// Claudeバイナリにフックをかけて入出力を横取りするラッパー
const { spawn } = require('child_process');
const path = require('path');
let WebSocket;
try {
WebSocket = require('ws');
} catch (e) {
console.error('FATAL: Cannot find module "ws"');
console.error('Hint: run "npm install" inside tools/codex-tmux-driver');
process.exit(1);
}
const fs = require('fs');
// 設定
const REAL_CLAUDE = process.env.CLAUDE_REAL_BIN || '/home/tomoaki/.volta/tools/image/node/22.16.0/bin/claude';
const HOOK_SERVER = process.env.CLAUDE_HOOK_SERVER || 'ws://localhost:8770';
const LOG_FILE = process.env.CLAUDE_LOG_FILE || '/tmp/claude-hook.log';
const ENABLE_HOOK = process.env.CLAUDE_HOOK_ENABLE !== 'false';
const USE_SCRIPT_PTY = process.env.CLAUDE_USE_SCRIPT_PTY !== 'false'; // デフォルトでPTY有効
// WebSocket接続
let ws = null;
if (ENABLE_HOOK) {
console.error(`[claude-hook] Attempting to connect to ${HOOK_SERVER}...`);
try {
ws = new WebSocket(HOOK_SERVER);
ws.on('open', () => {
log('hook-connect', { url: HOOK_SERVER });
console.error(`[claude-hook] ✅ Successfully connected to ${HOOK_SERVER}`);
});
ws.on('error', (e) => {
console.error(`[claude-hook] ❌ Connection error: ${e?.message || e}`);
});
ws.on('close', () => {
console.error(`[claude-hook] 🔌 Connection closed`);
});
} catch (e) {
console.error(`[claude-hook] ❌ Failed to create WebSocket: ${e}`);
}
}
// ログ関数
function log(type, data) {
const timestamp = new Date().toISOString();
const logEntry = { timestamp, type, data };
// ファイルログ
fs.appendFileSync(LOG_FILE, JSON.stringify(logEntry) + '\n');
// WebSocket送信
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(logEntry));
}
}
// Claudeプロセス起動
if (!fs.existsSync(REAL_CLAUDE)) {
console.error(`FATAL: REAL_CLAUDE not found: ${REAL_CLAUDE}`);
console.error('Set CLAUDE_REAL_BIN to the real Claude binary path.');
process.exit(1);
}
// 引数を渡してClaude起動
const userArgs = process.argv.slice(2);
// script(1) を使って擬似TTY経由で起動Claudeはインタラクティブモードに必要
function shEscape(s) { return `'${String(s).replace(/'/g, `'\\''`)}'`; }
let claudeProcess;
let usingPty = false;
try {
if (USE_SCRIPT_PTY) {
const cmdStr = [REAL_CLAUDE, ...userArgs].map(shEscape).join(' ');
// -q: quiet, -f: flush, -e: return child exit code, -c: command
claudeProcess = spawn('script', ['-qfec', cmdStr, '/dev/null'], {
stdio: ['pipe', 'pipe', 'pipe'],
env: process.env
});
usingPty = true;
log('start-info', { mode: 'pty(script)', cmd: cmdStr });
}
} catch (e) {
// フォールバック
console.error(`[claude-hook] PTY spawn failed: ${e.message}`);
}
if (!claudeProcess) {
claudeProcess = spawn(REAL_CLAUDE, userArgs, {
stdio: ['pipe', 'pipe', 'pipe'],
env: process.env
});
log('start-info', { mode: 'pipe', bin: REAL_CLAUDE, args: userArgs });
}
// 標準入力をClaudeへ
process.stdin.on('data', (chunk) => {
const input = chunk.toString();
log('input', input);
claudeProcess.stdin.write(chunk);
});
// 標準出力
let outputBuffer = '';
claudeProcess.stdout.on('data', (chunk) => {
const data = chunk.toString();
outputBuffer += data;
// 改行で区切って出力をログ
if (data.includes('\n')) {
log('output', outputBuffer);
outputBuffer = '';
}
process.stdout.write(chunk);
});
// エラー出力
claudeProcess.stderr.on('data', (chunk) => {
log('error', chunk.toString());
process.stderr.write(chunk);
});
// プロセス終了
claudeProcess.on('exit', (code, signal) => {
log('exit', { code, signal });
if (ws) {
try {
ws.send(JSON.stringify({
type: 'hook-event',
event: 'claude-exit',
data: { code, signal }
}));
} catch {}
ws.close();
}
process.exit(typeof code === 'number' ? code : 0);
});
// シグナルハンドリング
process.on('SIGINT', () => {
claudeProcess.kill('SIGINT');
});
process.on('SIGTERM', () => {
claudeProcess.kill('SIGTERM');
});
// WebSocketからのメッセージ受信
if (ws) {
ws.on('message', (data) => {
try {
const cmd = JSON.parse(data.toString());
if (cmd.type === 'inject-input') {
// Claudeに入力を注入
log('inject', cmd.data);
claudeProcess.stdin.write(cmd.data + '\n');
}
} catch (e) {
console.error(`[claude-hook] ❌ Error parsing message: ${e}`);
}
});
}
// 起動ログ
log('start', {
args: userArgs,
pid: process.pid,
hookEnabled: ENABLE_HOOK,
usingPty
});
console.error(`[claude-hook] active (pty=${usingPty ? 'on' : 'off'}) REAL_CLAUDE=${REAL_CLAUDE}`);