elsabro 2.3.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/bin/install.js +0 -0
- package/flows/development-flow.json +452 -0
- package/flows/quick-flow.json +118 -0
- package/package.json +3 -2
- 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-v2.md +968 -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/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/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/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/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/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,681 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: interrupt-system
|
|
3
|
+
description: Sistema de interrupts para intervención humana
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# ELSABRO Interrupt System
|
|
8
|
+
|
|
9
|
+
## Vision General
|
|
10
|
+
|
|
11
|
+
El sistema de Interrupts permite pausar la ejecución en puntos críticos para obtener input del usuario, aprobar decisiones, o manejar situaciones excepcionales.
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
┌──────────────────────────────────────────────────────────────────────────┐
|
|
15
|
+
│ INTERRUPT FLOW │
|
|
16
|
+
├──────────────────────────────────────────────────────────────────────────┤
|
|
17
|
+
│ │
|
|
18
|
+
│ EXECUTION │
|
|
19
|
+
│ │ │
|
|
20
|
+
│ ▼ │
|
|
21
|
+
│ ┌──────────┐ │
|
|
22
|
+
│ │ NODE A │ │
|
|
23
|
+
│ └────┬─────┘ │
|
|
24
|
+
│ │ │
|
|
25
|
+
│ ▼ │
|
|
26
|
+
│ ┌──────────────────────────────────────────┐ │
|
|
27
|
+
│ │ INTERRUPT TRIGGERED │ │
|
|
28
|
+
│ │ ┌────────────────────────────────────┐ │ │
|
|
29
|
+
│ │ │ Reason: Approval required │ │ │
|
|
30
|
+
│ │ │ Data: {...plan details...} │ │ │
|
|
31
|
+
│ │ │ Options: [approve, modify, reject] │ │ │
|
|
32
|
+
│ │ └────────────────────────────────────┘ │ │
|
|
33
|
+
│ └─────────────────┬────────────────────────┘ │
|
|
34
|
+
│ │ │
|
|
35
|
+
│ SAVE CHECKPOINT │
|
|
36
|
+
│ │ │
|
|
37
|
+
│ WAIT FOR USER │
|
|
38
|
+
│ │ │
|
|
39
|
+
│ ▼ │
|
|
40
|
+
│ ┌──────────────┐ │
|
|
41
|
+
│ │ USER RESPONDS │ │
|
|
42
|
+
│ └──────┬───────┘ │
|
|
43
|
+
│ │ │
|
|
44
|
+
│ ┌──────────┼──────────┐ │
|
|
45
|
+
│ ▼ ▼ ▼ │
|
|
46
|
+
│ [approve] [modify] [reject] │
|
|
47
|
+
│ │ │ │ │
|
|
48
|
+
│ ▼ ▼ ▼ │
|
|
49
|
+
│ NODE B NODE A END │
|
|
50
|
+
│ (retry) │
|
|
51
|
+
│ │
|
|
52
|
+
└──────────────────────────────────────────────────────────────────────────┘
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Tipos de Interrupts
|
|
58
|
+
|
|
59
|
+
### 1. Approval Gates
|
|
60
|
+
|
|
61
|
+
Requiere aprobación antes de continuar.
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"type": "approval",
|
|
66
|
+
"trigger": "before",
|
|
67
|
+
"nodeId": "deploy",
|
|
68
|
+
"display": {
|
|
69
|
+
"title": "🚀 Deployment Approval",
|
|
70
|
+
"content": "Ready to deploy to production",
|
|
71
|
+
"data": {
|
|
72
|
+
"environment": "production",
|
|
73
|
+
"changes": 15,
|
|
74
|
+
"testsPassed": true
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
"options": [
|
|
78
|
+
{ "id": "approve", "label": "Deploy Now", "next": "deploy" },
|
|
79
|
+
{ "id": "delay", "label": "Schedule Later", "next": "schedule" },
|
|
80
|
+
{ "id": "reject", "label": "Cancel", "next": "end" }
|
|
81
|
+
],
|
|
82
|
+
"timeout": 86400000,
|
|
83
|
+
"default": "reject"
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 2. Decision Points
|
|
88
|
+
|
|
89
|
+
Usuario debe elegir entre opciones.
|
|
90
|
+
|
|
91
|
+
```json
|
|
92
|
+
{
|
|
93
|
+
"type": "decision",
|
|
94
|
+
"nodeId": "choose_approach",
|
|
95
|
+
"display": {
|
|
96
|
+
"title": "💡 Choose Implementation Approach",
|
|
97
|
+
"content": "Multiple valid approaches found",
|
|
98
|
+
"options": [
|
|
99
|
+
{
|
|
100
|
+
"id": "approach_a",
|
|
101
|
+
"label": "Server Components",
|
|
102
|
+
"description": "Better performance, simpler code",
|
|
103
|
+
"recommended": true
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"id": "approach_b",
|
|
107
|
+
"label": "Client Components + API",
|
|
108
|
+
"description": "More flexible, easier testing"
|
|
109
|
+
}
|
|
110
|
+
]
|
|
111
|
+
},
|
|
112
|
+
"routes": {
|
|
113
|
+
"approach_a": "implement_server",
|
|
114
|
+
"approach_b": "implement_client"
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### 3. Error Recovery
|
|
120
|
+
|
|
121
|
+
Intervención cuando auto-recovery falla.
|
|
122
|
+
|
|
123
|
+
```json
|
|
124
|
+
{
|
|
125
|
+
"type": "error_recovery",
|
|
126
|
+
"nodeId": "fix_failed",
|
|
127
|
+
"trigger": "on_max_iterations",
|
|
128
|
+
"display": {
|
|
129
|
+
"title": "⚠️ Auto-Fix Failed",
|
|
130
|
+
"content": "Unable to automatically resolve issues",
|
|
131
|
+
"errors": ["TypeScript error in auth.ts:45", "Test failure in login.test.ts"],
|
|
132
|
+
"attempts": 3
|
|
133
|
+
},
|
|
134
|
+
"options": [
|
|
135
|
+
{ "id": "retry", "label": "Retry Auto-Fix" },
|
|
136
|
+
{ "id": "manual", "label": "I'll Fix Manually" },
|
|
137
|
+
{ "id": "skip", "label": "Skip and Continue" },
|
|
138
|
+
{ "id": "abort", "label": "Abort Execution" }
|
|
139
|
+
]
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### 4. Information Request
|
|
144
|
+
|
|
145
|
+
Necesita input del usuario para continuar.
|
|
146
|
+
|
|
147
|
+
```json
|
|
148
|
+
{
|
|
149
|
+
"type": "input",
|
|
150
|
+
"nodeId": "get_config",
|
|
151
|
+
"display": {
|
|
152
|
+
"title": "🔧 Configuration Required",
|
|
153
|
+
"content": "Need some information to proceed",
|
|
154
|
+
"fields": [
|
|
155
|
+
{
|
|
156
|
+
"id": "api_key",
|
|
157
|
+
"type": "secret",
|
|
158
|
+
"label": "API Key",
|
|
159
|
+
"required": true
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
"id": "environment",
|
|
163
|
+
"type": "select",
|
|
164
|
+
"label": "Environment",
|
|
165
|
+
"options": ["development", "staging", "production"],
|
|
166
|
+
"default": "development"
|
|
167
|
+
}
|
|
168
|
+
]
|
|
169
|
+
},
|
|
170
|
+
"next": "continue_with_config"
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### 5. Confirmation
|
|
175
|
+
|
|
176
|
+
Confirmar acción potencialmente destructiva.
|
|
177
|
+
|
|
178
|
+
```json
|
|
179
|
+
{
|
|
180
|
+
"type": "confirmation",
|
|
181
|
+
"nodeId": "before_delete",
|
|
182
|
+
"display": {
|
|
183
|
+
"title": "⚠️ Confirm Deletion",
|
|
184
|
+
"content": "This action cannot be undone",
|
|
185
|
+
"warning": "You are about to delete 5 files",
|
|
186
|
+
"files": ["src/old/auth.ts", "src/old/user.ts", "..."]
|
|
187
|
+
},
|
|
188
|
+
"options": [
|
|
189
|
+
{ "id": "confirm", "label": "Yes, Delete", "style": "danger" },
|
|
190
|
+
{ "id": "cancel", "label": "Cancel", "style": "default" }
|
|
191
|
+
],
|
|
192
|
+
"default": "cancel"
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### 6. Progress Checkpoint
|
|
197
|
+
|
|
198
|
+
Checkpoint manual durante operación larga.
|
|
199
|
+
|
|
200
|
+
```json
|
|
201
|
+
{
|
|
202
|
+
"type": "checkpoint",
|
|
203
|
+
"nodeId": "midpoint",
|
|
204
|
+
"display": {
|
|
205
|
+
"title": "📍 Progress Checkpoint",
|
|
206
|
+
"content": "50% complete - Want to continue?",
|
|
207
|
+
"progress": {
|
|
208
|
+
"completed": ["Phase 1", "Phase 2"],
|
|
209
|
+
"remaining": ["Phase 3", "Phase 4"]
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
"options": [
|
|
213
|
+
{ "id": "continue", "label": "Continue" },
|
|
214
|
+
{ "id": "pause", "label": "Pause Here" },
|
|
215
|
+
{ "id": "stop", "label": "Stop Execution" }
|
|
216
|
+
]
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## API del Sistema de Interrupts
|
|
223
|
+
|
|
224
|
+
### InterruptManager
|
|
225
|
+
|
|
226
|
+
```javascript
|
|
227
|
+
/**
|
|
228
|
+
* InterruptManager
|
|
229
|
+
* Gestiona interrupts para intervención humana
|
|
230
|
+
*/
|
|
231
|
+
class InterruptManager {
|
|
232
|
+
constructor(options = {}) {
|
|
233
|
+
this.checkpointManager = options.checkpointManager;
|
|
234
|
+
this.notificationService = options.notificationService;
|
|
235
|
+
|
|
236
|
+
this.config = {
|
|
237
|
+
defaultTimeout: options.defaultTimeout || 86400000, // 24 horas
|
|
238
|
+
persistInterrupts: options.persistInterrupts !== false
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
this.activeInterrupts = new Map();
|
|
242
|
+
this.history = [];
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Crea y dispara un interrupt
|
|
247
|
+
*/
|
|
248
|
+
async trigger(interruptDef, context = {}) {
|
|
249
|
+
const interrupt = {
|
|
250
|
+
id: this.generateId(),
|
|
251
|
+
type: interruptDef.type,
|
|
252
|
+
nodeId: interruptDef.nodeId,
|
|
253
|
+
display: this.resolveTemplate(interruptDef.display, context),
|
|
254
|
+
options: interruptDef.options,
|
|
255
|
+
routes: interruptDef.routes,
|
|
256
|
+
timeout: interruptDef.timeout || this.config.defaultTimeout,
|
|
257
|
+
default: interruptDef.default,
|
|
258
|
+
createdAt: new Date().toISOString(),
|
|
259
|
+
context: context,
|
|
260
|
+
status: 'pending'
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
// Guardar interrupt
|
|
264
|
+
this.activeInterrupts.set(interrupt.id, interrupt);
|
|
265
|
+
|
|
266
|
+
// Persistir en checkpoint si está habilitado
|
|
267
|
+
if (this.config.persistInterrupts && this.checkpointManager) {
|
|
268
|
+
await this.checkpointManager.saveInterrupt(interrupt.type, {
|
|
269
|
+
interruptId: interrupt.id,
|
|
270
|
+
display: interrupt.display,
|
|
271
|
+
options: interrupt.options
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Notificar al usuario
|
|
276
|
+
await this.notifyUser(interrupt);
|
|
277
|
+
|
|
278
|
+
// Configurar timeout
|
|
279
|
+
this.setupTimeout(interrupt);
|
|
280
|
+
|
|
281
|
+
return interrupt;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Espera respuesta del usuario
|
|
286
|
+
*/
|
|
287
|
+
async waitForResponse(interruptId) {
|
|
288
|
+
const interrupt = this.activeInterrupts.get(interruptId);
|
|
289
|
+
|
|
290
|
+
if (!interrupt) {
|
|
291
|
+
throw new Error(`Interrupt ${interruptId} not found`);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return new Promise((resolve, reject) => {
|
|
295
|
+
interrupt.resolver = resolve;
|
|
296
|
+
interrupt.rejecter = reject;
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Procesa respuesta del usuario
|
|
302
|
+
*/
|
|
303
|
+
async respond(interruptId, response, additionalData = {}) {
|
|
304
|
+
const interrupt = this.activeInterrupts.get(interruptId);
|
|
305
|
+
|
|
306
|
+
if (!interrupt) {
|
|
307
|
+
throw new Error(`Interrupt ${interruptId} not found`);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (interrupt.status !== 'pending') {
|
|
311
|
+
throw new Error(`Interrupt ${interruptId} already resolved`);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Validar respuesta
|
|
315
|
+
const validOptions = interrupt.options.map(o => o.id);
|
|
316
|
+
if (!validOptions.includes(response)) {
|
|
317
|
+
throw new Error(`Invalid response: ${response}. Valid: ${validOptions.join(', ')}`);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Actualizar interrupt
|
|
321
|
+
interrupt.status = 'resolved';
|
|
322
|
+
interrupt.response = response;
|
|
323
|
+
interrupt.responseData = additionalData;
|
|
324
|
+
interrupt.resolvedAt = new Date().toISOString();
|
|
325
|
+
|
|
326
|
+
// Determinar siguiente nodo
|
|
327
|
+
const nextNode = interrupt.routes
|
|
328
|
+
? interrupt.routes[response]
|
|
329
|
+
: interrupt.options.find(o => o.id === response)?.next;
|
|
330
|
+
|
|
331
|
+
// Mover a historial
|
|
332
|
+
this.history.push(interrupt);
|
|
333
|
+
this.activeInterrupts.delete(interruptId);
|
|
334
|
+
|
|
335
|
+
// Resolver promise si existe
|
|
336
|
+
if (interrupt.resolver) {
|
|
337
|
+
interrupt.resolver({
|
|
338
|
+
response,
|
|
339
|
+
nextNode,
|
|
340
|
+
data: additionalData
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return {
|
|
345
|
+
response,
|
|
346
|
+
nextNode,
|
|
347
|
+
interrupt
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Cancela un interrupt
|
|
353
|
+
*/
|
|
354
|
+
async cancel(interruptId, reason = 'cancelled') {
|
|
355
|
+
const interrupt = this.activeInterrupts.get(interruptId);
|
|
356
|
+
|
|
357
|
+
if (interrupt) {
|
|
358
|
+
interrupt.status = 'cancelled';
|
|
359
|
+
interrupt.cancelReason = reason;
|
|
360
|
+
interrupt.cancelledAt = new Date().toISOString();
|
|
361
|
+
|
|
362
|
+
this.history.push(interrupt);
|
|
363
|
+
this.activeInterrupts.delete(interruptId);
|
|
364
|
+
|
|
365
|
+
if (interrupt.rejecter) {
|
|
366
|
+
interrupt.rejecter(new Error(`Interrupt cancelled: ${reason}`));
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Configura timeout para interrupt
|
|
373
|
+
*/
|
|
374
|
+
setupTimeout(interrupt) {
|
|
375
|
+
if (interrupt.timeout > 0) {
|
|
376
|
+
setTimeout(() => {
|
|
377
|
+
if (this.activeInterrupts.has(interrupt.id)) {
|
|
378
|
+
const defaultResponse = interrupt.default;
|
|
379
|
+
|
|
380
|
+
if (defaultResponse) {
|
|
381
|
+
this.respond(interrupt.id, defaultResponse, { timedOut: true });
|
|
382
|
+
} else {
|
|
383
|
+
this.cancel(interrupt.id, 'timeout');
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}, interrupt.timeout);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Notifica al usuario sobre el interrupt
|
|
392
|
+
*/
|
|
393
|
+
async notifyUser(interrupt) {
|
|
394
|
+
const display = interrupt.display;
|
|
395
|
+
|
|
396
|
+
// Formato de consola
|
|
397
|
+
const notification = `
|
|
398
|
+
╔════════════════════════════════════════════════════════════════╗
|
|
399
|
+
║ ${this.getIcon(interrupt.type)} ${display.title.padEnd(54)}║
|
|
400
|
+
╠════════════════════════════════════════════════════════════════╣
|
|
401
|
+
${this.formatContent(display.content)}
|
|
402
|
+
${display.data ? this.formatData(display.data) : ''}
|
|
403
|
+
${display.warning ? `║ ⚠️ ${display.warning.padEnd(55)}║\n` : ''}
|
|
404
|
+
╠════════════════════════════════════════════════════════════════╣
|
|
405
|
+
${this.formatOptions(interrupt.options)}
|
|
406
|
+
╚════════════════════════════════════════════════════════════════╝
|
|
407
|
+
`;
|
|
408
|
+
|
|
409
|
+
console.log(notification);
|
|
410
|
+
|
|
411
|
+
// También notificar via servicio si está configurado
|
|
412
|
+
if (this.notificationService) {
|
|
413
|
+
await this.notificationService.send({
|
|
414
|
+
type: 'interrupt',
|
|
415
|
+
id: interrupt.id,
|
|
416
|
+
display: display
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Lista interrupts pendientes
|
|
423
|
+
*/
|
|
424
|
+
listPending() {
|
|
425
|
+
return Array.from(this.activeInterrupts.values()).map(i => ({
|
|
426
|
+
id: i.id,
|
|
427
|
+
type: i.type,
|
|
428
|
+
title: i.display.title,
|
|
429
|
+
createdAt: i.createdAt,
|
|
430
|
+
timeout: i.timeout
|
|
431
|
+
}));
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Obtiene historial de interrupts
|
|
436
|
+
*/
|
|
437
|
+
getHistory(options = {}) {
|
|
438
|
+
let history = [...this.history];
|
|
439
|
+
|
|
440
|
+
if (options.type) {
|
|
441
|
+
history = history.filter(i => i.type === options.type);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (options.limit) {
|
|
445
|
+
history = history.slice(-options.limit);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
return history;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// ==================== Helpers ====================
|
|
452
|
+
|
|
453
|
+
generateId() {
|
|
454
|
+
return `int_${Date.now()}_${Math.random().toString(36).substring(2, 8)}`;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
resolveTemplate(template, context) {
|
|
458
|
+
if (typeof template === 'string') {
|
|
459
|
+
return template.replace(/\{\{([^}]+)\}\}/g, (match, path) => {
|
|
460
|
+
return this.getNestedValue(context, path.trim()) || match;
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
if (typeof template === 'object' && template !== null) {
|
|
465
|
+
const resolved = Array.isArray(template) ? [] : {};
|
|
466
|
+
for (const [key, value] of Object.entries(template)) {
|
|
467
|
+
resolved[key] = this.resolveTemplate(value, context);
|
|
468
|
+
}
|
|
469
|
+
return resolved;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
return template;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
getNestedValue(obj, path) {
|
|
476
|
+
return path.split('.').reduce((current, key) => current?.[key], obj);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
getIcon(type) {
|
|
480
|
+
const icons = {
|
|
481
|
+
approval: '✅',
|
|
482
|
+
decision: '💡',
|
|
483
|
+
error_recovery: '⚠️',
|
|
484
|
+
input: '🔧',
|
|
485
|
+
confirmation: '❓',
|
|
486
|
+
checkpoint: '📍'
|
|
487
|
+
};
|
|
488
|
+
return icons[type] || '📢';
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
formatContent(content) {
|
|
492
|
+
const lines = content.split('\n');
|
|
493
|
+
return lines.map(line => `║ ${line.padEnd(60)}║`).join('\n');
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
formatData(data) {
|
|
497
|
+
let output = '';
|
|
498
|
+
for (const [key, value] of Object.entries(data)) {
|
|
499
|
+
output += `║ • ${key}: ${JSON.stringify(value).substring(0, 50).padEnd(52)}║\n`;
|
|
500
|
+
}
|
|
501
|
+
return output;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
formatOptions(options) {
|
|
505
|
+
return options.map(opt => {
|
|
506
|
+
const style = opt.style === 'danger' ? '🔴' : opt.recommended ? '⭐' : ' ';
|
|
507
|
+
return `║ ${style} [${opt.id}] ${opt.label.padEnd(50)}║`;
|
|
508
|
+
}).join('\n');
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
---
|
|
514
|
+
|
|
515
|
+
## Integración con Flow Engine
|
|
516
|
+
|
|
517
|
+
### Declaración en Flows
|
|
518
|
+
|
|
519
|
+
```json
|
|
520
|
+
{
|
|
521
|
+
"id": "deploy_node",
|
|
522
|
+
"type": "agent",
|
|
523
|
+
"agent": "deploy-agent",
|
|
524
|
+
"interrupt": {
|
|
525
|
+
"before": {
|
|
526
|
+
"type": "approval",
|
|
527
|
+
"condition": "{{inputs.environment}} === 'production'",
|
|
528
|
+
"display": {
|
|
529
|
+
"title": "Production Deployment Approval",
|
|
530
|
+
"content": "Review changes before deploying to production"
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
### Interrupts Automáticos
|
|
538
|
+
|
|
539
|
+
Configurar interrupts que se disparan automáticamente:
|
|
540
|
+
|
|
541
|
+
```json
|
|
542
|
+
{
|
|
543
|
+
"autoInterrupts": {
|
|
544
|
+
"onCriticalError": {
|
|
545
|
+
"type": "error_recovery",
|
|
546
|
+
"display": {
|
|
547
|
+
"title": "Critical Error",
|
|
548
|
+
"content": "{{error.message}}"
|
|
549
|
+
}
|
|
550
|
+
},
|
|
551
|
+
"onHighRisk": {
|
|
552
|
+
"type": "confirmation",
|
|
553
|
+
"condition": "{{riskScore}} > 0.8",
|
|
554
|
+
"display": {
|
|
555
|
+
"title": "High Risk Operation",
|
|
556
|
+
"warning": "This operation has been flagged as high risk"
|
|
557
|
+
}
|
|
558
|
+
},
|
|
559
|
+
"beforeDestructive": {
|
|
560
|
+
"type": "confirmation",
|
|
561
|
+
"trigger": "destructive_action",
|
|
562
|
+
"display": {
|
|
563
|
+
"title": "Confirm Destructive Action"
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
---
|
|
571
|
+
|
|
572
|
+
## Comandos de Usuario
|
|
573
|
+
|
|
574
|
+
### /elsabro:interrupt
|
|
575
|
+
|
|
576
|
+
```bash
|
|
577
|
+
/elsabro:interrupt list # Ver interrupts pendientes
|
|
578
|
+
/elsabro:interrupt respond <id> <opt> # Responder a interrupt
|
|
579
|
+
/elsabro:interrupt cancel <id> # Cancelar interrupt
|
|
580
|
+
/elsabro:interrupt history # Ver historial de interrupts
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
---
|
|
584
|
+
|
|
585
|
+
## Configuración
|
|
586
|
+
|
|
587
|
+
### .planning/interrupt-config.json
|
|
588
|
+
|
|
589
|
+
```json
|
|
590
|
+
{
|
|
591
|
+
"interrupts": {
|
|
592
|
+
"enabled": true,
|
|
593
|
+
"defaultTimeout": 86400000,
|
|
594
|
+
"persistToCheckpoint": true,
|
|
595
|
+
"notifications": {
|
|
596
|
+
"console": true,
|
|
597
|
+
"sound": false
|
|
598
|
+
},
|
|
599
|
+
"autoInterrupts": {
|
|
600
|
+
"onCriticalError": true,
|
|
601
|
+
"beforeDestructive": true,
|
|
602
|
+
"onHighRisk": true,
|
|
603
|
+
"progressCheckpoints": false
|
|
604
|
+
},
|
|
605
|
+
"defaults": {
|
|
606
|
+
"approval": "reject",
|
|
607
|
+
"confirmation": "cancel",
|
|
608
|
+
"error_recovery": "manual"
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
---
|
|
615
|
+
|
|
616
|
+
## Ejemplos de Uso
|
|
617
|
+
|
|
618
|
+
### Interrupt en Fase de Deploy
|
|
619
|
+
|
|
620
|
+
```javascript
|
|
621
|
+
// En el flow de deployment
|
|
622
|
+
const deployInterrupt = await interruptManager.trigger({
|
|
623
|
+
type: 'approval',
|
|
624
|
+
nodeId: 'deploy',
|
|
625
|
+
display: {
|
|
626
|
+
title: '🚀 Production Deployment',
|
|
627
|
+
content: 'Ready to deploy v2.3.0 to production',
|
|
628
|
+
data: {
|
|
629
|
+
version: '2.3.0',
|
|
630
|
+
changes: 15,
|
|
631
|
+
tests: 'All passed',
|
|
632
|
+
coverage: '87%'
|
|
633
|
+
}
|
|
634
|
+
},
|
|
635
|
+
options: [
|
|
636
|
+
{ id: 'deploy', label: 'Deploy Now', next: 'execute_deploy' },
|
|
637
|
+
{ id: 'staging', label: 'Deploy to Staging First', next: 'staging_deploy' },
|
|
638
|
+
{ id: 'cancel', label: 'Cancel', next: 'end' }
|
|
639
|
+
],
|
|
640
|
+
timeout: 3600000 // 1 hora
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
const response = await interruptManager.waitForResponse(deployInterrupt.id);
|
|
644
|
+
console.log(`User chose: ${response.response}`);
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
### Interrupt en Auto-Fix Loop
|
|
648
|
+
|
|
649
|
+
```javascript
|
|
650
|
+
// Después de N intentos de auto-fix
|
|
651
|
+
if (attempts >= maxAttempts) {
|
|
652
|
+
const fixInterrupt = await interruptManager.trigger({
|
|
653
|
+
type: 'error_recovery',
|
|
654
|
+
nodeId: 'fix_loop',
|
|
655
|
+
display: {
|
|
656
|
+
title: '⚠️ Auto-Fix Exhausted',
|
|
657
|
+
content: `Failed to fix after ${attempts} attempts`,
|
|
658
|
+
errors: collectedErrors
|
|
659
|
+
},
|
|
660
|
+
options: [
|
|
661
|
+
{ id: 'retry', label: 'Retry with Different Approach' },
|
|
662
|
+
{ id: 'manual', label: 'I\'ll Fix Manually' },
|
|
663
|
+
{ id: 'skip', label: 'Skip and Continue' },
|
|
664
|
+
{ id: 'abort', label: 'Abort' }
|
|
665
|
+
]
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
const response = await interruptManager.waitForResponse(fixInterrupt.id);
|
|
669
|
+
|
|
670
|
+
switch (response.response) {
|
|
671
|
+
case 'retry':
|
|
672
|
+
return retryWithDifferentApproach();
|
|
673
|
+
case 'manual':
|
|
674
|
+
return waitForManualFix();
|
|
675
|
+
case 'skip':
|
|
676
|
+
return continueWithWarning();
|
|
677
|
+
case 'abort':
|
|
678
|
+
throw new Error('Aborted by user');
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
```
|