elsabro 2.2.0 → 3.7.0
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/README.md +668 -20
- package/agents/elsabro-orchestrator.md +113 -0
- package/bin/install.js +0 -0
- package/commands/elsabro/execute.md +223 -46
- package/commands/elsabro/start.md +34 -0
- package/commands/elsabro/verify-work.md +29 -0
- package/flows/development-flow.json +452 -0
- package/flows/quick-flow.json +118 -0
- package/hooks/confirm-destructive.sh +145 -0
- package/hooks/hooks-config.json +81 -0
- package/hooks/lint-check.sh +238 -0
- package/hooks/post-edit-test.sh +189 -0
- package/package.json +5 -3
- package/references/SYSTEM_INDEX.md +379 -5
- package/references/agent-marketplace.md +2274 -0
- package/references/agent-protocol.md +1126 -0
- package/references/ai-code-suggestions.md +2413 -0
- package/references/checkpointing.md +595 -0
- package/references/collaboration-patterns.md +851 -0
- package/references/collaborative-sessions.md +1081 -0
- package/references/configuration-management.md +1810 -0
- package/references/cost-tracking.md +1095 -0
- package/references/enterprise-sso.md +2001 -0
- package/references/error-contracts-tests.md +1171 -0
- package/references/error-contracts-v2.md +968 -0
- package/references/error-contracts.md +3102 -0
- package/references/event-driven.md +1031 -0
- package/references/flow-orchestration.md +940 -0
- package/references/flow-visualization.md +1557 -0
- package/references/ide-integrations.md +3513 -0
- package/references/interrupt-system.md +681 -0
- package/references/kubernetes-deployment.md +3099 -0
- package/references/memory-system.md +683 -0
- package/references/mobile-companion.md +3236 -0
- package/references/multi-llm-providers.md +2494 -0
- package/references/multi-project-memory.md +1182 -0
- package/references/observability.md +793 -0
- package/references/output-schemas.md +858 -0
- package/references/parallel-worktrees.md +293 -0
- package/references/performance-profiler.md +955 -0
- package/references/plugin-system.md +1526 -0
- package/references/prompt-management.md +292 -0
- package/references/sandbox-execution.md +303 -0
- package/references/security-system.md +1253 -0
- package/references/streaming.md +696 -0
- package/references/testing-framework.md +1151 -0
- package/references/time-travel.md +802 -0
- package/references/tool-registry.md +886 -0
- package/references/voice-commands.md +3296 -0
- package/scripts/setup-parallel-worktrees.sh +319 -0
- package/skills/memory-update.md +207 -0
- package/skills/review.md +331 -0
- package/skills/techdebt.md +289 -0
- package/skills/tutor.md +219 -0
- package/templates/.planning/notes/.gitkeep +0 -0
- package/templates/CLAUDE.md.template +48 -0
- package/templates/agent-marketplace-config.json +220 -0
- package/templates/agent-protocol-config.json +136 -0
- package/templates/ai-suggestions-config.json +100 -0
- package/templates/checkpoint-state.json +61 -0
- package/templates/collaboration-config.json +157 -0
- package/templates/collaborative-sessions-config.json +153 -0
- package/templates/configuration-config.json +245 -0
- package/templates/cost-tracking-config.json +148 -0
- package/templates/enterprise-sso-config.json +438 -0
- package/templates/error-handling-config.json +79 -2
- package/templates/events-config.json +148 -0
- package/templates/flow-visualization-config.json +196 -0
- package/templates/ide-integrations-config.json +442 -0
- package/templates/kubernetes-config.json +764 -0
- package/templates/memory-state.json +84 -0
- package/templates/mistakes.md.template +52 -0
- package/templates/mobile-companion-config.json +600 -0
- package/templates/multi-llm-config.json +544 -0
- package/templates/multi-project-memory-config.json +145 -0
- package/templates/observability-config.json +109 -0
- package/templates/patterns.md.template +114 -0
- package/templates/performance-profiler-config.json +125 -0
- package/templates/plugin-config.json +170 -0
- package/templates/prompt-management-config.json +86 -0
- package/templates/sandbox-config.json +185 -0
- package/templates/schemas-config.json +65 -0
- package/templates/security-config.json +120 -0
- package/templates/streaming-config.json +72 -0
- package/templates/testing-config.json +81 -0
- package/templates/timetravel-config.json +62 -0
- package/templates/tool-registry-config.json +109 -0
- package/templates/voice-commands-config.json +658 -0
|
@@ -0,0 +1,940 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: flow-orchestration
|
|
3
|
+
description: Sistema de orquestacion basado en grafos inspirado en LangGraph
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# ELSABRO Flow-Based Orchestration
|
|
8
|
+
|
|
9
|
+
## Vision General
|
|
10
|
+
|
|
11
|
+
El sistema de Flow Orchestration permite definir workflows complejos como grafos dirigidos, con soporte para:
|
|
12
|
+
- **Nodos**: Agentes, funciones, decisiones
|
|
13
|
+
- **Edges**: Transiciones condicionales o incondicionales
|
|
14
|
+
- **Subflows**: Workflows reutilizables anidados
|
|
15
|
+
- **Parallel branches**: Ejecución paralela de ramas independientes
|
|
16
|
+
- **Interrupts**: Pausas programáticas para intervención humana
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
┌──────────────────────────────────────────────────────────────────────────┐
|
|
20
|
+
│ FLOW ARCHITECTURE │
|
|
21
|
+
├──────────────────────────────────────────────────────────────────────────┤
|
|
22
|
+
│ │
|
|
23
|
+
│ ┌───────────┐ │
|
|
24
|
+
│ │ START │ │
|
|
25
|
+
│ └─────┬─────┘ │
|
|
26
|
+
│ │ │
|
|
27
|
+
│ ▼ │
|
|
28
|
+
│ ┌───────────┐ condition=true ┌───────────┐ │
|
|
29
|
+
│ │ Analyst │ ──────────────────────► │ Planner │ │
|
|
30
|
+
│ └───────────┘ └─────┬─────┘ │
|
|
31
|
+
│ │ │ │
|
|
32
|
+
│ │ condition=false │ │
|
|
33
|
+
│ ▼ ▼ │
|
|
34
|
+
│ ┌───────────┐ ┌─────────────────────┐ │
|
|
35
|
+
│ │ Quick │ │ PARALLEL BRANCH │ │
|
|
36
|
+
│ │ Mode │ ├─────────────────────┤ │
|
|
37
|
+
│ └─────┬─────┘ │ ┌─────┐ ┌─────┐ │ │
|
|
38
|
+
│ │ │ │Exec │ │ QA │ │ │
|
|
39
|
+
│ │ │ └──┬──┘ └──┬──┘ │ │
|
|
40
|
+
│ │ │ │ │ │ │
|
|
41
|
+
│ │ │ └────┬────┘ │ │
|
|
42
|
+
│ │ │ │ │ │
|
|
43
|
+
│ │ └─────────┼──────────┘ │
|
|
44
|
+
│ │ │ │
|
|
45
|
+
│ │ ▼ │
|
|
46
|
+
│ │ ┌───────────────┐ │
|
|
47
|
+
│ │ │ Verifier │ │
|
|
48
|
+
│ │ └───────┬───────┘ │
|
|
49
|
+
│ │ │ │
|
|
50
|
+
│ └──────────────────────────────────┤ │
|
|
51
|
+
│ │ │
|
|
52
|
+
│ ▼ │
|
|
53
|
+
│ ┌───────────┐ │
|
|
54
|
+
│ │ END │ │
|
|
55
|
+
│ └───────────┘ │
|
|
56
|
+
│ │
|
|
57
|
+
└──────────────────────────────────────────────────────────────────────────┘
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Definición de Flows
|
|
63
|
+
|
|
64
|
+
### Flow Definition Schema
|
|
65
|
+
|
|
66
|
+
```json
|
|
67
|
+
{
|
|
68
|
+
"id": "flow_feature_dev",
|
|
69
|
+
"name": "Feature Development Flow",
|
|
70
|
+
"version": "1.0.0",
|
|
71
|
+
"description": "Workflow completo para desarrollar una feature",
|
|
72
|
+
|
|
73
|
+
"config": {
|
|
74
|
+
"timeout": 3600000,
|
|
75
|
+
"maxRetries": 3,
|
|
76
|
+
"checkpointEnabled": true,
|
|
77
|
+
"interruptEnabled": true
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
"inputs": {
|
|
81
|
+
"featureDescription": { "type": "string", "required": true },
|
|
82
|
+
"complexity": { "type": "enum", "values": ["low", "medium", "high"], "default": "medium" }
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
"outputs": {
|
|
86
|
+
"filesCreated": { "type": "array" },
|
|
87
|
+
"testsCreated": { "type": "array" },
|
|
88
|
+
"success": { "type": "boolean" }
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
"nodes": [
|
|
92
|
+
{
|
|
93
|
+
"id": "start",
|
|
94
|
+
"type": "entry",
|
|
95
|
+
"next": "analyze"
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"id": "analyze",
|
|
99
|
+
"type": "agent",
|
|
100
|
+
"agent": "elsabro-analyst",
|
|
101
|
+
"config": {
|
|
102
|
+
"model": "opus",
|
|
103
|
+
"timeout": 300000
|
|
104
|
+
},
|
|
105
|
+
"inputs": {
|
|
106
|
+
"task": "{{inputs.featureDescription}}"
|
|
107
|
+
},
|
|
108
|
+
"outputs": ["analysis", "complexity", "scope"]
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"id": "route_complexity",
|
|
112
|
+
"type": "router",
|
|
113
|
+
"condition": "{{nodes.analyze.outputs.complexity}}",
|
|
114
|
+
"routes": {
|
|
115
|
+
"low": "quick_implement",
|
|
116
|
+
"medium": "plan_implement",
|
|
117
|
+
"high": "detailed_plan"
|
|
118
|
+
},
|
|
119
|
+
"default": "plan_implement"
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"id": "quick_implement",
|
|
123
|
+
"type": "agent",
|
|
124
|
+
"agent": "elsabro-quick-dev",
|
|
125
|
+
"next": "verify"
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"id": "plan_implement",
|
|
129
|
+
"type": "subflow",
|
|
130
|
+
"flow": "planning_subflow",
|
|
131
|
+
"next": "parallel_dev"
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
"id": "detailed_plan",
|
|
135
|
+
"type": "sequence",
|
|
136
|
+
"steps": [
|
|
137
|
+
{ "agent": "elsabro-analyst", "as": "deep_analysis" },
|
|
138
|
+
{ "agent": "elsabro-planner", "as": "architecture" }
|
|
139
|
+
],
|
|
140
|
+
"next": "interrupt_approval"
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
"id": "interrupt_approval",
|
|
144
|
+
"type": "interrupt",
|
|
145
|
+
"reason": "Plan requires approval for high complexity feature",
|
|
146
|
+
"display": {
|
|
147
|
+
"title": "Plan Approval Required",
|
|
148
|
+
"content": "{{nodes.detailed_plan.outputs.architecture}}",
|
|
149
|
+
"options": ["approve", "modify", "reject"]
|
|
150
|
+
},
|
|
151
|
+
"routes": {
|
|
152
|
+
"approve": "parallel_dev",
|
|
153
|
+
"modify": "detailed_plan",
|
|
154
|
+
"reject": "end_rejected"
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
"id": "parallel_dev",
|
|
159
|
+
"type": "parallel",
|
|
160
|
+
"branches": [
|
|
161
|
+
{
|
|
162
|
+
"id": "implementation",
|
|
163
|
+
"agent": "elsabro-executor",
|
|
164
|
+
"config": { "model": "opus" }
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
"id": "testing",
|
|
168
|
+
"agent": "elsabro-qa",
|
|
169
|
+
"config": { "model": "opus" }
|
|
170
|
+
}
|
|
171
|
+
],
|
|
172
|
+
"joinType": "all",
|
|
173
|
+
"next": "verify"
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
"id": "verify",
|
|
177
|
+
"type": "agent",
|
|
178
|
+
"agent": "elsabro-verifier",
|
|
179
|
+
"config": { "model": "opus" }
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
"id": "verify_check",
|
|
183
|
+
"type": "condition",
|
|
184
|
+
"condition": "{{nodes.verify.outputs.passed}}",
|
|
185
|
+
"true": "end_success",
|
|
186
|
+
"false": "fix_issues"
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
"id": "fix_issues",
|
|
190
|
+
"type": "agent",
|
|
191
|
+
"agent": "elsabro-debugger",
|
|
192
|
+
"next": "verify",
|
|
193
|
+
"maxIterations": 3,
|
|
194
|
+
"onMaxIterations": "interrupt_manual"
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
"id": "interrupt_manual",
|
|
198
|
+
"type": "interrupt",
|
|
199
|
+
"reason": "Auto-fix failed after 3 attempts",
|
|
200
|
+
"display": {
|
|
201
|
+
"title": "Manual Intervention Required",
|
|
202
|
+
"errors": "{{nodes.verify.outputs.errors}}"
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
"id": "end_success",
|
|
207
|
+
"type": "exit",
|
|
208
|
+
"status": "success",
|
|
209
|
+
"outputs": {
|
|
210
|
+
"filesCreated": "{{collectOutputs('filesCreated')}}",
|
|
211
|
+
"testsCreated": "{{collectOutputs('testsCreated')}}",
|
|
212
|
+
"success": true
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
"id": "end_rejected",
|
|
217
|
+
"type": "exit",
|
|
218
|
+
"status": "rejected",
|
|
219
|
+
"outputs": {
|
|
220
|
+
"success": false,
|
|
221
|
+
"reason": "Plan rejected by user"
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
],
|
|
225
|
+
|
|
226
|
+
"edges": [
|
|
227
|
+
{ "from": "start", "to": "analyze" },
|
|
228
|
+
{ "from": "analyze", "to": "route_complexity" },
|
|
229
|
+
{ "from": "verify", "to": "verify_check" }
|
|
230
|
+
]
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Tipos de Nodos
|
|
237
|
+
|
|
238
|
+
### 1. Entry Node
|
|
239
|
+
|
|
240
|
+
Punto de entrada del flow.
|
|
241
|
+
|
|
242
|
+
```json
|
|
243
|
+
{
|
|
244
|
+
"id": "start",
|
|
245
|
+
"type": "entry",
|
|
246
|
+
"next": "first_node"
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### 2. Agent Node
|
|
251
|
+
|
|
252
|
+
Ejecuta un agente ELSABRO.
|
|
253
|
+
|
|
254
|
+
```json
|
|
255
|
+
{
|
|
256
|
+
"id": "analyze",
|
|
257
|
+
"type": "agent",
|
|
258
|
+
"agent": "elsabro-analyst",
|
|
259
|
+
"config": {
|
|
260
|
+
"model": "opus",
|
|
261
|
+
"timeout": 300000,
|
|
262
|
+
"retries": 2
|
|
263
|
+
},
|
|
264
|
+
"inputs": {
|
|
265
|
+
"task": "{{inputs.description}}",
|
|
266
|
+
"context": "{{state.previousAnalysis}}"
|
|
267
|
+
},
|
|
268
|
+
"outputs": ["result", "decisions"],
|
|
269
|
+
"next": "next_node"
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### 3. Router Node
|
|
274
|
+
|
|
275
|
+
Enruta basado en condiciones.
|
|
276
|
+
|
|
277
|
+
```json
|
|
278
|
+
{
|
|
279
|
+
"id": "complexity_router",
|
|
280
|
+
"type": "router",
|
|
281
|
+
"condition": "{{nodes.analyze.outputs.complexity}}",
|
|
282
|
+
"routes": {
|
|
283
|
+
"low": "quick_path",
|
|
284
|
+
"medium": "normal_path",
|
|
285
|
+
"high": "detailed_path"
|
|
286
|
+
},
|
|
287
|
+
"default": "normal_path"
|
|
288
|
+
}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### 4. Condition Node
|
|
292
|
+
|
|
293
|
+
Bifurcación binaria simple.
|
|
294
|
+
|
|
295
|
+
```json
|
|
296
|
+
{
|
|
297
|
+
"id": "check_tests",
|
|
298
|
+
"type": "condition",
|
|
299
|
+
"condition": "{{nodes.test.outputs.passed}} === true",
|
|
300
|
+
"true": "deploy",
|
|
301
|
+
"false": "fix_tests"
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### 5. Parallel Node
|
|
306
|
+
|
|
307
|
+
Ejecuta múltiples ramas en paralelo.
|
|
308
|
+
|
|
309
|
+
```json
|
|
310
|
+
{
|
|
311
|
+
"id": "parallel_work",
|
|
312
|
+
"type": "parallel",
|
|
313
|
+
"branches": [
|
|
314
|
+
{ "id": "code", "agent": "elsabro-executor" },
|
|
315
|
+
{ "id": "tests", "agent": "elsabro-qa" },
|
|
316
|
+
{ "id": "docs", "agent": "elsabro-tech-writer" }
|
|
317
|
+
],
|
|
318
|
+
"joinType": "all",
|
|
319
|
+
"timeout": 600000,
|
|
320
|
+
"next": "merge_results"
|
|
321
|
+
}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
**Join Types**:
|
|
325
|
+
- `all`: Espera que todas terminen
|
|
326
|
+
- `any`: Continúa cuando cualquiera termine
|
|
327
|
+
- `quorum`: Continúa cuando >50% termine
|
|
328
|
+
- `first_success`: Continúa con el primer éxito
|
|
329
|
+
|
|
330
|
+
### 6. Sequence Node
|
|
331
|
+
|
|
332
|
+
Ejecuta pasos en secuencia.
|
|
333
|
+
|
|
334
|
+
```json
|
|
335
|
+
{
|
|
336
|
+
"id": "analysis_sequence",
|
|
337
|
+
"type": "sequence",
|
|
338
|
+
"steps": [
|
|
339
|
+
{ "agent": "elsabro-analyst", "as": "market" },
|
|
340
|
+
{ "agent": "elsabro-analyst", "as": "tech" },
|
|
341
|
+
{ "agent": "elsabro-planner", "as": "plan" }
|
|
342
|
+
],
|
|
343
|
+
"next": "implementation"
|
|
344
|
+
}
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### 7. Subflow Node
|
|
348
|
+
|
|
349
|
+
Ejecuta otro flow como subrutina.
|
|
350
|
+
|
|
351
|
+
```json
|
|
352
|
+
{
|
|
353
|
+
"id": "auth_feature",
|
|
354
|
+
"type": "subflow",
|
|
355
|
+
"flow": "authentication_flow",
|
|
356
|
+
"inputs": {
|
|
357
|
+
"provider": "nextauth",
|
|
358
|
+
"features": ["email", "oauth"]
|
|
359
|
+
},
|
|
360
|
+
"next": "continue"
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
### 8. Interrupt Node
|
|
365
|
+
|
|
366
|
+
Pausa para intervención humana.
|
|
367
|
+
|
|
368
|
+
```json
|
|
369
|
+
{
|
|
370
|
+
"id": "approval_gate",
|
|
371
|
+
"type": "interrupt",
|
|
372
|
+
"reason": "Approval required before deployment",
|
|
373
|
+
"display": {
|
|
374
|
+
"title": "Deployment Approval",
|
|
375
|
+
"content": "Review changes before proceeding",
|
|
376
|
+
"data": "{{nodes.review.outputs.summary}}",
|
|
377
|
+
"options": [
|
|
378
|
+
{ "id": "approve", "label": "Approve & Deploy" },
|
|
379
|
+
{ "id": "reject", "label": "Reject" },
|
|
380
|
+
{ "id": "modify", "label": "Request Changes" }
|
|
381
|
+
]
|
|
382
|
+
},
|
|
383
|
+
"routes": {
|
|
384
|
+
"approve": "deploy",
|
|
385
|
+
"reject": "end_rejected",
|
|
386
|
+
"modify": "make_changes"
|
|
387
|
+
},
|
|
388
|
+
"timeout": 86400000
|
|
389
|
+
}
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
### 9. Loop Node
|
|
393
|
+
|
|
394
|
+
Repite hasta condición.
|
|
395
|
+
|
|
396
|
+
```json
|
|
397
|
+
{
|
|
398
|
+
"id": "fix_loop",
|
|
399
|
+
"type": "loop",
|
|
400
|
+
"body": [
|
|
401
|
+
{ "agent": "elsabro-debugger" },
|
|
402
|
+
{ "agent": "elsabro-verifier" }
|
|
403
|
+
],
|
|
404
|
+
"condition": "{{nodes.verifier.outputs.passed}} === false",
|
|
405
|
+
"maxIterations": 5,
|
|
406
|
+
"onMaxIterations": "escalate"
|
|
407
|
+
}
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
### 10. Exit Node
|
|
411
|
+
|
|
412
|
+
Punto de salida del flow.
|
|
413
|
+
|
|
414
|
+
```json
|
|
415
|
+
{
|
|
416
|
+
"id": "end_success",
|
|
417
|
+
"type": "exit",
|
|
418
|
+
"status": "success",
|
|
419
|
+
"outputs": {
|
|
420
|
+
"files": "{{collectOutputs('files')}}",
|
|
421
|
+
"duration": "{{duration()}}"
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
---
|
|
427
|
+
|
|
428
|
+
## API del Flow Engine
|
|
429
|
+
|
|
430
|
+
### FlowEngine
|
|
431
|
+
|
|
432
|
+
```javascript
|
|
433
|
+
/**
|
|
434
|
+
* FlowEngine
|
|
435
|
+
* Motor de ejecución de flows basados en grafos
|
|
436
|
+
*/
|
|
437
|
+
class FlowEngine {
|
|
438
|
+
constructor(options = {}) {
|
|
439
|
+
this.checkpointManager = options.checkpointManager || new CheckpointManager();
|
|
440
|
+
this.memoryManager = options.memoryManager || new MemoryManager();
|
|
441
|
+
this.agentRegistry = options.agentRegistry || new AgentRegistry();
|
|
442
|
+
|
|
443
|
+
this.state = {
|
|
444
|
+
currentNode: null,
|
|
445
|
+
variables: {},
|
|
446
|
+
nodeOutputs: {},
|
|
447
|
+
history: [],
|
|
448
|
+
interrupts: []
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Carga y valida una definición de flow
|
|
454
|
+
*/
|
|
455
|
+
async loadFlow(flowDefinition) {
|
|
456
|
+
// Validar schema
|
|
457
|
+
this.validateFlowSchema(flowDefinition);
|
|
458
|
+
|
|
459
|
+
// Construir grafo
|
|
460
|
+
this.flow = flowDefinition;
|
|
461
|
+
this.graph = this.buildGraph(flowDefinition);
|
|
462
|
+
|
|
463
|
+
// Validar grafo (ciclos, nodos huérfanos, etc.)
|
|
464
|
+
this.validateGraph();
|
|
465
|
+
|
|
466
|
+
return this;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Ejecuta el flow desde el inicio
|
|
471
|
+
*/
|
|
472
|
+
async execute(inputs = {}) {
|
|
473
|
+
// Inicializar estado
|
|
474
|
+
this.state.variables = { ...inputs };
|
|
475
|
+
this.state.startedAt = new Date().toISOString();
|
|
476
|
+
|
|
477
|
+
// Crear checkpoint inicial
|
|
478
|
+
await this.checkpointManager.createCheckpoint({
|
|
479
|
+
flowId: this.flow.id,
|
|
480
|
+
execution: { status: 'ACTIVE', currentNode: 'start' },
|
|
481
|
+
state: this.state
|
|
482
|
+
}, 'FLOW_START');
|
|
483
|
+
|
|
484
|
+
// Encontrar nodo de entrada
|
|
485
|
+
const entryNode = this.findEntryNode();
|
|
486
|
+
|
|
487
|
+
// Ejecutar desde entrada
|
|
488
|
+
return this.executeNode(entryNode);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Resume un flow desde un checkpoint
|
|
493
|
+
*/
|
|
494
|
+
async resume(checkpointId) {
|
|
495
|
+
const checkpoint = await this.checkpointManager.loadCheckpoint(checkpointId);
|
|
496
|
+
|
|
497
|
+
// Restaurar estado
|
|
498
|
+
this.state = checkpoint.state;
|
|
499
|
+
|
|
500
|
+
// Encontrar nodo donde quedó
|
|
501
|
+
const currentNode = this.graph.getNode(checkpoint.execution.currentNode);
|
|
502
|
+
|
|
503
|
+
// Continuar ejecución
|
|
504
|
+
return this.executeNode(currentNode);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* Resume desde un interrupt
|
|
509
|
+
*/
|
|
510
|
+
async resumeFromInterrupt(interruptId, response) {
|
|
511
|
+
const interrupt = this.state.interrupts.find(i => i.id === interruptId);
|
|
512
|
+
|
|
513
|
+
if (!interrupt) {
|
|
514
|
+
throw new Error(`Interrupt ${interruptId} not found`);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// Determinar siguiente nodo basado en respuesta
|
|
518
|
+
const nextNode = interrupt.routes[response];
|
|
519
|
+
|
|
520
|
+
if (!nextNode) {
|
|
521
|
+
throw new Error(`Invalid response: ${response}`);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// Marcar interrupt como resuelto
|
|
525
|
+
interrupt.resolvedAt = new Date().toISOString();
|
|
526
|
+
interrupt.response = response;
|
|
527
|
+
|
|
528
|
+
// Continuar ejecución
|
|
529
|
+
return this.executeNode(this.graph.getNode(nextNode));
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Ejecuta un nodo específico
|
|
534
|
+
*/
|
|
535
|
+
async executeNode(node) {
|
|
536
|
+
this.state.currentNode = node.id;
|
|
537
|
+
this.state.history.push({
|
|
538
|
+
nodeId: node.id,
|
|
539
|
+
startedAt: new Date().toISOString()
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
// Checkpoint antes de ejecutar
|
|
543
|
+
await this.checkpointManager.updateCheckpoint({
|
|
544
|
+
execution: { currentNode: node.id },
|
|
545
|
+
state: this.state
|
|
546
|
+
}, 'NODE_START');
|
|
547
|
+
|
|
548
|
+
try {
|
|
549
|
+
// Ejecutar según tipo de nodo
|
|
550
|
+
const result = await this.executeByType(node);
|
|
551
|
+
|
|
552
|
+
// Guardar output
|
|
553
|
+
this.state.nodeOutputs[node.id] = result;
|
|
554
|
+
|
|
555
|
+
// Actualizar historial
|
|
556
|
+
const historyEntry = this.state.history[this.state.history.length - 1];
|
|
557
|
+
historyEntry.completedAt = new Date().toISOString();
|
|
558
|
+
historyEntry.status = 'completed';
|
|
559
|
+
historyEntry.output = result;
|
|
560
|
+
|
|
561
|
+
// Checkpoint después de ejecutar
|
|
562
|
+
await this.checkpointManager.updateCheckpoint({
|
|
563
|
+
state: this.state
|
|
564
|
+
}, 'NODE_COMPLETE');
|
|
565
|
+
|
|
566
|
+
// Determinar siguiente nodo
|
|
567
|
+
const nextNode = await this.determineNextNode(node, result);
|
|
568
|
+
|
|
569
|
+
if (nextNode) {
|
|
570
|
+
return this.executeNode(nextNode);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// Si no hay siguiente, terminamos
|
|
574
|
+
return this.finalize(result);
|
|
575
|
+
|
|
576
|
+
} catch (error) {
|
|
577
|
+
return this.handleNodeError(node, error);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* Ejecuta nodo según su tipo
|
|
583
|
+
*/
|
|
584
|
+
async executeByType(node) {
|
|
585
|
+
switch (node.type) {
|
|
586
|
+
case 'entry':
|
|
587
|
+
return this.executeEntryNode(node);
|
|
588
|
+
|
|
589
|
+
case 'agent':
|
|
590
|
+
return this.executeAgentNode(node);
|
|
591
|
+
|
|
592
|
+
case 'router':
|
|
593
|
+
return this.executeRouterNode(node);
|
|
594
|
+
|
|
595
|
+
case 'condition':
|
|
596
|
+
return this.executeConditionNode(node);
|
|
597
|
+
|
|
598
|
+
case 'parallel':
|
|
599
|
+
return this.executeParallelNode(node);
|
|
600
|
+
|
|
601
|
+
case 'sequence':
|
|
602
|
+
return this.executeSequenceNode(node);
|
|
603
|
+
|
|
604
|
+
case 'subflow':
|
|
605
|
+
return this.executeSubflowNode(node);
|
|
606
|
+
|
|
607
|
+
case 'interrupt':
|
|
608
|
+
return this.executeInterruptNode(node);
|
|
609
|
+
|
|
610
|
+
case 'loop':
|
|
611
|
+
return this.executeLoopNode(node);
|
|
612
|
+
|
|
613
|
+
case 'exit':
|
|
614
|
+
return this.executeExitNode(node);
|
|
615
|
+
|
|
616
|
+
default:
|
|
617
|
+
throw new Error(`Unknown node type: ${node.type}`);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* Ejecuta nodo de agente
|
|
623
|
+
*/
|
|
624
|
+
async executeAgentNode(node) {
|
|
625
|
+
// Resolver inputs con template
|
|
626
|
+
const inputs = this.resolveTemplate(node.inputs);
|
|
627
|
+
|
|
628
|
+
// Obtener contexto de memoria
|
|
629
|
+
const context = await this.memoryManager.getFullContext({
|
|
630
|
+
description: inputs.task || inputs.description,
|
|
631
|
+
context: node.context || []
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
// Ejecutar agente
|
|
635
|
+
const result = await this.agentRegistry.execute(node.agent, {
|
|
636
|
+
...inputs,
|
|
637
|
+
memoryContext: context,
|
|
638
|
+
config: node.config
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
// Procesar resultado con memoria
|
|
642
|
+
await this.memoryManager.processAgentResult(node.agent, result);
|
|
643
|
+
|
|
644
|
+
return result;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
/**
|
|
648
|
+
* Ejecuta nodo paralelo
|
|
649
|
+
*/
|
|
650
|
+
async executeParallelNode(node) {
|
|
651
|
+
const branches = node.branches;
|
|
652
|
+
const joinType = node.joinType || 'all';
|
|
653
|
+
|
|
654
|
+
// Lanzar todas las ramas en paralelo
|
|
655
|
+
const promises = branches.map(branch =>
|
|
656
|
+
this.executeBranch(branch).catch(error => ({
|
|
657
|
+
branchId: branch.id,
|
|
658
|
+
error: error.message,
|
|
659
|
+
status: 'failed'
|
|
660
|
+
}))
|
|
661
|
+
);
|
|
662
|
+
|
|
663
|
+
// Esperar según joinType
|
|
664
|
+
let results;
|
|
665
|
+
|
|
666
|
+
switch (joinType) {
|
|
667
|
+
case 'all':
|
|
668
|
+
results = await Promise.all(promises);
|
|
669
|
+
break;
|
|
670
|
+
|
|
671
|
+
case 'any':
|
|
672
|
+
results = [await Promise.race(promises)];
|
|
673
|
+
break;
|
|
674
|
+
|
|
675
|
+
case 'quorum':
|
|
676
|
+
results = await this.waitForQuorum(promises);
|
|
677
|
+
break;
|
|
678
|
+
|
|
679
|
+
case 'first_success':
|
|
680
|
+
results = [await this.waitForFirstSuccess(promises)];
|
|
681
|
+
break;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
// Combinar resultados
|
|
685
|
+
return {
|
|
686
|
+
branches: results.reduce((acc, r, i) => {
|
|
687
|
+
acc[branches[i].id] = r;
|
|
688
|
+
return acc;
|
|
689
|
+
}, {}),
|
|
690
|
+
completedCount: results.filter(r => !r.error).length,
|
|
691
|
+
totalCount: branches.length
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/**
|
|
696
|
+
* Ejecuta nodo de interrupt
|
|
697
|
+
*/
|
|
698
|
+
async executeInterruptNode(node) {
|
|
699
|
+
const interrupt = {
|
|
700
|
+
id: `int_${Date.now()}`,
|
|
701
|
+
nodeId: node.id,
|
|
702
|
+
reason: node.reason,
|
|
703
|
+
display: this.resolveTemplate(node.display),
|
|
704
|
+
routes: node.routes,
|
|
705
|
+
createdAt: new Date().toISOString()
|
|
706
|
+
};
|
|
707
|
+
|
|
708
|
+
// Guardar interrupt
|
|
709
|
+
this.state.interrupts.push(interrupt);
|
|
710
|
+
|
|
711
|
+
// Guardar checkpoint especial
|
|
712
|
+
await this.checkpointManager.saveInterrupt(node.reason, {
|
|
713
|
+
nodeId: node.id,
|
|
714
|
+
interruptId: interrupt.id,
|
|
715
|
+
display: interrupt.display
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
// Retornar para que el caller maneje
|
|
719
|
+
return {
|
|
720
|
+
type: 'interrupt',
|
|
721
|
+
interrupt: interrupt
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
/**
|
|
726
|
+
* Ejecuta subflow
|
|
727
|
+
*/
|
|
728
|
+
async executeSubflowNode(node) {
|
|
729
|
+
// Cargar definición del subflow
|
|
730
|
+
const subflowDef = await this.loadSubflowDefinition(node.flow);
|
|
731
|
+
|
|
732
|
+
// Crear engine para subflow
|
|
733
|
+
const subEngine = new FlowEngine({
|
|
734
|
+
checkpointManager: this.checkpointManager,
|
|
735
|
+
memoryManager: this.memoryManager,
|
|
736
|
+
agentRegistry: this.agentRegistry
|
|
737
|
+
});
|
|
738
|
+
|
|
739
|
+
await subEngine.loadFlow(subflowDef);
|
|
740
|
+
|
|
741
|
+
// Ejecutar subflow con inputs
|
|
742
|
+
const inputs = this.resolveTemplate(node.inputs);
|
|
743
|
+
return subEngine.execute(inputs);
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// ==================== Helpers ====================
|
|
747
|
+
|
|
748
|
+
resolveTemplate(template) {
|
|
749
|
+
if (typeof template === 'string') {
|
|
750
|
+
return template.replace(/\{\{([^}]+)\}\}/g, (match, path) => {
|
|
751
|
+
return this.resolvePath(path.trim());
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
if (Array.isArray(template)) {
|
|
756
|
+
return template.map(item => this.resolveTemplate(item));
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
if (typeof template === 'object' && template !== null) {
|
|
760
|
+
const resolved = {};
|
|
761
|
+
for (const [key, value] of Object.entries(template)) {
|
|
762
|
+
resolved[key] = this.resolveTemplate(value);
|
|
763
|
+
}
|
|
764
|
+
return resolved;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
return template;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
resolvePath(path) {
|
|
771
|
+
const parts = path.split('.');
|
|
772
|
+
let current;
|
|
773
|
+
|
|
774
|
+
if (parts[0] === 'inputs') {
|
|
775
|
+
current = this.state.variables;
|
|
776
|
+
parts.shift();
|
|
777
|
+
} else if (parts[0] === 'nodes') {
|
|
778
|
+
current = this.state.nodeOutputs;
|
|
779
|
+
parts.shift();
|
|
780
|
+
} else if (parts[0] === 'state') {
|
|
781
|
+
current = this.state;
|
|
782
|
+
parts.shift();
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
for (const part of parts) {
|
|
786
|
+
if (current === undefined) return undefined;
|
|
787
|
+
current = current[part];
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
return current;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
buildGraph(flowDefinition) {
|
|
794
|
+
const graph = new Map();
|
|
795
|
+
|
|
796
|
+
for (const node of flowDefinition.nodes) {
|
|
797
|
+
graph.set(node.id, {
|
|
798
|
+
...node,
|
|
799
|
+
inEdges: [],
|
|
800
|
+
outEdges: []
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// Procesar edges explícitos
|
|
805
|
+
for (const edge of flowDefinition.edges || []) {
|
|
806
|
+
const fromNode = graph.get(edge.from);
|
|
807
|
+
const toNode = graph.get(edge.to);
|
|
808
|
+
|
|
809
|
+
if (fromNode && toNode) {
|
|
810
|
+
fromNode.outEdges.push(edge);
|
|
811
|
+
toNode.inEdges.push(edge);
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// Procesar edges implícitos (next)
|
|
816
|
+
for (const [id, node] of graph) {
|
|
817
|
+
if (node.next && !node.outEdges.find(e => e.to === node.next)) {
|
|
818
|
+
node.outEdges.push({ from: id, to: node.next });
|
|
819
|
+
const nextNode = graph.get(node.next);
|
|
820
|
+
if (nextNode) {
|
|
821
|
+
nextNode.inEdges.push({ from: id, to: node.next });
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
return {
|
|
827
|
+
nodes: graph,
|
|
828
|
+
getNode: (id) => graph.get(id),
|
|
829
|
+
getEdges: (from) => graph.get(from)?.outEdges || []
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
findEntryNode() {
|
|
834
|
+
for (const [id, node] of this.graph.nodes) {
|
|
835
|
+
if (node.type === 'entry') {
|
|
836
|
+
return node;
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
throw new Error('No entry node found in flow');
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
async determineNextNode(currentNode, result) {
|
|
843
|
+
// Si el resultado es un interrupt, no hay siguiente
|
|
844
|
+
if (result?.type === 'interrupt') {
|
|
845
|
+
return null;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
// Router determina siguiente basado en condición
|
|
849
|
+
if (currentNode.type === 'router') {
|
|
850
|
+
const route = currentNode.routes[result.route];
|
|
851
|
+
return route ? this.graph.getNode(route) : this.graph.getNode(currentNode.default);
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
// Condition determina siguiente basado en true/false
|
|
855
|
+
if (currentNode.type === 'condition') {
|
|
856
|
+
const nextId = result.value ? currentNode.true : currentNode.false;
|
|
857
|
+
return this.graph.getNode(nextId);
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
// Exit no tiene siguiente
|
|
861
|
+
if (currentNode.type === 'exit') {
|
|
862
|
+
return null;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
// Default: usar next o primer outEdge
|
|
866
|
+
if (currentNode.next) {
|
|
867
|
+
return this.graph.getNode(currentNode.next);
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
const outEdges = this.graph.getEdges(currentNode.id);
|
|
871
|
+
if (outEdges.length > 0) {
|
|
872
|
+
return this.graph.getNode(outEdges[0].to);
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
return null;
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
```
|
|
879
|
+
|
|
880
|
+
---
|
|
881
|
+
|
|
882
|
+
## Flows Predefinidos
|
|
883
|
+
|
|
884
|
+
### development_flow.json
|
|
885
|
+
|
|
886
|
+
Flow estándar para desarrollo de features.
|
|
887
|
+
|
|
888
|
+
### quick_flow.json
|
|
889
|
+
|
|
890
|
+
Flow simplificado para tareas rápidas.
|
|
891
|
+
|
|
892
|
+
### review_flow.json
|
|
893
|
+
|
|
894
|
+
Flow para code review multi-agente.
|
|
895
|
+
|
|
896
|
+
### debug_flow.json
|
|
897
|
+
|
|
898
|
+
Flow para debugging sistemático.
|
|
899
|
+
|
|
900
|
+
---
|
|
901
|
+
|
|
902
|
+
## Comandos de Usuario
|
|
903
|
+
|
|
904
|
+
### /elsabro:flow
|
|
905
|
+
|
|
906
|
+
```bash
|
|
907
|
+
/elsabro:flow list # Listar flows disponibles
|
|
908
|
+
/elsabro:flow run <name> [inputs] # Ejecutar flow
|
|
909
|
+
/elsabro:flow status # Ver estado del flow actual
|
|
910
|
+
/elsabro:flow resume [checkpoint] # Resumir flow pausado
|
|
911
|
+
/elsabro:flow cancel # Cancelar flow actual
|
|
912
|
+
/elsabro:flow create # Crear nuevo flow (interactivo)
|
|
913
|
+
```
|
|
914
|
+
|
|
915
|
+
---
|
|
916
|
+
|
|
917
|
+
## Configuración
|
|
918
|
+
|
|
919
|
+
### .planning/flows/config.json
|
|
920
|
+
|
|
921
|
+
```json
|
|
922
|
+
{
|
|
923
|
+
"flows": {
|
|
924
|
+
"defaultFlow": "development_flow",
|
|
925
|
+
"timeout": 3600000,
|
|
926
|
+
"checkpointing": {
|
|
927
|
+
"enabled": true,
|
|
928
|
+
"interval": "node"
|
|
929
|
+
},
|
|
930
|
+
"parallelism": {
|
|
931
|
+
"maxConcurrent": 4,
|
|
932
|
+
"defaultJoinType": "all"
|
|
933
|
+
},
|
|
934
|
+
"interrupts": {
|
|
935
|
+
"timeout": 86400000,
|
|
936
|
+
"defaultAction": "pause"
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
```
|