Files
hakorune/tools/codex-tmux-driver/common-message-file.js

184 lines
4.9 KiB
JavaScript
Raw Permalink Normal View History

// common-message-file.js
// 共通テキストファイル経由の通信システム
const fs = require('fs');
const path = require('path');
const EventEmitter = require('events');
class FileBasedMessenger extends EventEmitter {
constructor(config = {}) {
super();
this.config = {
messageFile: config.messageFile || './shared-messages.txt',
lockFile: config.lockFile || './shared-messages.lock',
pollInterval: config.pollInterval || 500,
...config
};
this.lastReadPosition = 0;
this.isWatching = false;
}
// メッセージを書き込む
async sendMessage(from, to, message) {
const timestamp = new Date().toISOString();
const entry = JSON.stringify({
timestamp,
from,
to,
message
}) + '\n';
// ロックを取得
await this.acquireLock();
try {
// ファイルに追記
fs.appendFileSync(this.config.messageFile, entry);
console.log(`📤 Sent: ${from}${to}: ${message}`);
} finally {
// ロック解放
this.releaseLock();
}
}
// メッセージを監視
startWatching(myName) {
this.isWatching = true;
console.log(`👁️ Watching messages for: ${myName}`);
// 初期位置を設定
if (fs.existsSync(this.config.messageFile)) {
const stats = fs.statSync(this.config.messageFile);
this.lastReadPosition = stats.size;
}
// 定期的にチェック
const checkMessages = () => {
if (!this.isWatching) return;
try {
if (!fs.existsSync(this.config.messageFile)) {
setTimeout(checkMessages, this.config.pollInterval);
return;
}
const stats = fs.statSync(this.config.messageFile);
if (stats.size > this.lastReadPosition) {
// 新しいメッセージがある
const buffer = Buffer.alloc(stats.size - this.lastReadPosition);
const fd = fs.openSync(this.config.messageFile, 'r');
fs.readSync(fd, buffer, 0, buffer.length, this.lastReadPosition);
fs.closeSync(fd);
const newLines = buffer.toString().trim().split('\n');
for (const line of newLines) {
if (line) {
try {
const msg = JSON.parse(line);
// 自分宛のメッセージ
if (msg.to === myName || msg.to === '*') {
this.emit('message', msg);
console.log(`📨 Received: ${msg.from}${msg.to}: ${msg.message}`);
}
} catch (e) {
console.error('Parse error:', e);
}
}
}
this.lastReadPosition = stats.size;
}
} catch (err) {
console.error('Watch error:', err);
}
setTimeout(checkMessages, this.config.pollInterval);
};
checkMessages();
}
// 監視停止
stopWatching() {
this.isWatching = false;
console.log('🛑 Stopped watching');
}
// 簡易ロック機構
async acquireLock(maxWait = 5000) {
const startTime = Date.now();
while (fs.existsSync(this.config.lockFile)) {
if (Date.now() - startTime > maxWait) {
throw new Error('Lock timeout');
}
await this.sleep(50);
}
fs.writeFileSync(this.config.lockFile, process.pid.toString());
}
releaseLock() {
if (fs.existsSync(this.config.lockFile)) {
fs.unlinkSync(this.config.lockFile);
}
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// メッセージ履歴をクリア
clearMessages() {
if (fs.existsSync(this.config.messageFile)) {
fs.unlinkSync(this.config.messageFile);
}
console.log('🗑️ Message history cleared');
}
}
// CLIとして使用
if (require.main === module) {
const messenger = new FileBasedMessenger();
const myName = process.argv[2];
const command = process.argv[3];
if (!myName || !command) {
console.log(`
使い方:
node common-message-file.js <名前> watch # メッセージを監視
node common-message-file.js <名前> send <宛先> <内容> # メッセージ送信
node common-message-file.js <名前> clear # 履歴クリア
`);
process.exit(1);
}
switch (command) {
case 'watch':
messenger.on('message', (msg) => {
// 自動返信(デモ用)
if (msg.message.includes('?')) {
setTimeout(() => {
messenger.sendMessage(myName, msg.from, 'はい、了解しました!');
}, 1000);
}
});
messenger.startWatching(myName);
console.log('Press Ctrl+C to stop...');
break;
case 'send':
const to = process.argv[4];
const message = process.argv.slice(5).join(' ');
messenger.sendMessage(myName, to, message);
break;
case 'clear':
messenger.clearMessages();
break;
}
}
module.exports = FileBasedMessenger;