pdca
Version:
🎯 AI 驅動的 PDCA 多代理開發系統 - 智能循環控制 + 成本管理 + Token 優化 + 多 AI 引擎支援
272 lines • 9.67 kB
JavaScript
/**
* 通訊管理器
* 負責代理間的訊息傳遞和檔案系統 IPC
*/
import { EventEmitter } from 'events';
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, unlinkSync, unwatchFile, statSync } from 'fs';
import { join } from 'path';
import { MessageStatus, MessageSerializer, MessageValidator } from './message-protocol.js';
export class CommunicationManager extends EventEmitter {
config;
watchedFiles = new Set();
processedMessages = new Set();
messageQueue = new Map();
constructor(config = {}) {
super();
this.config = {
baseDir: config.baseDir || '.raiy-pdca/communication',
pollingInterval: config.pollingInterval || 1000,
messageRetention: config.messageRetention || 3600000 // 1 小時
};
this.initializeDirectories();
}
initializeDirectories() {
const dirs = [
this.config.baseDir,
join(this.config.baseDir, 'inbox'),
join(this.config.baseDir, 'outbox'),
join(this.config.baseDir, 'processed'),
join(this.config.baseDir, 'broadcasts')
];
dirs.forEach(dir => {
if (!existsSync(dir)) {
mkdirSync(dir, { recursive: true });
}
});
}
/**
* 發送訊息
*/
async sendMessage(message) {
// 設定狀態為待處理
message.status = MessageStatus.PENDING;
// 序列化訊息
const serialized = MessageSerializer.serialize(message);
// 決定儲存位置
if (message.to === 'all') {
// 廣播訊息
const broadcastPath = join(this.config.baseDir, 'broadcasts', `${message.id}.json`);
writeFileSync(broadcastPath, serialized);
}
else {
// 點對點訊息
const inboxPath = join(this.config.baseDir, 'inbox', message.to);
if (!existsSync(inboxPath)) {
mkdirSync(inboxPath);
}
const messagePath = join(inboxPath, `${message.id}.json`);
writeFileSync(messagePath, serialized);
}
// 發送事件
this.emit('message-sent', message);
}
/**
* 開始監聽訊息
*/
startListening(agentRole) {
// 監聽個人收件匣
const inboxPath = join(this.config.baseDir, 'inbox', agentRole);
if (!existsSync(inboxPath)) {
mkdirSync(inboxPath);
}
this.watchDirectory(inboxPath, agentRole);
// 監聽廣播
const broadcastPath = join(this.config.baseDir, 'broadcasts');
this.watchDirectory(broadcastPath, agentRole);
// 定期清理過期訊息
setInterval(() => this.cleanupOldMessages(), 60000); // 每分鐘清理一次
}
/**
* 停止監聽
*/
stopListening() {
this.watchedFiles.forEach(file => {
unwatchFile(file);
});
this.watchedFiles.clear();
}
/**
* 監聽目錄變化
*/
watchDirectory(dirPath, agentRole) {
// 初始掃描
this.scanDirectory(dirPath, agentRole);
// 定期掃描新檔案
setInterval(() => {
this.scanDirectory(dirPath, agentRole);
}, this.config.pollingInterval);
}
/**
* 掃描目錄中的訊息
*/
scanDirectory(dirPath, agentRole) {
if (!existsSync(dirPath))
return;
const files = readdirSync(dirPath).filter(f => f.endsWith('.json'));
files.forEach(file => {
const filePath = join(dirPath, file);
const messageId = file.replace('.json', '');
// 跳過已處理的訊息
if (this.processedMessages.has(messageId))
return;
try {
const content = readFileSync(filePath, 'utf-8');
const message = MessageSerializer.deserialize(content);
if (MessageValidator.validate(message)) {
// 標記為已處理
this.processedMessages.add(messageId);
// 更新狀態為已接收
message.status = MessageStatus.RECEIVED;
// 發送事件
this.emit('message-received', message, agentRole);
// 移動到已處理目錄
this.moveToProcessed(filePath, message);
}
}
catch (error) {
console.error(`處理訊息失敗: ${file}`, error);
}
});
}
/**
* 移動訊息到已處理目錄
*/
moveToProcessed(filePath, message) {
const processedDir = join(this.config.baseDir, 'processed', message.to);
if (!existsSync(processedDir)) {
mkdirSync(processedDir, { recursive: true });
}
const processedPath = join(processedDir, `${message.id}.json`);
const processedMessage = {
...message,
status: MessageStatus.COMPLETED
};
const content = MessageSerializer.serialize(processedMessage);
writeFileSync(processedPath, content);
unlinkSync(filePath);
}
/**
* 清理過期訊息
*/
cleanupOldMessages() {
const processedDir = join(this.config.baseDir, 'processed');
if (!existsSync(processedDir))
return;
const now = Date.now();
this.cleanupDirectory(processedDir, now);
}
cleanupDirectory(dirPath, now) {
const items = readdirSync(dirPath);
items.forEach(item => {
const itemPath = join(dirPath, item);
const stat = statSync(itemPath);
if (stat.isDirectory()) {
this.cleanupDirectory(itemPath, now);
}
else if (stat.isFile() && item.endsWith('.json')) {
if (now - stat.mtimeMs > this.config.messageRetention) {
unlinkSync(itemPath);
}
}
});
}
/**
* 取得代理的未讀訊息
*/
getUnreadMessages(agentRole) {
const messages = [];
// 檢查個人收件匣
const inboxPath = join(this.config.baseDir, 'inbox', agentRole);
if (existsSync(inboxPath)) {
const files = readdirSync(inboxPath).filter(f => f.endsWith('.json'));
files.forEach(file => {
try {
const content = readFileSync(join(inboxPath, file), 'utf-8');
const message = MessageSerializer.deserialize(content);
if (MessageValidator.validate(message)) {
messages.push(message);
}
}
catch (error) {
console.error(`讀取訊息失敗: ${file}`, error);
}
});
}
// 檢查廣播
const broadcastPath = join(this.config.baseDir, 'broadcasts');
if (existsSync(broadcastPath)) {
const files = readdirSync(broadcastPath).filter(f => f.endsWith('.json'));
files.forEach(file => {
const messageId = file.replace('.json', '');
if (!this.processedMessages.has(messageId)) {
try {
const content = readFileSync(join(broadcastPath, file), 'utf-8');
const message = MessageSerializer.deserialize(content);
if (MessageValidator.validate(message)) {
messages.push(message);
}
}
catch (error) {
console.error(`讀取廣播失敗: ${file}`, error);
}
}
});
}
return messages;
}
/**
* 回覆訊息
*/
async replyToMessage(originalMessage, reply) {
const replyMessage = {
...reply,
id: `reply_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
timestamp: new Date().toISOString(),
replyTo: originalMessage.id,
to: originalMessage.from
};
await this.sendMessage(replyMessage);
}
/**
* 廣播訊息給所有代理
*/
async broadcastMessage(message) {
const broadcastMessage = {
...message,
to: 'all'
};
await this.sendMessage(broadcastMessage);
}
/**
* 取得通訊統計
*/
getStatistics() {
let totalSent = 0;
let totalReceived = this.processedMessages.size;
let pendingMessages = 0;
// 計算待處理訊息
const inboxDir = join(this.config.baseDir, 'inbox');
if (existsSync(inboxDir)) {
const agents = readdirSync(inboxDir);
agents.forEach(agent => {
const agentInbox = join(inboxDir, agent);
if (statSync(agentInbox).isDirectory()) {
pendingMessages += readdirSync(agentInbox).filter(f => f.endsWith('.json')).length;
}
});
}
// 計算已發送訊息
const outboxDir = join(this.config.baseDir, 'outbox');
if (existsSync(outboxDir)) {
totalSent = readdirSync(outboxDir).filter(f => f.endsWith('.json')).length;
}
return {
totalSent,
totalReceived,
pendingMessages,
processedMessages: this.processedMessages.size
};
}
}
export default CommunicationManager;
//# sourceMappingURL=communication-manager.js.map