@poolzin/pool-bot 2026.3.7 → 2026.3.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +40 -0
- package/README.md +147 -69
- package/dist/.buildstamp +1 -1
- package/dist/agents/error-classifier.js +251 -0
- package/dist/agents/skills/security.js +211 -0
- package/dist/build-info.json +3 -3
- package/dist/cli/cron-cli/register.cron-dashboard.js +339 -0
- package/dist/cli/cron-cli/register.js +2 -0
- package/dist/cli/errors.js +187 -0
- package/dist/cli/lazy-commands.example.js +113 -0
- package/dist/cli/lazy-commands.js +329 -0
- package/dist/cli/program/command-registry.js +26 -0
- package/dist/cli/program/register.maintenance.js +21 -0
- package/dist/cli/program/register.skills.js +4 -0
- package/dist/cli/program/register.subclis.js +9 -0
- package/dist/cli/swarm-cli/register.js +8 -0
- package/dist/cli/swarm-cli/register.swarm-status.js +488 -0
- package/dist/cli/telemetry-cli/register.js +10 -0
- package/dist/cli/telemetry-cli/register.telemetry-alerts.js +176 -0
- package/dist/cli/telemetry-cli/register.telemetry-metrics.js +323 -0
- package/dist/cli/telemetry-cli/register.telemetry-status.js +179 -0
- package/dist/commands/doctor-checks.js +498 -0
- package/dist/config/config.js +1 -0
- package/dist/config/secrets-integration.js +88 -0
- package/dist/context-engine/index.js +33 -0
- package/dist/context-engine/legacy.js +179 -0
- package/dist/context-engine/registry.js +86 -0
- package/dist/context-engine/summarizing.js +290 -0
- package/dist/context-engine/types.js +7 -0
- package/dist/cron/service/timer.js +18 -0
- package/dist/gateway/protocol/index.js +5 -2
- package/dist/gateway/protocol/schema/error-codes.js +1 -0
- package/dist/gateway/protocol/schema/swarm.js +80 -0
- package/dist/gateway/protocol/schema.js +1 -0
- package/dist/gateway/server-close.js +4 -0
- package/dist/gateway/server-constants.js +1 -0
- package/dist/gateway/server-cron.js +29 -0
- package/dist/gateway/server-maintenance.js +35 -2
- package/dist/gateway/server-methods/swarm.js +58 -0
- package/dist/gateway/server-methods/telemetry.js +71 -0
- package/dist/gateway/server-methods-list.js +8 -0
- package/dist/gateway/server-methods.js +9 -2
- package/dist/gateway/server.impl.js +33 -16
- package/dist/infra/abort-pattern.js +106 -0
- package/dist/infra/retry.js +96 -0
- package/dist/secrets/index.js +28 -0
- package/dist/secrets/resolver.js +185 -0
- package/dist/secrets/runtime.js +142 -0
- package/dist/secrets/types.js +11 -0
- package/dist/security/dangerous-tools.js +80 -0
- package/dist/security/types.js +12 -0
- package/dist/skills/commands.js +333 -0
- package/dist/skills/index.js +164 -0
- package/dist/skills/loader.js +282 -0
- package/dist/skills/parser.js +446 -0
- package/dist/skills/registry.js +394 -0
- package/dist/skills/security.js +312 -0
- package/dist/skills/types.js +21 -0
- package/dist/swarm/service.js +247 -0
- package/dist/telemetry/alert-engine.js +258 -0
- package/dist/telemetry/cron-instrumentation.js +49 -0
- package/dist/telemetry/gateway-instrumentation.js +80 -0
- package/dist/telemetry/instrumentation.js +66 -0
- package/dist/telemetry/service.js +345 -0
- package/dist/test-utils/index.js +219 -0
- package/dist/tui/components/assistant-message.js +6 -2
- package/dist/tui/components/hyperlink-markdown.js +32 -0
- package/dist/tui/components/searchable-select-list.js +12 -1
- package/dist/tui/components/user-message.js +6 -2
- package/dist/tui/index.js +611 -0
- package/dist/tui/theme/theme-detection.js +226 -0
- package/dist/tui/tui-command-handlers.js +20 -0
- package/dist/tui/tui-formatters.js +4 -3
- package/dist/tui/utils/ctrl-c-handler.js +67 -0
- package/dist/tui/utils/osc8-hyperlinks.js +208 -0
- package/dist/tui/utils/safe-stop.js +180 -0
- package/dist/tui/utils/session-key-utils.js +81 -0
- package/dist/tui/utils/text-sanitization.js +284 -0
- package/dist/utils/lru-cache.js +116 -0
- package/dist/utils/performance.js +199 -0
- package/dist/utils/retry.js +240 -0
- package/docs/INTEGRATION_PLAN.md +475 -0
- package/docs/INTEGRATION_SUMMARY.md +215 -0
- package/docs/MELHORIAS_IMPLEMENTADAS.md +228 -0
- package/docs/MELHORIAS_PROFISSIONAIS.md +282 -0
- package/docs/PLANO_ACAO_TUI.md +357 -0
- package/docs/PROGRESSO_TUI.md +66 -0
- package/docs/RELATORIO_FINAL.md +217 -0
- package/docs/diagnostico-shell-completion.md +265 -0
- package/docs/features/advanced-memory.md +585 -0
- package/docs/features/discord-components-v2.md +277 -0
- package/docs/features/swarm.md +100 -0
- package/docs/features/telemetry.md +284 -0
- package/docs/integrations/HEXSTRIKE_PLAN.md +796 -0
- package/docs/integrations/INTEGRATION_PLAN.md +744 -0
- package/docs/integrations/PAGE_AGENT_PLAN.md +370 -0
- package/docs/integrations/XYOPS_PLAN.md +978 -0
- package/docs/models/provider-infrastructure.md +400 -0
- package/docs/security/exec-approvals.md +294 -0
- package/docs/skills/IMPLEMENTATION_SUMMARY.md +145 -0
- package/docs/skills/SKILL.md +524 -0
- package/docs/skills.md +405 -0
- package/extensions/bluebubbles/package.json +1 -1
- package/extensions/copilot-proxy/package.json +1 -1
- package/extensions/diagnostics-otel/package.json +1 -1
- package/extensions/discord/package.json +1 -1
- package/extensions/feishu/package.json +1 -1
- package/extensions/google-antigravity-auth/package.json +1 -1
- package/extensions/google-gemini-cli-auth/package.json +1 -1
- package/extensions/googlechat/package.json +1 -1
- package/extensions/hexstrike-bridge/README.md +119 -0
- package/extensions/hexstrike-bridge/index.test.ts +247 -0
- package/extensions/hexstrike-bridge/index.ts +487 -0
- package/extensions/hexstrike-bridge/package.json +17 -0
- package/extensions/imessage/package.json +1 -1
- package/extensions/irc/package.json +1 -1
- package/extensions/line/package.json +1 -1
- package/extensions/llm-task/package.json +1 -1
- package/extensions/lobster/package.json +1 -1
- package/extensions/matrix/CHANGELOG.md +5 -0
- package/extensions/matrix/package.json +1 -1
- package/extensions/mattermost/package.json +1 -1
- package/extensions/mcp-server/index.ts +14 -0
- package/extensions/mcp-server/package.json +11 -0
- package/extensions/mcp-server/src/service.ts +540 -0
- package/extensions/memory-core/package.json +1 -1
- package/extensions/memory-lancedb/package.json +1 -1
- package/extensions/minimax-portal-auth/package.json +1 -1
- package/extensions/msteams/CHANGELOG.md +5 -0
- package/extensions/msteams/package.json +1 -1
- package/extensions/nextcloud-talk/package.json +1 -1
- package/extensions/nostr/CHANGELOG.md +5 -0
- package/extensions/nostr/package.json +1 -1
- package/extensions/open-prose/package.json +1 -1
- package/extensions/openai-codex-auth/package.json +1 -1
- package/extensions/signal/package.json +1 -1
- package/extensions/slack/package.json +1 -1
- package/extensions/telegram/package.json +1 -1
- package/extensions/tlon/package.json +1 -1
- package/extensions/twitch/CHANGELOG.md +5 -0
- package/extensions/twitch/package.json +1 -1
- package/extensions/voice-call/CHANGELOG.md +5 -0
- package/extensions/voice-call/package.json +1 -1
- package/extensions/whatsapp/package.json +1 -1
- package/extensions/zalo/CHANGELOG.md +5 -0
- package/extensions/zalo/package.json +1 -1
- package/extensions/zalouser/CHANGELOG.md +5 -0
- package/extensions/zalouser/package.json +1 -1
- package/package.json +8 -1
- package/skills/example-skill/SKILL.md +195 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill types and interfaces for PoolBot Skills System
|
|
3
|
+
* Compatible with agentskills.io ecosystem
|
|
4
|
+
*
|
|
5
|
+
* @module skills/types
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Skill system error
|
|
9
|
+
*/
|
|
10
|
+
export class SkillError extends Error {
|
|
11
|
+
code;
|
|
12
|
+
skillId;
|
|
13
|
+
cause;
|
|
14
|
+
constructor(code, message, skillId, cause) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.code = code;
|
|
17
|
+
this.skillId = skillId;
|
|
18
|
+
this.cause = cause;
|
|
19
|
+
this.name = "SkillError";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
// Agent Swarm Service - Multi-agent coordination and task distribution
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { createSubsystemLogger } from "../logging/subsystem.js";
|
|
5
|
+
const log = createSubsystemLogger("swarm");
|
|
6
|
+
const POOLBOT_DIR = join(process.env.HOME ?? "/tmp", ".poolbot");
|
|
7
|
+
const SWARM_STATE_FILE = join(POOLBOT_DIR, "swarm-state.json");
|
|
8
|
+
export class SwarmService {
|
|
9
|
+
swarms;
|
|
10
|
+
persistEnabled;
|
|
11
|
+
constructor(persistEnabled = true) {
|
|
12
|
+
this.swarms = new Map();
|
|
13
|
+
this.persistEnabled = persistEnabled;
|
|
14
|
+
this.loadFromDisk();
|
|
15
|
+
}
|
|
16
|
+
ensureDir() {
|
|
17
|
+
if (!existsSync(POOLBOT_DIR)) {
|
|
18
|
+
mkdirSync(POOLBOT_DIR, { recursive: true });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
loadFromDisk() {
|
|
22
|
+
if (!this.persistEnabled)
|
|
23
|
+
return;
|
|
24
|
+
try {
|
|
25
|
+
this.ensureDir();
|
|
26
|
+
if (existsSync(SWARM_STATE_FILE)) {
|
|
27
|
+
const data = readFileSync(SWARM_STATE_FILE, "utf-8");
|
|
28
|
+
const parsed = JSON.parse(data);
|
|
29
|
+
for (const [id, swarm] of Object.entries(parsed)) {
|
|
30
|
+
this.swarms.set(id, swarm);
|
|
31
|
+
}
|
|
32
|
+
log.info(`Loaded ${this.swarms.size} swarms from disk`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
log.error(`Failed to load swarm state: ${String(error)}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
saveToDisk() {
|
|
40
|
+
if (!this.persistEnabled)
|
|
41
|
+
return;
|
|
42
|
+
try {
|
|
43
|
+
this.ensureDir();
|
|
44
|
+
const data = {};
|
|
45
|
+
for (const [id, swarm] of this.swarms) {
|
|
46
|
+
data[id] = swarm;
|
|
47
|
+
}
|
|
48
|
+
writeFileSync(SWARM_STATE_FILE, JSON.stringify(data, null, 2));
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
log.error(`Failed to save swarm state: ${String(error)}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async list() {
|
|
55
|
+
const swarmList = Array.from(this.swarms.values()).map((swarm) => ({
|
|
56
|
+
id: swarm.id,
|
|
57
|
+
name: swarm.name,
|
|
58
|
+
status: swarm.status,
|
|
59
|
+
members: swarm.members.length,
|
|
60
|
+
tasks: swarm.tasks.length,
|
|
61
|
+
}));
|
|
62
|
+
return { swarms: swarmList };
|
|
63
|
+
}
|
|
64
|
+
async getStatus(swarmId) {
|
|
65
|
+
const swarm = this.swarms.get(swarmId);
|
|
66
|
+
return { swarm: swarm ?? null };
|
|
67
|
+
}
|
|
68
|
+
async create(params) {
|
|
69
|
+
const id = `swarm-${Date.now()}`;
|
|
70
|
+
const now = Date.now();
|
|
71
|
+
const swarm = {
|
|
72
|
+
id,
|
|
73
|
+
name: params.name,
|
|
74
|
+
description: params.description,
|
|
75
|
+
createdAt: now,
|
|
76
|
+
orchestratorAgentId: params.orchestratorAgentId,
|
|
77
|
+
members: [],
|
|
78
|
+
tasks: [],
|
|
79
|
+
strategy: params.strategy,
|
|
80
|
+
status: "active",
|
|
81
|
+
};
|
|
82
|
+
this.swarms.set(id, swarm);
|
|
83
|
+
this.saveToDisk();
|
|
84
|
+
log.info(`Created swarm: ${id} (${params.name})`);
|
|
85
|
+
return {
|
|
86
|
+
id,
|
|
87
|
+
name: params.name,
|
|
88
|
+
description: params.description,
|
|
89
|
+
strategy: params.strategy,
|
|
90
|
+
orchestratorAgentId: params.orchestratorAgentId,
|
|
91
|
+
createdAt: now,
|
|
92
|
+
status: "active",
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
async delete(swarmId) {
|
|
96
|
+
const deleted = this.swarms.delete(swarmId);
|
|
97
|
+
if (deleted) {
|
|
98
|
+
this.saveToDisk();
|
|
99
|
+
log.info(`Deleted swarm: ${swarmId}`);
|
|
100
|
+
}
|
|
101
|
+
return deleted;
|
|
102
|
+
}
|
|
103
|
+
async addMember(swarmId, member) {
|
|
104
|
+
const swarm = this.swarms.get(swarmId);
|
|
105
|
+
if (!swarm)
|
|
106
|
+
return false;
|
|
107
|
+
// Check if member already exists
|
|
108
|
+
const existingIndex = swarm.members.findIndex((m) => m.agentId === member.agentId);
|
|
109
|
+
if (existingIndex >= 0) {
|
|
110
|
+
swarm.members[existingIndex] = member;
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
swarm.members.push(member);
|
|
114
|
+
}
|
|
115
|
+
this.saveToDisk();
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
async removeMember(swarmId, agentId) {
|
|
119
|
+
const swarm = this.swarms.get(swarmId);
|
|
120
|
+
if (!swarm)
|
|
121
|
+
return false;
|
|
122
|
+
const initialLength = swarm.members.length;
|
|
123
|
+
swarm.members = swarm.members.filter((m) => m.agentId !== agentId);
|
|
124
|
+
const removed = swarm.members.length < initialLength;
|
|
125
|
+
if (removed) {
|
|
126
|
+
this.saveToDisk();
|
|
127
|
+
}
|
|
128
|
+
return removed;
|
|
129
|
+
}
|
|
130
|
+
async addTask(swarmId, task) {
|
|
131
|
+
const swarm = this.swarms.get(swarmId);
|
|
132
|
+
if (!swarm)
|
|
133
|
+
return false;
|
|
134
|
+
swarm.tasks.push(task);
|
|
135
|
+
this.saveToDisk();
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
async removeTask(swarmId, taskId) {
|
|
139
|
+
const swarm = this.swarms.get(swarmId);
|
|
140
|
+
if (!swarm)
|
|
141
|
+
return false;
|
|
142
|
+
const initialLength = swarm.tasks.length;
|
|
143
|
+
swarm.tasks = swarm.tasks.filter((t) => t.id !== taskId);
|
|
144
|
+
const removed = swarm.tasks.length < initialLength;
|
|
145
|
+
if (removed) {
|
|
146
|
+
this.saveToDisk();
|
|
147
|
+
}
|
|
148
|
+
return removed;
|
|
149
|
+
}
|
|
150
|
+
async updateTaskStatus(swarmId, taskId, status, assignedTo) {
|
|
151
|
+
const swarm = this.swarms.get(swarmId);
|
|
152
|
+
if (!swarm)
|
|
153
|
+
return false;
|
|
154
|
+
const task = swarm.tasks.find((t) => t.id === taskId);
|
|
155
|
+
if (!task)
|
|
156
|
+
return false;
|
|
157
|
+
task.status = status;
|
|
158
|
+
if (assignedTo)
|
|
159
|
+
task.assignedTo = assignedTo;
|
|
160
|
+
if (status === "in_progress" && !task.startedAt)
|
|
161
|
+
task.startedAt = Date.now();
|
|
162
|
+
if (status === "completed" && !task.completedAt)
|
|
163
|
+
task.completedAt = Date.now();
|
|
164
|
+
this.saveToDisk();
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
async updateMemberStatus(swarmId, agentId, status, currentTaskId) {
|
|
168
|
+
const swarm = this.swarms.get(swarmId);
|
|
169
|
+
if (!swarm)
|
|
170
|
+
return false;
|
|
171
|
+
const member = swarm.members.find((m) => m.agentId === agentId);
|
|
172
|
+
if (!member)
|
|
173
|
+
return false;
|
|
174
|
+
member.status = status;
|
|
175
|
+
member.lastHeartbeatAt = Date.now();
|
|
176
|
+
if (currentTaskId !== undefined)
|
|
177
|
+
member.currentTaskId = currentTaskId;
|
|
178
|
+
if (status === "working" && currentTaskId) {
|
|
179
|
+
member.completedTasks++;
|
|
180
|
+
}
|
|
181
|
+
this.saveToDisk();
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
async updateSwarmStatus(swarmId, status) {
|
|
185
|
+
const swarm = this.swarms.get(swarmId);
|
|
186
|
+
if (!swarm)
|
|
187
|
+
return false;
|
|
188
|
+
swarm.status = status;
|
|
189
|
+
this.saveToDisk();
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
// Get or create default swarm for demo purposes
|
|
193
|
+
async getOrCreateDefaultSwarm() {
|
|
194
|
+
const defaultId = "swarm-default";
|
|
195
|
+
let swarm = this.swarms.get(defaultId);
|
|
196
|
+
if (!swarm) {
|
|
197
|
+
const now = Date.now();
|
|
198
|
+
swarm = {
|
|
199
|
+
id: defaultId,
|
|
200
|
+
name: "Default Swarm",
|
|
201
|
+
description: "Default agent swarm for task coordination",
|
|
202
|
+
createdAt: now,
|
|
203
|
+
orchestratorAgentId: "main",
|
|
204
|
+
members: [
|
|
205
|
+
{
|
|
206
|
+
agentId: "agent-1",
|
|
207
|
+
sessionKey: "swarm/agent-1",
|
|
208
|
+
status: "idle",
|
|
209
|
+
capabilities: ["typescript", "review", "coding"],
|
|
210
|
+
joinedAt: now,
|
|
211
|
+
lastHeartbeatAt: now,
|
|
212
|
+
completedTasks: 0,
|
|
213
|
+
failedTasks: 0,
|
|
214
|
+
},
|
|
215
|
+
],
|
|
216
|
+
tasks: [],
|
|
217
|
+
strategy: "capability_match",
|
|
218
|
+
status: "active",
|
|
219
|
+
};
|
|
220
|
+
this.swarms.set(defaultId, swarm);
|
|
221
|
+
this.saveToDisk();
|
|
222
|
+
}
|
|
223
|
+
return swarm;
|
|
224
|
+
}
|
|
225
|
+
// Get swarm statistics
|
|
226
|
+
async getStats(swarmId) {
|
|
227
|
+
const swarm = this.swarms.get(swarmId);
|
|
228
|
+
if (!swarm)
|
|
229
|
+
return null;
|
|
230
|
+
const activeMembers = swarm.members.filter((m) => m.status === "idle" || m.status === "working").length;
|
|
231
|
+
const completedTasks = swarm.tasks.filter((t) => t.status === "completed").length;
|
|
232
|
+
const failedTasks = swarm.tasks.filter((t) => t.status === "failed").length;
|
|
233
|
+
const pendingTasks = swarm.tasks.filter((t) => t.status === "pending").length;
|
|
234
|
+
const inProgressTasks = swarm.tasks.filter((t) => t.status === "in_progress").length;
|
|
235
|
+
return {
|
|
236
|
+
totalMembers: swarm.members.length,
|
|
237
|
+
activeMembers,
|
|
238
|
+
totalTasks: swarm.tasks.length,
|
|
239
|
+
completedTasks,
|
|
240
|
+
failedTasks,
|
|
241
|
+
pendingTasks,
|
|
242
|
+
inProgressTasks,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
// Singleton instance
|
|
247
|
+
export const swarmService = new SwarmService();
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { createSubsystemLogger } from "../logging/subsystem.js";
|
|
2
|
+
const log = createSubsystemLogger("telemetry:alerts");
|
|
3
|
+
export class AlertEngine {
|
|
4
|
+
rules = new Map();
|
|
5
|
+
states = new Map();
|
|
6
|
+
onAlertCallback;
|
|
7
|
+
constructor(rules = []) {
|
|
8
|
+
for (const rule of rules) {
|
|
9
|
+
this.addRule(rule);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
addRule(rule) {
|
|
13
|
+
this.rules.set(rule.id, rule);
|
|
14
|
+
this.states.set(rule.id, { consecutiveCount: 0 });
|
|
15
|
+
log.debug(`Added alert rule: ${rule.id} (${rule.name})`);
|
|
16
|
+
}
|
|
17
|
+
removeRule(ruleId) {
|
|
18
|
+
this.states.delete(ruleId);
|
|
19
|
+
return this.rules.delete(ruleId);
|
|
20
|
+
}
|
|
21
|
+
getRules() {
|
|
22
|
+
return Array.from(this.rules.values());
|
|
23
|
+
}
|
|
24
|
+
onAlert(callback) {
|
|
25
|
+
this.onAlertCallback = callback;
|
|
26
|
+
}
|
|
27
|
+
evaluate(snapshot) {
|
|
28
|
+
const alerts = [];
|
|
29
|
+
const now = Date.now();
|
|
30
|
+
for (const rule of this.rules.values()) {
|
|
31
|
+
if (!rule.enabled)
|
|
32
|
+
continue;
|
|
33
|
+
const alert = this.evaluateRule(rule, snapshot, now);
|
|
34
|
+
if (alert) {
|
|
35
|
+
alerts.push(alert);
|
|
36
|
+
this.onAlertCallback?.(alert);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return alerts;
|
|
40
|
+
}
|
|
41
|
+
evaluateRule(rule, snapshot, now) {
|
|
42
|
+
const state = this.states.get(rule.id);
|
|
43
|
+
if (!state)
|
|
44
|
+
return null;
|
|
45
|
+
// Find matching metrics
|
|
46
|
+
const matchingMetrics = snapshot.metrics.filter((m) => {
|
|
47
|
+
if (m.name !== rule.threshold.metric)
|
|
48
|
+
return false;
|
|
49
|
+
if (rule.attributes) {
|
|
50
|
+
for (const [key, value] of Object.entries(rule.attributes)) {
|
|
51
|
+
if (m.attributes?.[key] !== value)
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return true;
|
|
56
|
+
});
|
|
57
|
+
if (matchingMetrics.length === 0) {
|
|
58
|
+
// Reset state if no matching metrics
|
|
59
|
+
state.consecutiveCount = 0;
|
|
60
|
+
state.triggeredAt = undefined;
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
// Check if any metric violates threshold
|
|
64
|
+
let violated = false;
|
|
65
|
+
let violatingMetric = null;
|
|
66
|
+
for (const metric of matchingMetrics) {
|
|
67
|
+
if (this.checkThreshold(metric.value, rule.threshold)) {
|
|
68
|
+
violated = true;
|
|
69
|
+
violatingMetric = metric;
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (!violated) {
|
|
74
|
+
// Reset state if threshold no longer violated
|
|
75
|
+
state.consecutiveCount = 0;
|
|
76
|
+
state.triggeredAt = undefined;
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
// Track consecutive violations
|
|
80
|
+
state.consecutiveCount++;
|
|
81
|
+
if (!state.triggeredAt) {
|
|
82
|
+
state.triggeredAt = now;
|
|
83
|
+
}
|
|
84
|
+
// Check duration requirement
|
|
85
|
+
const durationMs = rule.threshold.durationMs ?? 0;
|
|
86
|
+
if (now - state.triggeredAt < durationMs) {
|
|
87
|
+
return null; // Haven't met duration requirement yet
|
|
88
|
+
}
|
|
89
|
+
// Check cooldown
|
|
90
|
+
const cooldownMs = rule.threshold.cooldownMs ?? 60000;
|
|
91
|
+
if (state.lastAlertAt && now - state.lastAlertAt < cooldownMs) {
|
|
92
|
+
return null; // Still in cooldown
|
|
93
|
+
}
|
|
94
|
+
// Trigger alert
|
|
95
|
+
state.lastAlertAt = now;
|
|
96
|
+
return {
|
|
97
|
+
id: `${rule.id}-${now}`,
|
|
98
|
+
ruleId: rule.id,
|
|
99
|
+
name: rule.name,
|
|
100
|
+
severity: rule.severity,
|
|
101
|
+
metric: rule.threshold.metric,
|
|
102
|
+
value: violatingMetric.value,
|
|
103
|
+
threshold: rule.threshold.value,
|
|
104
|
+
operator: rule.threshold.operator,
|
|
105
|
+
timestamp: now,
|
|
106
|
+
message: this.buildAlertMessage(rule, violatingMetric.value),
|
|
107
|
+
attributes: violatingMetric.attributes,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
checkThreshold(value, threshold) {
|
|
111
|
+
switch (threshold.operator) {
|
|
112
|
+
case "gt":
|
|
113
|
+
return value > threshold.value;
|
|
114
|
+
case "gte":
|
|
115
|
+
return value >= threshold.value;
|
|
116
|
+
case "lt":
|
|
117
|
+
return value < threshold.value;
|
|
118
|
+
case "lte":
|
|
119
|
+
return value <= threshold.value;
|
|
120
|
+
case "eq":
|
|
121
|
+
return value === threshold.value;
|
|
122
|
+
default:
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
buildAlertMessage(rule, value) {
|
|
127
|
+
const opMap = {
|
|
128
|
+
gt: ">",
|
|
129
|
+
gte: ">=",
|
|
130
|
+
lt: "<",
|
|
131
|
+
lte: "<=",
|
|
132
|
+
eq: "=",
|
|
133
|
+
};
|
|
134
|
+
return `${rule.name}: ${rule.threshold.metric} = ${value} ${opMap[rule.threshold.operator]} ${rule.threshold.value}`;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// Predefined alert rules for common Pool Bot scenarios
|
|
138
|
+
export const defaultAlertRules = [
|
|
139
|
+
// Cron Job Alerts
|
|
140
|
+
{
|
|
141
|
+
id: "cron-high-failure-rate",
|
|
142
|
+
name: "High Cron Job Failure Rate",
|
|
143
|
+
description: "Triggers when cron job failures exceed 3 in 5 minutes",
|
|
144
|
+
enabled: true,
|
|
145
|
+
severity: "warning",
|
|
146
|
+
threshold: {
|
|
147
|
+
metric: "poolbot.cron.job_failures",
|
|
148
|
+
operator: "gt",
|
|
149
|
+
value: 3,
|
|
150
|
+
durationMs: 300000, // 5 minutes
|
|
151
|
+
cooldownMs: 600000, // 10 minutes
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
id: "cron-slow-jobs",
|
|
156
|
+
name: "Slow Cron Job Execution",
|
|
157
|
+
description: "Triggers when cron job duration exceeds 60 seconds",
|
|
158
|
+
enabled: true,
|
|
159
|
+
severity: "info",
|
|
160
|
+
threshold: {
|
|
161
|
+
metric: "poolbot.cron.job_duration_ms",
|
|
162
|
+
operator: "gt",
|
|
163
|
+
value: 60000,
|
|
164
|
+
durationMs: 60000, // 1 minute
|
|
165
|
+
cooldownMs: 300000, // 5 minutes
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
id: "cron-job-stuck",
|
|
170
|
+
name: "Cron Job Possibly Stuck",
|
|
171
|
+
description: "Triggers when cron job duration exceeds 5 minutes",
|
|
172
|
+
enabled: true,
|
|
173
|
+
severity: "critical",
|
|
174
|
+
threshold: {
|
|
175
|
+
metric: "poolbot.cron.job_duration_ms",
|
|
176
|
+
operator: "gt",
|
|
177
|
+
value: 300000,
|
|
178
|
+
durationMs: 60000,
|
|
179
|
+
cooldownMs: 300000,
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
// Security Scan Alerts (HexStrike)
|
|
183
|
+
{
|
|
184
|
+
id: "security-scan-failures",
|
|
185
|
+
name: "Security Scan Failures",
|
|
186
|
+
description: "Triggers when security scans fail",
|
|
187
|
+
enabled: true,
|
|
188
|
+
severity: "warning",
|
|
189
|
+
threshold: {
|
|
190
|
+
metric: "poolbot.security.scan_failures",
|
|
191
|
+
operator: "gt",
|
|
192
|
+
value: 2,
|
|
193
|
+
durationMs: 300000,
|
|
194
|
+
cooldownMs: 600000,
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
id: "security-high-latency",
|
|
199
|
+
name: "HexStrike API High Latency",
|
|
200
|
+
description: "Triggers when HexStrike API latency exceeds 5 seconds",
|
|
201
|
+
enabled: true,
|
|
202
|
+
severity: "info",
|
|
203
|
+
threshold: {
|
|
204
|
+
metric: "poolbot.security.api_latency_ms",
|
|
205
|
+
operator: "gt",
|
|
206
|
+
value: 5000,
|
|
207
|
+
durationMs: 120000,
|
|
208
|
+
cooldownMs: 300000,
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
// Swarm Alerts
|
|
212
|
+
{
|
|
213
|
+
id: "swarm-high-failure-rate",
|
|
214
|
+
name: "Swarm Task Failure Rate",
|
|
215
|
+
description: "Triggers when swarm tasks fail frequently",
|
|
216
|
+
enabled: true,
|
|
217
|
+
severity: "warning",
|
|
218
|
+
threshold: {
|
|
219
|
+
metric: "poolbot.swarm.task_failures",
|
|
220
|
+
operator: "gt",
|
|
221
|
+
value: 5,
|
|
222
|
+
durationMs: 600000,
|
|
223
|
+
cooldownMs: 600000,
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
// Gateway Health Alerts
|
|
227
|
+
{
|
|
228
|
+
id: "gateway-high-memory",
|
|
229
|
+
name: "High Gateway Memory Usage",
|
|
230
|
+
description: "Triggers when gateway memory usage exceeds 512MB",
|
|
231
|
+
enabled: true,
|
|
232
|
+
severity: "critical",
|
|
233
|
+
threshold: {
|
|
234
|
+
metric: "poolbot.gateway.memory_usage_bytes",
|
|
235
|
+
operator: "gt",
|
|
236
|
+
value: 536870912, // 512MB
|
|
237
|
+
durationMs: 120000,
|
|
238
|
+
cooldownMs: 300000,
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
id: "gateway-high-cpu",
|
|
243
|
+
name: "High Gateway CPU Usage",
|
|
244
|
+
description: "Triggers when gateway CPU usage exceeds 80%",
|
|
245
|
+
enabled: true,
|
|
246
|
+
severity: "warning",
|
|
247
|
+
threshold: {
|
|
248
|
+
metric: "poolbot.gateway.cpu_usage_percent",
|
|
249
|
+
operator: "gt",
|
|
250
|
+
value: 80,
|
|
251
|
+
durationMs: 180000,
|
|
252
|
+
cooldownMs: 300000,
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
];
|
|
256
|
+
export function createAlertEngine(customRules) {
|
|
257
|
+
return new AlertEngine([...defaultAlertRules, ...(customRules ?? [])]);
|
|
258
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { recordCounter, recordGauge, recordHistogram, withInstrumentation, } from "../telemetry/instrumentation.js";
|
|
2
|
+
export function instrumentCronOperation(operation, fn, attributes) {
|
|
3
|
+
return withInstrumentation({
|
|
4
|
+
operation,
|
|
5
|
+
component: "cron",
|
|
6
|
+
attributes,
|
|
7
|
+
}, fn);
|
|
8
|
+
}
|
|
9
|
+
export function recordCronJobExecution(metrics) {
|
|
10
|
+
recordCounter("poolbot.cron.jobs_executed", 1, {
|
|
11
|
+
job_id: metrics.jobId,
|
|
12
|
+
job_name: metrics.jobName,
|
|
13
|
+
schedule: metrics.schedule,
|
|
14
|
+
status: metrics.success ? "success" : "failure",
|
|
15
|
+
});
|
|
16
|
+
recordHistogram("poolbot.cron.job_duration_ms", metrics.durationMs, {
|
|
17
|
+
job_id: metrics.jobId,
|
|
18
|
+
job_name: metrics.jobName,
|
|
19
|
+
});
|
|
20
|
+
if (!metrics.success) {
|
|
21
|
+
recordCounter("poolbot.cron.job_failures", 1, {
|
|
22
|
+
job_id: metrics.jobId,
|
|
23
|
+
job_name: metrics.jobName,
|
|
24
|
+
error_type: metrics.errorType ?? "unknown",
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export function recordCronJobScheduled(jobId, jobName, schedule) {
|
|
29
|
+
recordCounter("poolbot.cron.jobs_scheduled", 1, {
|
|
30
|
+
job_id: jobId,
|
|
31
|
+
job_name: jobName,
|
|
32
|
+
schedule,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
export function recordCronJobCancelled(jobId, jobName) {
|
|
36
|
+
recordCounter("poolbot.cron.jobs_cancelled", 1, {
|
|
37
|
+
job_id: jobId,
|
|
38
|
+
job_name: jobName,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
export function updateActiveCronJobsGauge(count) {
|
|
42
|
+
recordGauge("poolbot.cron.active_jobs", count);
|
|
43
|
+
}
|
|
44
|
+
export function recordCronQueueDepth(depth) {
|
|
45
|
+
recordGauge("poolbot.cron.queue_depth", depth);
|
|
46
|
+
}
|
|
47
|
+
export function recordCronOverdueJobs(count) {
|
|
48
|
+
recordGauge("poolbot.cron.overdue_jobs", count);
|
|
49
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { recordCounter, recordHistogram, withInstrumentation, } from "../telemetry/instrumentation.js";
|
|
2
|
+
export function instrumentGatewayRequest(operation, fn, attributes) {
|
|
3
|
+
return withInstrumentation({
|
|
4
|
+
operation,
|
|
5
|
+
component: "gateway",
|
|
6
|
+
attributes,
|
|
7
|
+
}, fn);
|
|
8
|
+
}
|
|
9
|
+
export function recordGatewayRequest(metrics) {
|
|
10
|
+
recordCounter("poolbot.gateway.requests", 1, {
|
|
11
|
+
method: metrics.method,
|
|
12
|
+
path: metrics.path,
|
|
13
|
+
status_code: String(metrics.statusCode),
|
|
14
|
+
});
|
|
15
|
+
recordHistogram("poolbot.gateway.request_duration_ms", metrics.durationMs, {
|
|
16
|
+
method: metrics.method,
|
|
17
|
+
path: metrics.path,
|
|
18
|
+
});
|
|
19
|
+
if (metrics.statusCode >= 500) {
|
|
20
|
+
recordCounter("poolbot.gateway.server_errors", 1, {
|
|
21
|
+
path: metrics.path,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
else if (metrics.statusCode >= 400) {
|
|
25
|
+
recordCounter("poolbot.gateway.client_errors", 1, {
|
|
26
|
+
path: metrics.path,
|
|
27
|
+
status_code: String(metrics.statusCode),
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export function createTelemetryMiddleware() {
|
|
32
|
+
return (req, res, next) => {
|
|
33
|
+
const startTime = Date.now();
|
|
34
|
+
const path = req.url ?? "/unknown";
|
|
35
|
+
const method = req.method ?? "UNKNOWN";
|
|
36
|
+
recordCounter("poolbot.gateway.active_requests", 1, {
|
|
37
|
+
method,
|
|
38
|
+
path,
|
|
39
|
+
});
|
|
40
|
+
const originalEnd = res.end.bind(res);
|
|
41
|
+
res.end = function (...args) {
|
|
42
|
+
const duration = Date.now() - startTime;
|
|
43
|
+
recordCounter("poolbot.gateway.active_requests", -1, {
|
|
44
|
+
method,
|
|
45
|
+
path,
|
|
46
|
+
});
|
|
47
|
+
recordGatewayRequest({
|
|
48
|
+
method,
|
|
49
|
+
path,
|
|
50
|
+
statusCode: res.statusCode,
|
|
51
|
+
durationMs: duration,
|
|
52
|
+
userAgent: req.headers["user-agent"],
|
|
53
|
+
});
|
|
54
|
+
return originalEnd(...args);
|
|
55
|
+
};
|
|
56
|
+
next();
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
export function instrumentWebSocketConnection(connectionId) {
|
|
60
|
+
recordCounter("poolbot.gateway.ws.connections", 1, {
|
|
61
|
+
connection_id: connectionId,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
export function recordWebSocketMessage(connectionId, messageType, sizeBytes) {
|
|
65
|
+
recordCounter("poolbot.gateway.ws.messages", 1, {
|
|
66
|
+
connection_id: connectionId,
|
|
67
|
+
message_type: messageType,
|
|
68
|
+
});
|
|
69
|
+
recordHistogram("poolbot.gateway.ws.message_size_bytes", sizeBytes, {
|
|
70
|
+
message_type: messageType,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
export function recordWebSocketDisconnection(connectionId, durationMs) {
|
|
74
|
+
recordCounter("poolbot.gateway.ws.disconnections", 1, {
|
|
75
|
+
connection_id: connectionId,
|
|
76
|
+
});
|
|
77
|
+
recordHistogram("poolbot.gateway.ws.connection_duration_ms", durationMs, {
|
|
78
|
+
connection_id: connectionId,
|
|
79
|
+
});
|
|
80
|
+
}
|