150 lines
3.9 KiB
JavaScript
150 lines
3.9 KiB
JavaScript
|
|
// codex-output-watcher.js
|
||
|
|
// Codexの出力を監視してClaudeに転送するウォッチャー
|
||
|
|
|
||
|
|
const { spawn } = require('child_process');
|
||
|
|
const EventEmitter = require('events');
|
||
|
|
|
||
|
|
class CodexOutputWatcher extends EventEmitter {
|
||
|
|
constructor(sessionName = 'codex-safe') {
|
||
|
|
super();
|
||
|
|
this.sessionName = sessionName;
|
||
|
|
this.lastOutput = '';
|
||
|
|
this.isWorking = false;
|
||
|
|
this.watchInterval = null;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 監視開始
|
||
|
|
start(intervalMs = 1000) {
|
||
|
|
console.log(`👁️ Starting to watch Codex output in ${this.sessionName}...`);
|
||
|
|
|
||
|
|
this.watchInterval = setInterval(() => {
|
||
|
|
this.checkOutput();
|
||
|
|
}, intervalMs);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 監視停止
|
||
|
|
stop() {
|
||
|
|
if (this.watchInterval) {
|
||
|
|
clearInterval(this.watchInterval);
|
||
|
|
this.watchInterval = null;
|
||
|
|
console.log('👁️ Stopped watching');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 画面をキャプチャして状態を確認
|
||
|
|
async checkOutput() {
|
||
|
|
try {
|
||
|
|
const output = await this.capturePane();
|
||
|
|
|
||
|
|
// 状態を解析
|
||
|
|
const wasWorking = this.isWorking;
|
||
|
|
this.isWorking = this.detectWorking(output);
|
||
|
|
|
||
|
|
// Working → 完了に変化した場合
|
||
|
|
if (wasWorking && !this.isWorking) {
|
||
|
|
console.log('✅ Codex finished working!');
|
||
|
|
const response = this.extractCodexResponse(output);
|
||
|
|
if (response) {
|
||
|
|
this.emit('response', response);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// プロンプトが表示されている = 入力待ち
|
||
|
|
if (this.detectPrompt(output) && !this.isWorking) {
|
||
|
|
this.emit('ready');
|
||
|
|
}
|
||
|
|
|
||
|
|
this.lastOutput = output;
|
||
|
|
} catch (err) {
|
||
|
|
console.error('❌ Watch error:', err);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// tmuxペインをキャプチャ
|
||
|
|
capturePane() {
|
||
|
|
return new Promise((resolve, reject) => {
|
||
|
|
const proc = spawn('tmux', ['capture-pane', '-t', this.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}`));
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// "Working" 状態を検出
|
||
|
|
detectWorking(output) {
|
||
|
|
return output.includes('Working (') || output.includes('⏳');
|
||
|
|
}
|
||
|
|
|
||
|
|
// プロンプト(入力待ち)を検出
|
||
|
|
detectPrompt(output) {
|
||
|
|
return output.includes('▌') && output.includes('⏎ send');
|
||
|
|
}
|
||
|
|
|
||
|
|
// Codexの応答を抽出
|
||
|
|
extractCodexResponse(output) {
|
||
|
|
const lines = output.split('\n');
|
||
|
|
let inCodexResponse = false;
|
||
|
|
let response = [];
|
||
|
|
|
||
|
|
for (let i = 0; i < lines.length; i++) {
|
||
|
|
const line = lines[i];
|
||
|
|
|
||
|
|
// "codex" ラベルを見つけたら応答開始
|
||
|
|
if (line.trim() === 'codex') {
|
||
|
|
inCodexResponse = true;
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 次のプロンプトや"user"が来たら終了
|
||
|
|
if (inCodexResponse && (line.includes('▌') || line.trim() === 'user')) {
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 応答を収集
|
||
|
|
if (inCodexResponse && line.trim()) {
|
||
|
|
// Working行やメタ情報を除外
|
||
|
|
if (!line.includes('Working') && !line.includes('⏎ send')) {
|
||
|
|
response.push(line);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return response.join('\n').trim();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 使用例とテスト
|
||
|
|
if (require.main === module) {
|
||
|
|
const watcher = new CodexOutputWatcher();
|
||
|
|
|
||
|
|
watcher.on('response', (response) => {
|
||
|
|
console.log('\n📝 Codex Response:');
|
||
|
|
console.log('-------------------');
|
||
|
|
console.log(response);
|
||
|
|
console.log('-------------------\n');
|
||
|
|
|
||
|
|
// ここでClaudeに転送する処理を追加
|
||
|
|
console.log('🚀 TODO: Send this to Claude!');
|
||
|
|
});
|
||
|
|
|
||
|
|
watcher.on('ready', () => {
|
||
|
|
console.log('💚 Codex is ready for input');
|
||
|
|
});
|
||
|
|
|
||
|
|
watcher.start(500); // 500msごとにチェック
|
||
|
|
|
||
|
|
// 30秒後に停止
|
||
|
|
setTimeout(() => {
|
||
|
|
watcher.stop();
|
||
|
|
process.exit(0);
|
||
|
|
}, 30000);
|
||
|
|
}
|
||
|
|
|
||
|
|
module.exports = CodexOutputWatcher;
|