@poolzin/pool-bot 2026.3.7 → 2026.3.9
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 +16 -0
- package/dist/.buildstamp +1 -1
- package/dist/agents/error-classifier.js +302 -0
- package/dist/agents/skills/security.js +217 -0
- package/dist/build-info.json +3 -3
- package/dist/cli/lazy-commands.example.js +113 -0
- package/dist/cli/lazy-commands.js +329 -0
- package/dist/cli/program/command-registry.js +13 -0
- package/dist/cli/program/register.skills.js +4 -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 +181 -0
- package/dist/context-engine/registry.js +86 -0
- package/dist/context-engine/summarizing.js +293 -0
- package/dist/context-engine/types.js +7 -0
- package/dist/infra/abort-pattern.js +106 -0
- package/dist/infra/retry.js +94 -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 +351 -0
- package/dist/skills/index.js +167 -0
- package/dist/skills/loader.js +282 -0
- package/dist/skills/parser.js +461 -0
- package/dist/skills/registry.js +397 -0
- package/dist/skills/security.js +318 -0
- package/dist/skills/types.js +21 -0
- package/dist/test-utils/index.js +219 -0
- package/dist/tui/index.js +595 -0
- package/docs/INTEGRATION_PLAN.md +475 -0
- package/docs/INTEGRATION_SUMMARY.md +215 -0
- package/docs/integrations/HEXSTRIKE_PLAN.md +796 -0
- package/docs/integrations/INTEGRATION_PLAN.md +424 -0
- package/docs/integrations/PAGE_AGENT_PLAN.md +370 -0
- package/docs/integrations/XYOPS_PLAN.md +978 -0
- package/docs/skills/IMPLEMENTATION_SUMMARY.md +145 -0
- package/docs/skills/SKILL.md +524 -0
- package/docs/skills.md +405 -0
- package/package.json +1 -1
- package/skills/example-skill/SKILL.md +195 -0
|
@@ -0,0 +1,978 @@
|
|
|
1
|
+
# xyOps Integration - Implementation Plan
|
|
2
|
+
|
|
3
|
+
**Status**: Ready for implementation
|
|
4
|
+
**Priority**: High
|
|
5
|
+
**Estimated Duration**: 3 weeks
|
|
6
|
+
**Dependencies**: None (Node.js based)
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Overview
|
|
11
|
+
|
|
12
|
+
Integrate xyOps as both a Plugin Extension and Gateway Node to enable workflow automation, job scheduling, and infrastructure monitoring within PoolBot.
|
|
13
|
+
|
|
14
|
+
## Architecture
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
18
|
+
│ PoolBot Core │
|
|
19
|
+
├─────────────────────────────────────────────────────────────┤
|
|
20
|
+
│ Plugin Layer (Deep Integration) │
|
|
21
|
+
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
|
22
|
+
│ │ Workflow │ │ Schedule │ │ Monitor │ │
|
|
23
|
+
│ │ Engine │ │ Engine │ │ Engine │ │
|
|
24
|
+
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
|
25
|
+
├─────────────────────────────────────────────────────────────┤
|
|
26
|
+
│ Gateway Node Layer (External Process Support) │
|
|
27
|
+
│ ┌─────────────────────────────────────────────────────┐ │
|
|
28
|
+
│ │ xyOps Gateway Node │ │
|
|
29
|
+
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────┐ │ │
|
|
30
|
+
│ │ │ Node Client │ │ xyOps Core │ │ Visual │ │ │
|
|
31
|
+
│ │ │ (WebSocket) │──│ │──│ Editor │ │ │
|
|
32
|
+
│ │ └──────────────┘ └──────────────┘ └──────────┘ │ │
|
|
33
|
+
│ └─────────────────────────────────────────────────────┘ │
|
|
34
|
+
└─────────────────────────────────────────────────────────────┘
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Integration Modes
|
|
38
|
+
|
|
39
|
+
| Mode | Use Case | Implementation |
|
|
40
|
+
|------|----------|----------------|
|
|
41
|
+
| **Plugin** | Single-instance, deep integration | Direct PoolBot extension |
|
|
42
|
+
| **Gateway Node** | Multi-instance, distributed | External process via WebSocket |
|
|
43
|
+
| **Hybrid** | Best of both | Plugin + optional external nodes |
|
|
44
|
+
|
|
45
|
+
## Implementation Steps
|
|
46
|
+
|
|
47
|
+
### Week 1: Plugin Foundation
|
|
48
|
+
|
|
49
|
+
#### Day 1-2: Plugin Structure
|
|
50
|
+
```typescript
|
|
51
|
+
// extensions/xyops/src/plugin.ts
|
|
52
|
+
import { PoolBotPlugin, PluginContext } from '@poolbot/plugin-sdk';
|
|
53
|
+
import { WorkflowEngine } from './workflow/engine';
|
|
54
|
+
import { ScheduleEngine } from './schedule/engine';
|
|
55
|
+
import { MonitorEngine } from './monitor/engine';
|
|
56
|
+
|
|
57
|
+
export default class XyOpsPlugin implements PoolBotPlugin {
|
|
58
|
+
name = 'xyops';
|
|
59
|
+
version = '1.0.0';
|
|
60
|
+
|
|
61
|
+
private workflowEngine: WorkflowEngine;
|
|
62
|
+
private scheduleEngine: ScheduleEngine;
|
|
63
|
+
private monitorEngine: MonitorEngine;
|
|
64
|
+
|
|
65
|
+
async onLoad(context: PluginContext) {
|
|
66
|
+
// Initialize engines
|
|
67
|
+
this.workflowEngine = new WorkflowEngine(context);
|
|
68
|
+
this.scheduleEngine = new ScheduleEngine(context);
|
|
69
|
+
this.monitorEngine = new MonitorEngine(context);
|
|
70
|
+
|
|
71
|
+
// Register CLI commands
|
|
72
|
+
context.registerCommand({
|
|
73
|
+
name: 'workflow',
|
|
74
|
+
description: 'Manage workflows',
|
|
75
|
+
subcommands: [
|
|
76
|
+
{ name: 'create', handler: this.handleWorkflowCreate },
|
|
77
|
+
{ name: 'list', handler: this.handleWorkflowList },
|
|
78
|
+
{ name: 'run', handler: this.handleWorkflowRun },
|
|
79
|
+
{ name: 'delete', handler: this.handleWorkflowDelete }
|
|
80
|
+
]
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
context.registerCommand({
|
|
84
|
+
name: 'schedule',
|
|
85
|
+
description: 'Manage scheduled jobs',
|
|
86
|
+
subcommands: [
|
|
87
|
+
{ name: 'add', handler: this.handleScheduleAdd },
|
|
88
|
+
{ name: 'list', handler: this.handleScheduleList },
|
|
89
|
+
{ name: 'remove', handler: this.handleScheduleRemove }
|
|
90
|
+
]
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Register skills
|
|
94
|
+
context.registerSkill({
|
|
95
|
+
name: 'workflow.create',
|
|
96
|
+
description: 'Create a new workflow',
|
|
97
|
+
parameters: z.object({
|
|
98
|
+
name: z.string(),
|
|
99
|
+
steps: z.array(z.object({
|
|
100
|
+
name: z.string(),
|
|
101
|
+
type: z.enum(['agent', 'tool', 'condition', 'loop']),
|
|
102
|
+
config: z.record(z.any())
|
|
103
|
+
}))
|
|
104
|
+
}),
|
|
105
|
+
handler: this.workflowEngine.create.bind(this.workflowEngine)
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
context.registerSkill({
|
|
109
|
+
name: 'schedule.cron',
|
|
110
|
+
description: 'Schedule a workflow with cron expression',
|
|
111
|
+
parameters: z.object({
|
|
112
|
+
workflowId: z.string(),
|
|
113
|
+
cron: z.string(),
|
|
114
|
+
enabled: z.boolean().optional()
|
|
115
|
+
}),
|
|
116
|
+
handler: this.scheduleEngine.schedule.bind(this.scheduleEngine)
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
context.registerSkill({
|
|
120
|
+
name: 'monitor.check',
|
|
121
|
+
description: 'Set up monitoring for a target',
|
|
122
|
+
parameters: z.object({
|
|
123
|
+
target: z.string(),
|
|
124
|
+
type: z.enum(['http', 'tcp', 'dns']),
|
|
125
|
+
interval: z.number(),
|
|
126
|
+
alerts: z.array(z.object({
|
|
127
|
+
condition: z.string(),
|
|
128
|
+
action: z.enum(['notify', 'webhook', 'workflow'])
|
|
129
|
+
}))
|
|
130
|
+
}),
|
|
131
|
+
handler: this.monitorEngine.addCheck.bind(this.monitorEngine)
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async onReady() {
|
|
136
|
+
// Start scheduled jobs
|
|
137
|
+
await this.scheduleEngine.start();
|
|
138
|
+
|
|
139
|
+
// Start monitoring
|
|
140
|
+
await this.monitorEngine.start();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async onUnload() {
|
|
144
|
+
// Cleanup
|
|
145
|
+
await this.scheduleEngine.stop();
|
|
146
|
+
await this.monitorEngine.stop();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
#### Day 3-4: Workflow Engine
|
|
152
|
+
```typescript
|
|
153
|
+
// extensions/xyops/src/workflow/engine.ts
|
|
154
|
+
export interface Workflow {
|
|
155
|
+
id: string;
|
|
156
|
+
name: string;
|
|
157
|
+
description?: string;
|
|
158
|
+
steps: WorkflowStep[];
|
|
159
|
+
variables: Record<string, any>;
|
|
160
|
+
createdAt: Date;
|
|
161
|
+
updatedAt: Date;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export interface WorkflowStep {
|
|
165
|
+
id: string;
|
|
166
|
+
name: string;
|
|
167
|
+
type: 'agent' | 'tool' | 'condition' | 'loop' | 'wait' | 'parallel';
|
|
168
|
+
config: Record<string, any>;
|
|
169
|
+
transitions: {
|
|
170
|
+
onSuccess?: string;
|
|
171
|
+
onFailure?: string;
|
|
172
|
+
onCondition?: {
|
|
173
|
+
condition: string;
|
|
174
|
+
target: string;
|
|
175
|
+
}[];
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export class WorkflowEngine {
|
|
180
|
+
private workflows: Map<string, Workflow> = new Map();
|
|
181
|
+
private executions: Map<string, WorkflowExecution> = new Map();
|
|
182
|
+
|
|
183
|
+
async create(params: CreateWorkflowParams): Promise<Workflow> {
|
|
184
|
+
const workflow: Workflow = {
|
|
185
|
+
id: generateId(),
|
|
186
|
+
name: params.name,
|
|
187
|
+
description: params.description,
|
|
188
|
+
steps: params.steps,
|
|
189
|
+
variables: params.variables || {},
|
|
190
|
+
createdAt: new Date(),
|
|
191
|
+
updatedAt: new Date()
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
this.workflows.set(workflow.id, workflow);
|
|
195
|
+
await this.persistWorkflow(workflow);
|
|
196
|
+
|
|
197
|
+
return workflow;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async run(workflowId: string, inputs?: Record<string, any>): Promise<WorkflowResult> {
|
|
201
|
+
const workflow = this.workflows.get(workflowId);
|
|
202
|
+
if (!workflow) {
|
|
203
|
+
throw new Error(`Workflow ${workflowId} not found`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const execution = new WorkflowExecution({
|
|
207
|
+
workflow,
|
|
208
|
+
inputs,
|
|
209
|
+
context: this.context
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
this.executions.set(execution.id, execution);
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
const result = await execution.run();
|
|
216
|
+
return result;
|
|
217
|
+
} finally {
|
|
218
|
+
this.executions.delete(execution.id);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async executeStep(step: WorkflowStep, context: ExecutionContext): Promise<StepResult> {
|
|
223
|
+
switch (step.type) {
|
|
224
|
+
case 'agent':
|
|
225
|
+
return this.executeAgentStep(step, context);
|
|
226
|
+
case 'tool':
|
|
227
|
+
return this.executeToolStep(step, context);
|
|
228
|
+
case 'condition':
|
|
229
|
+
return this.executeConditionStep(step, context);
|
|
230
|
+
case 'loop':
|
|
231
|
+
return this.executeLoopStep(step, context);
|
|
232
|
+
case 'wait':
|
|
233
|
+
return this.executeWaitStep(step, context);
|
|
234
|
+
case 'parallel':
|
|
235
|
+
return this.executeParallelStep(step, context);
|
|
236
|
+
default:
|
|
237
|
+
throw new Error(`Unknown step type: ${step.type}`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
private async executeAgentStep(step: WorkflowStep, context: ExecutionContext): Promise<StepResult> {
|
|
242
|
+
const agent = await this.context.createAgent({
|
|
243
|
+
type: step.config.agentType || 'standard',
|
|
244
|
+
systemPrompt: step.config.systemPrompt
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
const result = await agent.execute({
|
|
248
|
+
message: step.config.prompt,
|
|
249
|
+
context: context.variables
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
success: true,
|
|
254
|
+
output: result,
|
|
255
|
+
variables: {
|
|
256
|
+
[step.config.outputVar || 'result']: result
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
private async executeToolStep(step: WorkflowStep, context: ExecutionContext): Promise<StepResult> {
|
|
262
|
+
const result = await this.context.callTool({
|
|
263
|
+
tool: step.config.tool,
|
|
264
|
+
params: this.interpolateParams(step.config.params, context.variables)
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
return {
|
|
268
|
+
success: result.success,
|
|
269
|
+
output: result.data,
|
|
270
|
+
error: result.error,
|
|
271
|
+
variables: {
|
|
272
|
+
[step.config.outputVar || 'result']: result.data
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
private interpolateParams(params: Record<string, any>, variables: Record<string, any>): Record<string, any> {
|
|
278
|
+
return JSON.parse(
|
|
279
|
+
JSON.stringify(params).replace(/\{\{(\w+)\}\}/g, (match, key) => {
|
|
280
|
+
return variables[key] ?? match;
|
|
281
|
+
})
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
#### Day 5: Schedule Engine
|
|
288
|
+
```typescript
|
|
289
|
+
// extensions/xyops/src/schedule/engine.ts
|
|
290
|
+
import { CronJob } from 'cron';
|
|
291
|
+
|
|
292
|
+
export interface ScheduledJob {
|
|
293
|
+
id: string;
|
|
294
|
+
name: string;
|
|
295
|
+
workflowId: string;
|
|
296
|
+
cronExpression: string;
|
|
297
|
+
enabled: boolean;
|
|
298
|
+
inputs?: Record<string, any>;
|
|
299
|
+
timezone?: string;
|
|
300
|
+
lastRun?: Date;
|
|
301
|
+
nextRun?: Date;
|
|
302
|
+
runCount: number;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export class ScheduleEngine {
|
|
306
|
+
private jobs: Map<string, CronJob> = new Map();
|
|
307
|
+
private scheduledJobs: Map<string, ScheduledJob> = new Map();
|
|
308
|
+
|
|
309
|
+
async schedule(params: ScheduleParams): Promise<ScheduledJob> {
|
|
310
|
+
const job: ScheduledJob = {
|
|
311
|
+
id: generateId(),
|
|
312
|
+
name: params.name,
|
|
313
|
+
workflowId: params.workflowId,
|
|
314
|
+
cronExpression: params.cron,
|
|
315
|
+
enabled: params.enabled ?? true,
|
|
316
|
+
inputs: params.inputs,
|
|
317
|
+
timezone: params.timezone,
|
|
318
|
+
runCount: 0
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
this.scheduledJobs.set(job.id, job);
|
|
322
|
+
|
|
323
|
+
if (job.enabled) {
|
|
324
|
+
await this.startJob(job);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
await this.persistJob(job);
|
|
328
|
+
return job;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
private async startJob(job: ScheduledJob): Promise<void> {
|
|
332
|
+
const cronJob = new CronJob(
|
|
333
|
+
job.cronExpression,
|
|
334
|
+
async () => {
|
|
335
|
+
await this.executeJob(job);
|
|
336
|
+
},
|
|
337
|
+
null,
|
|
338
|
+
true,
|
|
339
|
+
job.timezone
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
this.jobs.set(job.id, cronJob);
|
|
343
|
+
job.nextRun = cronJob.nextDate().toJSDate();
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
private async executeJob(job: ScheduledJob): Promise<void> {
|
|
347
|
+
console.log(`[Schedule] Executing job ${job.name} (${job.id})`);
|
|
348
|
+
|
|
349
|
+
try {
|
|
350
|
+
const result = await this.workflowEngine.run(job.workflowId, job.inputs);
|
|
351
|
+
|
|
352
|
+
job.lastRun = new Date();
|
|
353
|
+
job.runCount++;
|
|
354
|
+
job.nextRun = this.jobs.get(job.id)?.nextDate().toJSDate();
|
|
355
|
+
|
|
356
|
+
await this.persistJob(job);
|
|
357
|
+
|
|
358
|
+
// Notify on completion if configured
|
|
359
|
+
if (job.notifyOnComplete) {
|
|
360
|
+
await this.context.notify({
|
|
361
|
+
type: 'job_complete',
|
|
362
|
+
jobId: job.id,
|
|
363
|
+
result
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
} catch (error) {
|
|
367
|
+
console.error(`[Schedule] Job ${job.id} failed:`, error);
|
|
368
|
+
|
|
369
|
+
if (job.notifyOnFailure) {
|
|
370
|
+
await this.context.notify({
|
|
371
|
+
type: 'job_failed',
|
|
372
|
+
jobId: job.id,
|
|
373
|
+
error: error.message
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
parseNaturalLanguage(input: string): ScheduleParams {
|
|
380
|
+
// Use LLM to parse natural language into cron
|
|
381
|
+
// Examples:
|
|
382
|
+
// "every day at 9am" -> "0 9 * * *"
|
|
383
|
+
// "every Monday at 8am" -> "0 8 * * 1"
|
|
384
|
+
// "every hour" -> "0 * * * *"
|
|
385
|
+
|
|
386
|
+
const patterns = [
|
|
387
|
+
{
|
|
388
|
+
regex: /every day at (\d+)(?::(\d+))?\s*(am|pm)?/i,
|
|
389
|
+
handler: (match) => {
|
|
390
|
+
let hour = parseInt(match[1]);
|
|
391
|
+
const minute = match[2] ? parseInt(match[2]) : 0;
|
|
392
|
+
const period = match[3]?.toLowerCase();
|
|
393
|
+
|
|
394
|
+
if (period === 'pm' && hour !== 12) hour += 12;
|
|
395
|
+
if (period === 'am' && hour === 12) hour = 0;
|
|
396
|
+
|
|
397
|
+
return `${minute} ${hour} * * *`;
|
|
398
|
+
}
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
regex: /every (\w+) at (\d+)(?::(\d+))?\s*(am|pm)?/i,
|
|
402
|
+
handler: (match) => {
|
|
403
|
+
const days = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'];
|
|
404
|
+
const dayIndex = days.indexOf(match[1].toLowerCase());
|
|
405
|
+
if (dayIndex === -1) return null;
|
|
406
|
+
|
|
407
|
+
let hour = parseInt(match[2]);
|
|
408
|
+
const minute = match[3] ? parseInt(match[3]) : 0;
|
|
409
|
+
const period = match[4]?.toLowerCase();
|
|
410
|
+
|
|
411
|
+
if (period === 'pm' && hour !== 12) hour += 12;
|
|
412
|
+
if (period === 'am' && hour === 12) hour = 0;
|
|
413
|
+
|
|
414
|
+
return `${minute} ${hour} * * ${dayIndex}`;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
];
|
|
418
|
+
|
|
419
|
+
for (const pattern of patterns) {
|
|
420
|
+
const match = input.match(pattern.regex);
|
|
421
|
+
if (match) {
|
|
422
|
+
const cron = pattern.handler(match);
|
|
423
|
+
if (cron) {
|
|
424
|
+
return {
|
|
425
|
+
cron,
|
|
426
|
+
name: input,
|
|
427
|
+
// ... other params
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
throw new Error(`Could not parse schedule: ${input}`);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
### Week 2: Monitoring & Visual Editor
|
|
439
|
+
|
|
440
|
+
#### Day 6-7: Monitor Engine
|
|
441
|
+
```typescript
|
|
442
|
+
// extensions/xyops/src/monitor/engine.ts
|
|
443
|
+
export interface HealthCheck {
|
|
444
|
+
id: string;
|
|
445
|
+
name: string;
|
|
446
|
+
target: string;
|
|
447
|
+
type: 'http' | 'tcp' | 'dns' | 'ping';
|
|
448
|
+
interval: number; // seconds
|
|
449
|
+
timeout: number; // seconds
|
|
450
|
+
retries: number;
|
|
451
|
+
alerts: AlertConfig[];
|
|
452
|
+
status: 'up' | 'down' | 'unknown';
|
|
453
|
+
lastCheck?: Date;
|
|
454
|
+
lastSuccess?: Date;
|
|
455
|
+
consecutiveFailures: number;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
export interface AlertConfig {
|
|
459
|
+
condition: 'down' | 'slow' | 'error_rate';
|
|
460
|
+
threshold?: number;
|
|
461
|
+
action: 'notify' | 'webhook' | 'workflow';
|
|
462
|
+
config: Record<string, any>;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
export class MonitorEngine {
|
|
466
|
+
private checks: Map<string, HealthCheck> = new Map();
|
|
467
|
+
private intervals: Map<string, NodeJS.Timeout> = new Map();
|
|
468
|
+
|
|
469
|
+
async addCheck(params: CreateCheckParams): Promise<HealthCheck> {
|
|
470
|
+
const check: HealthCheck = {
|
|
471
|
+
id: generateId(),
|
|
472
|
+
name: params.name,
|
|
473
|
+
target: params.target,
|
|
474
|
+
type: params.type,
|
|
475
|
+
interval: params.interval,
|
|
476
|
+
timeout: params.timeout || 30,
|
|
477
|
+
retries: params.retries || 3,
|
|
478
|
+
alerts: params.alerts || [],
|
|
479
|
+
status: 'unknown',
|
|
480
|
+
consecutiveFailures: 0
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
this.checks.set(check.id, check);
|
|
484
|
+
|
|
485
|
+
// Start monitoring
|
|
486
|
+
this.startCheck(check);
|
|
487
|
+
|
|
488
|
+
await this.persistCheck(check);
|
|
489
|
+
return check;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
private startCheck(check: HealthCheck): void {
|
|
493
|
+
const interval = setInterval(async () => {
|
|
494
|
+
await this.runCheck(check);
|
|
495
|
+
}, check.interval * 1000);
|
|
496
|
+
|
|
497
|
+
this.intervals.set(check.id, interval);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
private async runCheck(check: HealthCheck): Promise<void> {
|
|
501
|
+
const startTime = Date.now();
|
|
502
|
+
|
|
503
|
+
try {
|
|
504
|
+
const result = await this.performCheck(check);
|
|
505
|
+
const responseTime = Date.now() - startTime;
|
|
506
|
+
|
|
507
|
+
check.lastCheck = new Date();
|
|
508
|
+
|
|
509
|
+
if (result.success) {
|
|
510
|
+
check.status = 'up';
|
|
511
|
+
check.lastSuccess = new Date();
|
|
512
|
+
check.consecutiveFailures = 0;
|
|
513
|
+
} else {
|
|
514
|
+
check.consecutiveFailures++;
|
|
515
|
+
|
|
516
|
+
if (check.consecutiveFailures >= check.retries) {
|
|
517
|
+
check.status = 'down';
|
|
518
|
+
await this.triggerAlert(check, 'down', result.error);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// Check for slow responses
|
|
523
|
+
if (responseTime > (check.timeout * 1000) / 2) {
|
|
524
|
+
await this.triggerAlert(check, 'slow', { responseTime });
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
} catch (error) {
|
|
528
|
+
check.consecutiveFailures++;
|
|
529
|
+
|
|
530
|
+
if (check.consecutiveFailures >= check.retries) {
|
|
531
|
+
check.status = 'down';
|
|
532
|
+
await this.triggerAlert(check, 'down', error.message);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
await this.persistCheck(check);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
private async performCheck(check: HealthCheck): Promise<CheckResult> {
|
|
540
|
+
switch (check.type) {
|
|
541
|
+
case 'http':
|
|
542
|
+
return this.checkHttp(check);
|
|
543
|
+
case 'tcp':
|
|
544
|
+
return this.checkTcp(check);
|
|
545
|
+
case 'dns':
|
|
546
|
+
return this.checkDns(check);
|
|
547
|
+
case 'ping':
|
|
548
|
+
return this.checkPing(check);
|
|
549
|
+
default:
|
|
550
|
+
throw new Error(`Unknown check type: ${check.type}`);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
private async checkHttp(check: HealthCheck): Promise<CheckResult> {
|
|
555
|
+
try {
|
|
556
|
+
const response = await fetch(check.target, {
|
|
557
|
+
method: 'GET',
|
|
558
|
+
signal: AbortSignal.timeout(check.timeout * 1000)
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
return {
|
|
562
|
+
success: response.ok,
|
|
563
|
+
statusCode: response.status,
|
|
564
|
+
error: response.ok ? undefined : `HTTP ${response.status}`
|
|
565
|
+
};
|
|
566
|
+
} catch (error) {
|
|
567
|
+
return {
|
|
568
|
+
success: false,
|
|
569
|
+
error: error.message
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
private async triggerAlert(check: HealthCheck, type: string, details: any): Promise<void> {
|
|
575
|
+
const matchingAlerts = check.alerts.filter(a => a.condition === type);
|
|
576
|
+
|
|
577
|
+
for (const alert of matchingAlerts) {
|
|
578
|
+
switch (alert.action) {
|
|
579
|
+
case 'notify':
|
|
580
|
+
await this.context.notify({
|
|
581
|
+
type: 'monitor_alert',
|
|
582
|
+
checkId: check.id,
|
|
583
|
+
checkName: check.name,
|
|
584
|
+
alertType: type,
|
|
585
|
+
details
|
|
586
|
+
});
|
|
587
|
+
break;
|
|
588
|
+
|
|
589
|
+
case 'webhook':
|
|
590
|
+
await fetch(alert.config.url, {
|
|
591
|
+
method: 'POST',
|
|
592
|
+
headers: { 'Content-Type': 'application/json' },
|
|
593
|
+
body: JSON.stringify({
|
|
594
|
+
check: check.name,
|
|
595
|
+
type,
|
|
596
|
+
details,
|
|
597
|
+
timestamp: new Date().toISOString()
|
|
598
|
+
})
|
|
599
|
+
});
|
|
600
|
+
break;
|
|
601
|
+
|
|
602
|
+
case 'workflow':
|
|
603
|
+
await this.workflowEngine.run(alert.config.workflowId, {
|
|
604
|
+
checkId: check.id,
|
|
605
|
+
alertType: type,
|
|
606
|
+
details
|
|
607
|
+
});
|
|
608
|
+
break;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
#### Day 8-10: Visual Workflow Editor
|
|
616
|
+
```typescript
|
|
617
|
+
// extensions/xyops/src/web/components/WorkflowEditor.tsx
|
|
618
|
+
import React, { useState, useCallback } from 'react';
|
|
619
|
+
import ReactFlow, {
|
|
620
|
+
Node,
|
|
621
|
+
Edge,
|
|
622
|
+
addEdge,
|
|
623
|
+
Background,
|
|
624
|
+
Controls,
|
|
625
|
+
useNodesState,
|
|
626
|
+
useEdgesState,
|
|
627
|
+
Connection
|
|
628
|
+
} from 'reactflow';
|
|
629
|
+
import 'reactflow/dist/style.css';
|
|
630
|
+
|
|
631
|
+
const nodeTypes = {
|
|
632
|
+
agent: AgentNode,
|
|
633
|
+
tool: ToolNode,
|
|
634
|
+
condition: ConditionNode,
|
|
635
|
+
loop: LoopNode,
|
|
636
|
+
wait: WaitNode
|
|
637
|
+
};
|
|
638
|
+
|
|
639
|
+
export const WorkflowEditor: React.FC = () => {
|
|
640
|
+
const [nodes, setNodes, onNodesChange] = useNodesState([]);
|
|
641
|
+
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
|
642
|
+
const [selectedNode, setSelectedNode] = useState<Node | null>(null);
|
|
643
|
+
|
|
644
|
+
const onConnect = useCallback(
|
|
645
|
+
(params: Connection) => setEdges((eds) => addEdge(params, eds)),
|
|
646
|
+
[setEdges]
|
|
647
|
+
);
|
|
648
|
+
|
|
649
|
+
const addNode = (type: string) => {
|
|
650
|
+
const newNode: Node = {
|
|
651
|
+
id: generateId(),
|
|
652
|
+
type,
|
|
653
|
+
position: { x: 100, y: 100 },
|
|
654
|
+
data: { label: `${type} node` }
|
|
655
|
+
};
|
|
656
|
+
setNodes((nds) => [...nds, newNode]);
|
|
657
|
+
};
|
|
658
|
+
|
|
659
|
+
const saveWorkflow = async () => {
|
|
660
|
+
const workflow = {
|
|
661
|
+
nodes,
|
|
662
|
+
edges,
|
|
663
|
+
// Convert to workflow format
|
|
664
|
+
steps: nodes.map(node => ({
|
|
665
|
+
id: node.id,
|
|
666
|
+
type: node.type,
|
|
667
|
+
config: node.data
|
|
668
|
+
}))
|
|
669
|
+
};
|
|
670
|
+
|
|
671
|
+
await fetch('/api/workflows', {
|
|
672
|
+
method: 'POST',
|
|
673
|
+
headers: { 'Content-Type': 'application/json' },
|
|
674
|
+
body: JSON.stringify(workflow)
|
|
675
|
+
});
|
|
676
|
+
};
|
|
677
|
+
|
|
678
|
+
return (
|
|
679
|
+
<div style={{ height: '100vh', display: 'flex' }}>
|
|
680
|
+
<div style={{ width: 250, padding: 20, borderRight: '1px solid #ddd' }}>
|
|
681
|
+
<h3>Toolbox</h3>
|
|
682
|
+
<button onClick={() => addNode('agent')}>Add Agent</button>
|
|
683
|
+
<button onClick={() => addNode('tool')}>Add Tool</button>
|
|
684
|
+
<button onClick={() => addNode('condition')}>Add Condition</button>
|
|
685
|
+
<button onClick={() => addNode('loop')}>Add Loop</button>
|
|
686
|
+
<button onClick={() => addNode('wait')}>Add Wait</button>
|
|
687
|
+
<hr />
|
|
688
|
+
<button onClick={saveWorkflow}>Save Workflow</button>
|
|
689
|
+
</div>
|
|
690
|
+
|
|
691
|
+
<div style={{ flex: 1 }}>
|
|
692
|
+
<ReactFlow
|
|
693
|
+
nodes={nodes}
|
|
694
|
+
edges={edges}
|
|
695
|
+
onNodesChange={onNodesChange}
|
|
696
|
+
onEdgesChange={onEdgesChange}
|
|
697
|
+
onConnect={onConnect}
|
|
698
|
+
nodeTypes={nodeTypes}
|
|
699
|
+
onNodeClick={(_, node) => setSelectedNode(node)}
|
|
700
|
+
fitView
|
|
701
|
+
>
|
|
702
|
+
<Background />
|
|
703
|
+
<Controls />
|
|
704
|
+
</ReactFlow>
|
|
705
|
+
</div>
|
|
706
|
+
|
|
707
|
+
{selectedNode && (
|
|
708
|
+
<NodeConfigPanel
|
|
709
|
+
node={selectedNode}
|
|
710
|
+
onUpdate={(data) => {
|
|
711
|
+
setNodes((nds) =>
|
|
712
|
+
nds.map((n) =>
|
|
713
|
+
n.id === selectedNode.id ? { ...n, data } : n
|
|
714
|
+
)
|
|
715
|
+
);
|
|
716
|
+
}}
|
|
717
|
+
/>
|
|
718
|
+
)}
|
|
719
|
+
</div>
|
|
720
|
+
);
|
|
721
|
+
};
|
|
722
|
+
```
|
|
723
|
+
|
|
724
|
+
### Week 3: Gateway Node & Integration
|
|
725
|
+
|
|
726
|
+
#### Day 11-12: Gateway Node
|
|
727
|
+
```typescript
|
|
728
|
+
// extensions/xyops-gateway/src/node.ts
|
|
729
|
+
import { GatewayNode } from '@poolbot/node-sdk';
|
|
730
|
+
|
|
731
|
+
class XyOpsGatewayNode extends GatewayNode {
|
|
732
|
+
private workflowEngine: WorkflowEngine;
|
|
733
|
+
private scheduleEngine: ScheduleEngine;
|
|
734
|
+
private monitorEngine: MonitorEngine;
|
|
735
|
+
|
|
736
|
+
async register() {
|
|
737
|
+
await this.send({
|
|
738
|
+
type: 'register',
|
|
739
|
+
payload: {
|
|
740
|
+
nodeId: this.nodeId,
|
|
741
|
+
capabilities: ['tools.workflow.*', 'tools.schedule.*', 'tools.monitor.*'],
|
|
742
|
+
tools: [
|
|
743
|
+
{
|
|
744
|
+
name: 'workflow.create',
|
|
745
|
+
description: 'Create a workflow',
|
|
746
|
+
parameters: { /* ... */ }
|
|
747
|
+
},
|
|
748
|
+
{
|
|
749
|
+
name: 'workflow.run',
|
|
750
|
+
description: 'Run a workflow',
|
|
751
|
+
parameters: { /* ... */ }
|
|
752
|
+
},
|
|
753
|
+
{
|
|
754
|
+
name: 'schedule.cron',
|
|
755
|
+
description: 'Schedule with cron',
|
|
756
|
+
parameters: { /* ... */ }
|
|
757
|
+
},
|
|
758
|
+
{
|
|
759
|
+
name: 'monitor.check',
|
|
760
|
+
description: 'Add health check',
|
|
761
|
+
parameters: { /* ... */ }
|
|
762
|
+
}
|
|
763
|
+
]
|
|
764
|
+
}
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
async handleToolCall(call: ToolCall) {
|
|
769
|
+
switch (call.tool) {
|
|
770
|
+
case 'workflow.create':
|
|
771
|
+
return this.workflowEngine.create(call.params);
|
|
772
|
+
case 'workflow.run':
|
|
773
|
+
return this.workflowEngine.run(call.params.workflowId, call.params.inputs);
|
|
774
|
+
case 'schedule.cron':
|
|
775
|
+
return this.scheduleEngine.schedule(call.params);
|
|
776
|
+
case 'monitor.check':
|
|
777
|
+
return this.monitorEngine.addCheck(call.params);
|
|
778
|
+
default:
|
|
779
|
+
throw new Error(`Unknown tool: ${call.tool}`);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
```
|
|
784
|
+
|
|
785
|
+
#### Day 13-14: CLI Integration
|
|
786
|
+
```typescript
|
|
787
|
+
// src/cli/mods.ts (enhancement)
|
|
788
|
+
command('mods:xyops')
|
|
789
|
+
.description('Manage xyOps workflow automation')
|
|
790
|
+
.option('--workflow-create <name>', 'Create new workflow')
|
|
791
|
+
.option('--workflow-run <id>', 'Run workflow')
|
|
792
|
+
.option('--schedule-add <cron>', 'Add scheduled job')
|
|
793
|
+
.option('--monitor-add <target>', 'Add health check')
|
|
794
|
+
.option('--status', 'Show status')
|
|
795
|
+
.action(async (options) => {
|
|
796
|
+
if (options.workflowCreate) {
|
|
797
|
+
const workflow = await createWorkflowFromPrompt(options.workflowCreate);
|
|
798
|
+
console.log(`Created workflow: ${workflow.id}`);
|
|
799
|
+
}
|
|
800
|
+
// ...
|
|
801
|
+
});
|
|
802
|
+
|
|
803
|
+
// Natural language workflow creation
|
|
804
|
+
async function createWorkflowFromPrompt(prompt: string): Promise<Workflow> {
|
|
805
|
+
const agent = await createAgent({ type: 'standard' });
|
|
806
|
+
|
|
807
|
+
const result = await agent.execute({
|
|
808
|
+
message: `
|
|
809
|
+
Create a workflow based on this request: "${prompt}"
|
|
810
|
+
|
|
811
|
+
Return a JSON object with:
|
|
812
|
+
- name: workflow name
|
|
813
|
+
- steps: array of steps with type, name, and config
|
|
814
|
+
|
|
815
|
+
Available step types:
|
|
816
|
+
- agent: AI agent execution
|
|
817
|
+
- tool: Tool call
|
|
818
|
+
- condition: If/else branch
|
|
819
|
+
- loop: Repeat steps
|
|
820
|
+
- wait: Delay execution
|
|
821
|
+
`
|
|
822
|
+
});
|
|
823
|
+
|
|
824
|
+
return workflowEngine.create(result.parsed);
|
|
825
|
+
}
|
|
826
|
+
```
|
|
827
|
+
|
|
828
|
+
#### Day 15: Testing & Documentation
|
|
829
|
+
- Unit tests for all engines
|
|
830
|
+
- Integration tests with Gateway
|
|
831
|
+
- Visual editor tests
|
|
832
|
+
- Documentation
|
|
833
|
+
|
|
834
|
+
## File Structure
|
|
835
|
+
|
|
836
|
+
```
|
|
837
|
+
extensions/xyops/
|
|
838
|
+
├── src/
|
|
839
|
+
│ ├── plugin.ts # PoolBot plugin entry
|
|
840
|
+
│ ├── workflow/
|
|
841
|
+
│ │ ├── engine.ts # Workflow execution
|
|
842
|
+
│ │ ├── execution.ts # Execution context
|
|
843
|
+
│ │ └── types.ts # Type definitions
|
|
844
|
+
│ ├── schedule/
|
|
845
|
+
│ │ ├── engine.ts # Cron scheduling
|
|
846
|
+
│ │ └── types.ts
|
|
847
|
+
│ ├── monitor/
|
|
848
|
+
│ │ ├── engine.ts # Health monitoring
|
|
849
|
+
│ │ └── types.ts
|
|
850
|
+
│ ├── web/
|
|
851
|
+
│ │ ├── components/
|
|
852
|
+
│ │ │ ├── WorkflowEditor.tsx
|
|
853
|
+
│ │ │ ├── NodeConfigPanel.tsx
|
|
854
|
+
│ │ │ └── NodeTypes/
|
|
855
|
+
│ │ ├── App.tsx
|
|
856
|
+
│ │ └── index.tsx
|
|
857
|
+
│ └── index.ts
|
|
858
|
+
├── package.json
|
|
859
|
+
├── tsconfig.json
|
|
860
|
+
└── README.md
|
|
861
|
+
```
|
|
862
|
+
|
|
863
|
+
## Usage Examples
|
|
864
|
+
|
|
865
|
+
### Example 1: Create Workflow
|
|
866
|
+
```typescript
|
|
867
|
+
const workflow = await agent.execute({
|
|
868
|
+
skill: 'workflow.create',
|
|
869
|
+
params: {
|
|
870
|
+
name: 'Daily Report',
|
|
871
|
+
steps: [
|
|
872
|
+
{
|
|
873
|
+
name: 'fetch_data',
|
|
874
|
+
type: 'tool',
|
|
875
|
+
config: {
|
|
876
|
+
tool: 'database.query',
|
|
877
|
+
params: { sql: 'SELECT * FROM sales WHERE date = TODAY' }
|
|
878
|
+
}
|
|
879
|
+
},
|
|
880
|
+
{
|
|
881
|
+
name: 'analyze',
|
|
882
|
+
type: 'agent',
|
|
883
|
+
config: {
|
|
884
|
+
prompt: 'Analyze this sales data and create a summary',
|
|
885
|
+
outputVar: 'summary'
|
|
886
|
+
}
|
|
887
|
+
},
|
|
888
|
+
{
|
|
889
|
+
name: 'send_report',
|
|
890
|
+
type: 'tool',
|
|
891
|
+
config: {
|
|
892
|
+
tool: 'email.send',
|
|
893
|
+
params: {
|
|
894
|
+
to: 'manager@company.com',
|
|
895
|
+
subject: 'Daily Sales Report',
|
|
896
|
+
body: '{{summary}}'
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
]
|
|
901
|
+
}
|
|
902
|
+
});
|
|
903
|
+
```
|
|
904
|
+
|
|
905
|
+
### Example 2: Schedule Workflow
|
|
906
|
+
```typescript
|
|
907
|
+
await agent.execute({
|
|
908
|
+
skill: 'schedule.cron',
|
|
909
|
+
params: {
|
|
910
|
+
workflowId: workflow.id,
|
|
911
|
+
cron: '0 9 * * *', // Every day at 9am
|
|
912
|
+
name: 'Daily morning report'
|
|
913
|
+
}
|
|
914
|
+
});
|
|
915
|
+
|
|
916
|
+
// Or use natural language
|
|
917
|
+
await agent.execute({
|
|
918
|
+
skill: 'schedule.cron',
|
|
919
|
+
params: {
|
|
920
|
+
workflowId: workflow.id,
|
|
921
|
+
cron: 'natural:every weekday at 9am'
|
|
922
|
+
}
|
|
923
|
+
});
|
|
924
|
+
```
|
|
925
|
+
|
|
926
|
+
### Example 3: Monitor Service
|
|
927
|
+
```typescript
|
|
928
|
+
await agent.execute({
|
|
929
|
+
skill: 'monitor.check',
|
|
930
|
+
params: {
|
|
931
|
+
name: 'API Health',
|
|
932
|
+
target: 'https://api.example.com/health',
|
|
933
|
+
type: 'http',
|
|
934
|
+
interval: 60,
|
|
935
|
+
alerts: [
|
|
936
|
+
{
|
|
937
|
+
condition: 'down',
|
|
938
|
+
action: 'notify'
|
|
939
|
+
},
|
|
940
|
+
{
|
|
941
|
+
condition: 'slow',
|
|
942
|
+
threshold: 5000,
|
|
943
|
+
action: 'webhook',
|
|
944
|
+
config: { url: 'https://alerts.example.com/webhook' }
|
|
945
|
+
}
|
|
946
|
+
]
|
|
947
|
+
}
|
|
948
|
+
});
|
|
949
|
+
```
|
|
950
|
+
|
|
951
|
+
## Success Criteria
|
|
952
|
+
|
|
953
|
+
- [ ] Workflow engine functional
|
|
954
|
+
- [ ] Schedule engine with cron support
|
|
955
|
+
- [ ] Monitor engine with HTTP/TCP/DNS checks
|
|
956
|
+
- [ ] Visual workflow editor
|
|
957
|
+
- [ ] Natural language workflow creation
|
|
958
|
+
- [ ] Gateway Node mode working
|
|
959
|
+
- [ ] CLI integration complete
|
|
960
|
+
- [ ] Tests pass (>80% coverage)
|
|
961
|
+
- [ ] Documentation complete
|
|
962
|
+
|
|
963
|
+
## Docker Support
|
|
964
|
+
|
|
965
|
+
```dockerfile
|
|
966
|
+
FROM node:20-alpine
|
|
967
|
+
|
|
968
|
+
WORKDIR /app
|
|
969
|
+
COPY package*.json ./
|
|
970
|
+
RUN npm install
|
|
971
|
+
|
|
972
|
+
COPY . .
|
|
973
|
+
RUN npm run build
|
|
974
|
+
|
|
975
|
+
EXPOSE 3000
|
|
976
|
+
|
|
977
|
+
CMD ["node", "dist/node.js"]
|
|
978
|
+
```
|