heartbeat-service.js
3.99 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
const fs = require('fs');
const path = require('path');
const http = require('http');
const config = require('../config');
const DATA_DIR = path.resolve(__dirname, '../../data/agents');
class HeartbeatService {
constructor() {
this.timers = new Map(); // agentId -> NodeJS.Timeout
this.port = config.port || 3000;
}
/**
* 初始化并启动全局的心跳监控
*/
start() {
console.log('[Heartbeat] 💓 开始初始化 Agent 心跳服务...');
if (!fs.existsSync(DATA_DIR)) return;
const agents = fs.readdirSync(DATA_DIR).filter(f => {
const p = path.join(DATA_DIR, f);
return fs.lstatSync(p).isDirectory() && fs.existsSync(path.join(p, 'config.json'));
});
for (const agentId of agents) {
this._setupAgentHeartbeat(agentId);
}
}
/**
* 停止所有心跳
*/
stop() {
for (const [agentId, timer] of this.timers.entries()) {
clearInterval(timer);
}
this.timers.clear();
console.log('[Heartbeat] 🛑 心跳服务已停止');
}
/**
* 为单个 Agent 根据 config 设置心跳
*/
_setupAgentHeartbeat(agentId) {
try {
const configPath = path.join(DATA_DIR, agentId, 'config.json');
const cfg = JSON.parse(fs.readFileSync(configPath, 'utf8'));
if (cfg.heartbeat && cfg.heartbeat.enabled === true) {
const intervalMs = (cfg.heartbeat.intervalMinutes || 15) * 60 * 1000;
// 确保不会重复启动
if (this.timers.has(agentId)) {
clearInterval(this.timers.get(agentId));
}
const timer = setInterval(() => this._triggerHeartbeat(agentId), intervalMs);
this.timers.set(agentId, timer);
console.log(`[Heartbeat] ✅ 已挂载 ${agentId} 的主动心跳 (间隔: ${cfg.heartbeat.intervalMinutes} 分钟)`);
}
} catch (err) {
console.warn(`[Heartbeat] ⚠️ 读取 ${agentId} 配置失败: ${err.message}`);
}
}
/**
* 内部方法:向 Agent 的 Chat API 发送一次隐式的系统心跳
*/
_triggerHeartbeat(agentId) {
const timestamp = new Date().toISOString().replace('T', ' ').substring(0, 19);
console.log(`[Heartbeat] 💓 触发 ${agentId} 主动心跳 (${timestamp})...`);
const convId = `heartbeat-${Date.now().toString(36)}`;
const heartbeatMessage = [
`[SYSTEM HEARTBEAT]`,
`当前系统时间:${timestamp}`,
`这是一个后台周期的主动心跳触发。`,
`要求:请根据你的身份和 heartbeat.md 规则,扫描当前最新的全局 OKR 目标状态和节点。`,
`1. 如果发现某个负责人超时、卡点或产出需要仲裁,请主动使用 dispatch 工具给他们发送通知或驳回。`,
`2. 如果一切正常,无事发生,请直接回复“[心跳正常]”并且不要使用任何工具。`,
`无需等待人类确认,请完全自主决策。`
].join('\n');
const postData = JSON.stringify({
message: heartbeatMessage,
conversationId: convId,
isHeartbeat: true // 特殊标记,用于前端或后端识别
});
const options = {
hostname: 'localhost',
port: this.port,
path: `/api/agent/${agentId}/chat`,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(postData)
}
};
const req = http.request(options, (res) => {
let body = '';
res.on('data', (chunk) => body += chunk);
res.on('end', () => {
if (res.statusCode >= 200 && res.statusCode < 300) {
console.log(`[Heartbeat] 📨 ${agentId} 成功启动心跳推理会话 (${convId})`);
} else {
console.warn(`[Heartbeat] ⚠️ ${agentId} 心跳触发异常: ${res.statusCode} ${body}`);
}
});
});
req.on('error', (err) => {
console.error(`[Heartbeat] ❌ ${agentId} 心跳请求失败: ${err.message}`);
});
req.write(postData);
req.end();
}
}
// 导出单例
const heartbeatService = new HeartbeatService();
module.exports = heartbeatService;