@saulwade/swl-ses 1.3.7 → 1.4.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/CLAUDE.md +12 -4
- package/README.md +1 -1
- package/bin/swl-mcp-server.js +187 -187
- package/bin/swl-webhook-server.js +198 -0
- package/comandos/swl/.evolved.json +22 -22
- package/comandos/swl/adoptar-proyecto.md +21 -1
- package/comandos/swl/claudemd.md +14 -1
- package/comandos/swl/contribuir.md +233 -233
- package/comandos/swl/exportar-vault.md +207 -7
- package/comandos/swl/nuevo-proyecto.md +24 -2
- package/gateway/adapters/base.js +109 -0
- package/gateway/adapters/discord.js +167 -0
- package/gateway/adapters/email.js +221 -0
- package/gateway/adapters/slack.js +192 -0
- package/gateway/adapters/telegram.js +183 -0
- package/gateway/adapters/webhook.js +113 -0
- package/gateway/adapters/whatsapp.js +214 -0
- package/gateway/agent-executor.js +322 -0
- package/gateway/command-relay.js +271 -0
- package/gateway/cron/jobs.js +263 -0
- package/gateway/cron/scheduler.js +322 -0
- package/gateway/cron/store.js +335 -0
- package/gateway/index.js +320 -0
- package/gateway/lib/event-channel.js +191 -0
- package/gateway/session.js +131 -0
- package/gateway/webhook-server.js +324 -0
- package/habilidades/backend-production-resilience/SKILL.md +288 -288
- package/habilidades/benchmark-memoria/SKILL.md +186 -186
- package/habilidades/build-errors-nextjs/SKILL.md +55 -1
- package/habilidades/diagrama-arquitectura/assets/template.html +276 -276
- package/habilidades/doubt-driven-review/SKILL.md +171 -171
- package/habilidades/doubt-driven-review/recursos/EXAMPLES.md +130 -130
- package/habilidades/eval-framework/SKILL.md +212 -212
- package/habilidades/extractor-de-aprendizajes/SKILL.md +24 -10
- package/habilidades/harness-claude-code/SKILL.md +299 -299
- package/habilidades/infra-github-actions/SKILL.md +166 -166
- package/habilidades/legacy-code-rescue/SKILL.md +267 -267
- package/habilidades/manejo-errores/.evolved.json +8 -8
- package/habilidades/meta-skills-estandar/recursos/convencion-examples.md +93 -93
- package/habilidades/meta-skills-estandar/recursos/skills-as-agents.md +163 -163
- package/habilidades/nextjs-testing/SKILL.md +89 -5
- package/habilidades/node-experto/SKILL.md +37 -1
- package/habilidades/patrones-python/SKILL.md +229 -229
- package/habilidades/patrones-python/recursos/patrones-avanzados.md +469 -469
- package/habilidades/planear-fase/SKILL.md +319 -319
- package/habilidades/react-experto/SKILL.md +45 -4
- package/habilidades/release-semver/.evolved.json +8 -8
- package/habilidades/swl-claudemd/SKILL.md +15 -1
- package/habilidades/tdd-workflow/SKILL.md +36 -4
- package/habilidades/testing-python/SKILL.md +340 -340
- package/hooks/claudemd-bloat-detector.js +161 -161
- package/hooks/inyeccion-contexto.js +8 -3
- package/hooks/lib/agent-routing.js +107 -107
- package/hooks/lib/auto-consolidator.js +335 -335
- package/hooks/lib/error-classifier.js +308 -308
- package/hooks/lib/merkle-audit.js +96 -96
- package/hooks/lib/provenance-tracker.js +191 -191
- package/hooks/lib/rate-limit-ip.js +177 -0
- package/hooks/lib/rate-limit-tracker.js +253 -253
- package/hooks/lib/resource-quota.js +122 -122
- package/hooks/lib/retry-jitter.js +165 -165
- package/hooks/lib/skill-auditor.js +588 -588
- package/hooks/lib/sync-status.js +228 -228
- package/hooks/lib/taint-tracker.js +107 -107
- package/hooks/lib/text-similarity.js +241 -241
- package/hooks/lib/toon-compressor.js +245 -245
- package/hooks/lib/webhook-dedup.js +184 -0
- package/hooks/lib/webhook-verify.js +123 -0
- package/hooks/proteccion-rutas.js +120 -15
- package/hooks/registro-turnos.js +209 -209
- package/hooks/sugerir-regenerar-inventario.js +170 -170
- package/hooks/validar-formato-post-subagente.js +140 -140
- package/hooks/validar-memoria-hook.js +218 -218
- package/instintos/prompt-appendices.yaml +57 -57
- package/manifiestos/agent-output-schemas.json +57 -57
- package/manifiestos/modulos.json +1 -0
- package/manifiestos/skills-lock.json +37 -37
- package/package.json +5 -3
- package/plantillas/auditor-veto-template.md +105 -105
- package/plantillas/github-workflows/README.md +47 -47
- package/plantillas/github-workflows/release-please.yml +44 -44
- package/plantillas/github-workflows/swl-ci.yml +107 -107
- package/plantillas/github-workflows/swl-security.yml +51 -51
- package/plugin.json +1 -1
- package/reglas/analisis-previo-tareas-grandes.md +172 -172
- package/reglas/arreglar-al-detectar.md +147 -147
- package/reglas/fragmentos-compartidos.md +152 -152
- package/reglas/harness-claude-code.md +213 -213
- package/reglas/usar-context7.md +226 -226
- package/reglas/usar-sistema-swl.md +251 -0
- package/schemas/diary-entry.schema.json +80 -80
- package/scripts/benchmark-memoria.js +167 -167
- package/scripts/comandos/skills.js +251 -2
- package/scripts/configurar-branch-protection.js +418 -418
- package/scripts/detectar-aprendizajes-duplicados.js +151 -151
- package/scripts/field-report.js +199 -199
- package/scripts/generar-checklists-consolidados.js +273 -273
- package/scripts/generar-inventario.js +420 -420
- package/scripts/generar-matriz-lenguajes.js +271 -271
- package/scripts/lib/artefactos-python.js +43 -43
- package/scripts/lib/benchmark-metrics.js +160 -160
- package/scripts/lib/budget-enforcer.js +252 -252
- package/scripts/lib/configurar-ci.js +380 -380
- package/scripts/lib/contadores-inventario.js +217 -217
- package/scripts/lib/detectar-stack-detallado.js +307 -307
- package/scripts/lib/diary-entry.js +234 -234
- package/scripts/lib/eval-metrics-store.js +218 -218
- package/scripts/lib/eval-quality.js +171 -171
- package/scripts/lib/eval-schemas.js +144 -144
- package/scripts/lib/eval-self-correct.js +106 -106
- package/scripts/lib/eval-validator.js +185 -185
- package/scripts/lib/jaccard-similarity.js +98 -98
- package/scripts/lib/longmemeval-runner.js +125 -125
- package/scripts/lib/npm-version.js +261 -261
- package/scripts/lib/paquetes-conocidos.js +50 -50
- package/scripts/lib/prompt-builder.js +264 -264
- package/scripts/lib/rrf-fusion.js +175 -175
- package/scripts/lib/scoring-instintos.js +277 -277
- package/scripts/lib/semantic-search.js +252 -252
- package/scripts/limpiar-artefactos-python.js +131 -131
- package/scripts/mcp-server/README.md +128 -128
- package/scripts/mcp-server/handlers.js +206 -206
- package/scripts/migrar-csv-a-array.js +168 -168
- package/scripts/migrar-fase-dominio.js +201 -201
- package/scripts/publicar.js +511 -511
- package/scripts/run-eval.js +141 -141
- package/scripts/validar-manifest.js +195 -195
- package/scripts/validar-userland-vacio.js +110 -110
- package/scripts/verificar-release.js +110 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* WhatsApp Adapter — Adaptador para WhatsApp Business API / bridges.
|
|
5
|
+
*
|
|
6
|
+
* Soporta dos modos:
|
|
7
|
+
* 1. WhatsApp Business API (Cloud API de Meta) — producción
|
|
8
|
+
* 2. whatsapp-web.js (bridge no oficial) — desarrollo/personal
|
|
9
|
+
*
|
|
10
|
+
* Ambos modos son bidireccionales: envío y recepción de mensajes.
|
|
11
|
+
*
|
|
12
|
+
* Dependencia modo Business API: ninguna (HTTP nativo)
|
|
13
|
+
* Dependencia modo bridge: whatsapp-web.js (npm install whatsapp-web.js)
|
|
14
|
+
*
|
|
15
|
+
* @module gateway/adapters/whatsapp
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const BaseAdapter = require('./base');
|
|
19
|
+
const https = require('https');
|
|
20
|
+
|
|
21
|
+
/** Límite de caracteres por mensaje de WhatsApp. */
|
|
22
|
+
const MAX_MSG_LENGTH = 4096;
|
|
23
|
+
|
|
24
|
+
/** Comandos reconocidos. */
|
|
25
|
+
const COMMANDS = {
|
|
26
|
+
'/status': 'Estado del sistema SWL',
|
|
27
|
+
'/salud': 'Ejecutar diagnóstico de salud',
|
|
28
|
+
'/sesiones': 'Listar últimas sesiones',
|
|
29
|
+
'/approve': 'Aprobar operación pendiente',
|
|
30
|
+
'/reject': 'Rechazar operación pendiente',
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
class WhatsAppAdapter extends BaseAdapter {
|
|
34
|
+
constructor(config) {
|
|
35
|
+
super('whatsapp', config);
|
|
36
|
+
this._client = null;
|
|
37
|
+
this._mode = config.mode || 'business'; // 'business' | 'bridge'
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async start() {
|
|
41
|
+
if (this._mode === 'bridge') {
|
|
42
|
+
return this._startBridge();
|
|
43
|
+
}
|
|
44
|
+
return this._startBusinessAPI();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Modo Business API (Cloud API de Meta).
|
|
49
|
+
* Requiere: WHATSAPP_TOKEN, WHATSAPP_PHONE_ID
|
|
50
|
+
*/
|
|
51
|
+
async _startBusinessAPI() {
|
|
52
|
+
const token = this.config.token || process.env.WHATSAPP_TOKEN;
|
|
53
|
+
const phoneId = this.config.phoneId || process.env.WHATSAPP_PHONE_ID;
|
|
54
|
+
|
|
55
|
+
if (!token || !phoneId) {
|
|
56
|
+
console.log('[whatsapp] Token o Phone ID no configurado. Establecer WHATSAPP_TOKEN y WHATSAPP_PHONE_ID.');
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
this._token = token;
|
|
61
|
+
this._phoneId = phoneId;
|
|
62
|
+
this.running = true;
|
|
63
|
+
console.log('[whatsapp] Adaptador iniciado (Business API mode).');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Modo bridge con whatsapp-web.js.
|
|
68
|
+
* Requiere: npm install whatsapp-web.js
|
|
69
|
+
*/
|
|
70
|
+
async _startBridge() {
|
|
71
|
+
let wwjs;
|
|
72
|
+
try {
|
|
73
|
+
wwjs = require('whatsapp-web.js');
|
|
74
|
+
} catch (_) {
|
|
75
|
+
console.log('[whatsapp] whatsapp-web.js no instalado. Ejecutar: npm install whatsapp-web.js');
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const { Client, LocalAuth } = wwjs;
|
|
80
|
+
|
|
81
|
+
this._client = new Client({
|
|
82
|
+
authStrategy: new LocalAuth({ dataPath: '.planning/whatsapp-session' }),
|
|
83
|
+
puppeteer: { headless: true },
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
this._client.on('qr', (qr) => {
|
|
87
|
+
console.log('[whatsapp] Escanea el código QR para conectar:');
|
|
88
|
+
console.log('[whatsapp] QR:', qr.substring(0, 50) + '...');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
this._client.on('ready', () => {
|
|
92
|
+
this.running = true;
|
|
93
|
+
console.log('[whatsapp] Adaptador iniciado (bridge mode).');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
this._client.on('message', (msg) => {
|
|
97
|
+
if (msg.fromMe) return;
|
|
98
|
+
|
|
99
|
+
const text = msg.body || '';
|
|
100
|
+
const parts = text.split(/\s+/);
|
|
101
|
+
const command = COMMANDS[parts[0]] ? parts[0] : null;
|
|
102
|
+
const args = command ? parts.slice(1).join(' ') : '';
|
|
103
|
+
|
|
104
|
+
this._emitMessage({
|
|
105
|
+
chatId: msg.from,
|
|
106
|
+
userId: msg.from,
|
|
107
|
+
userName: msg._data?.notifyName || msg.from,
|
|
108
|
+
text: text,
|
|
109
|
+
command: command,
|
|
110
|
+
args: args,
|
|
111
|
+
raw: msg,
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
this._client.on('auth_failure', () => {
|
|
116
|
+
console.error('[whatsapp] Autenticación fallida. Eliminar .planning/whatsapp-session/ y reiniciar.');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
await this._client.initialize();
|
|
121
|
+
} catch (err) {
|
|
122
|
+
console.error(`[whatsapp] Error inicializando: ${err.message}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async stop() {
|
|
127
|
+
if (this._client) {
|
|
128
|
+
try { await this._client.destroy(); } catch (_) {}
|
|
129
|
+
this._client = null;
|
|
130
|
+
}
|
|
131
|
+
this.running = false;
|
|
132
|
+
console.log('[whatsapp] Adaptador detenido.');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async send(message) {
|
|
136
|
+
const chatId = message.chatId || this.config.defaultChat;
|
|
137
|
+
if (!chatId) return false;
|
|
138
|
+
|
|
139
|
+
const text = this.formatMessage(message);
|
|
140
|
+
|
|
141
|
+
if (this._mode === 'business') {
|
|
142
|
+
return this._sendBusinessAPI(chatId, text);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Bridge mode
|
|
146
|
+
if (!this._client) return false;
|
|
147
|
+
try {
|
|
148
|
+
await this._client.sendMessage(chatId, text);
|
|
149
|
+
return true;
|
|
150
|
+
} catch (err) {
|
|
151
|
+
console.error(`[whatsapp] Error enviando: ${err.message}`);
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Envía mensaje via WhatsApp Business Cloud API.
|
|
158
|
+
*/
|
|
159
|
+
async _sendBusinessAPI(to, text) {
|
|
160
|
+
if (!this._token || !this._phoneId) return false;
|
|
161
|
+
|
|
162
|
+
const body = JSON.stringify({
|
|
163
|
+
messaging_product: 'whatsapp',
|
|
164
|
+
to: to.replace(/[^0-9]/g, ''),
|
|
165
|
+
type: 'text',
|
|
166
|
+
text: { body: text.substring(0, MAX_MSG_LENGTH) },
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
return new Promise((resolve) => {
|
|
170
|
+
const req = https.request({
|
|
171
|
+
hostname: 'graph.facebook.com',
|
|
172
|
+
path: `/v18.0/${this._phoneId}/messages`,
|
|
173
|
+
method: 'POST',
|
|
174
|
+
headers: {
|
|
175
|
+
'Authorization': `Bearer ${this._token}`,
|
|
176
|
+
'Content-Type': 'application/json',
|
|
177
|
+
'Content-Length': Buffer.byteLength(body),
|
|
178
|
+
},
|
|
179
|
+
timeout: 10000,
|
|
180
|
+
}, (res) => {
|
|
181
|
+
let data = '';
|
|
182
|
+
res.on('data', chunk => data += chunk);
|
|
183
|
+
res.on('end', () => {
|
|
184
|
+
resolve(res.statusCode >= 200 && res.statusCode < 300);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
req.on('error', () => resolve(false));
|
|
189
|
+
req.on('timeout', () => { req.destroy(); resolve(false); });
|
|
190
|
+
req.write(body);
|
|
191
|
+
req.end();
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
formatMessage(swlMessage) {
|
|
196
|
+
const payload = swlMessage.payload || swlMessage;
|
|
197
|
+
|
|
198
|
+
if (payload.jobName) {
|
|
199
|
+
const icon = payload.status === 'completed' ? '✅' : '❌';
|
|
200
|
+
const lines = [
|
|
201
|
+
`${icon} *${payload.jobName}*`,
|
|
202
|
+
`Estado: ${payload.status}`,
|
|
203
|
+
];
|
|
204
|
+
if (payload.output) {
|
|
205
|
+
lines.push('```' + payload.output.substring(0, 800) + '```');
|
|
206
|
+
}
|
|
207
|
+
return lines.join('\n');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return swlMessage.text || JSON.stringify(payload).substring(0, 500);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
module.exports = WhatsAppAdapter;
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* agent-executor.js
|
|
5
|
+
*
|
|
6
|
+
* Contrato unificado de ejecución de agentes SWL.
|
|
7
|
+
*
|
|
8
|
+
* Implementa el patrón "execute(name, input) → string" de Managed Agents:
|
|
9
|
+
* cualquier agente SWL expone la misma interfaz al orquestador, independientemente
|
|
10
|
+
* de su tipo (implementador, planificador, revisor, etc.).
|
|
11
|
+
*
|
|
12
|
+
* Esto permite que el gateway/cron y el orquestador-swl invoquen agentes
|
|
13
|
+
* de forma programática con trazabilidad completa via run-log.js y HandoffContext.
|
|
14
|
+
*
|
|
15
|
+
* Integra con:
|
|
16
|
+
* - manifiestos/handoff-context.json — schema de trazabilidad entre agentes
|
|
17
|
+
* - hooks/lib/run-log.js — observabilidad de invocaciones
|
|
18
|
+
* - hooks/lib/abort-registry.js — cancelación cooperativa
|
|
19
|
+
*
|
|
20
|
+
* Uso:
|
|
21
|
+
* const executor = require('./agent-executor');
|
|
22
|
+
*
|
|
23
|
+
* // Invocación simple
|
|
24
|
+
* const result = await executor.executeAgent('planificador-swl', 'Planifica la feature X');
|
|
25
|
+
*
|
|
26
|
+
* // Con HandoffContext (trazabilidad multi-agente)
|
|
27
|
+
* const result = await executor.executeAgent('implementador-swl', prompt, {
|
|
28
|
+
* handoff: {
|
|
29
|
+
* reason: 'task_delegation',
|
|
30
|
+
* parentAgent: 'orquestador-swl',
|
|
31
|
+
* transferCount: 1,
|
|
32
|
+
* sessionId: 'abc123',
|
|
33
|
+
* },
|
|
34
|
+
* timeoutMs: 300_000,
|
|
35
|
+
* });
|
|
36
|
+
*
|
|
37
|
+
* console.log(result.output); // string con respuesta del agente
|
|
38
|
+
* console.log(result.status); // 'completed' | 'failed' | 'aborted' | 'timeout'
|
|
39
|
+
* console.log(result.handoff); // HandoffContext actualizado (transferCount++)
|
|
40
|
+
*
|
|
41
|
+
* @module gateway/agent-executor
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
const { execFile } = require('child_process');
|
|
45
|
+
const path = require('path');
|
|
46
|
+
|
|
47
|
+
// run-log puede no estar disponible fuera del repo — importación defensiva
|
|
48
|
+
let runLog;
|
|
49
|
+
try { runLog = require('../hooks/lib/run-log'); } catch (_) { runLog = null; }
|
|
50
|
+
|
|
51
|
+
// abort-registry — cancelación cooperativa
|
|
52
|
+
let abortRegistry;
|
|
53
|
+
try { abortRegistry = require('../hooks/lib/abort-registry'); } catch (_) { abortRegistry = null; }
|
|
54
|
+
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// Constantes
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
/** Máximo de transferencias en cadena antes de rechazar (anti-loop). */
|
|
60
|
+
const MAX_TRANSFER_COUNT = 10;
|
|
61
|
+
|
|
62
|
+
/** Timeout por defecto en ms (5 minutos). */
|
|
63
|
+
const DEFAULT_TIMEOUT_MS = 300_000;
|
|
64
|
+
|
|
65
|
+
/** Modelo Claude por defecto para agentes programáticos. */
|
|
66
|
+
const DEFAULT_MODEL = 'claude-sonnet-4-6';
|
|
67
|
+
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// Tipos (JSDoc)
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @typedef {{
|
|
74
|
+
* reason: string,
|
|
75
|
+
* parentAgent: string,
|
|
76
|
+
* transferCount: number,
|
|
77
|
+
* sessionId?: string,
|
|
78
|
+
* metadata?: object,
|
|
79
|
+
* }} HandoffContext
|
|
80
|
+
*
|
|
81
|
+
* @typedef {{
|
|
82
|
+
* output: string,
|
|
83
|
+
* sessionId: string|null,
|
|
84
|
+
* status: 'completed'|'failed'|'aborted'|'timeout',
|
|
85
|
+
* handoff: HandoffContext|null,
|
|
86
|
+
* durationMs: number,
|
|
87
|
+
* error?: string,
|
|
88
|
+
* }} ExecuteResult
|
|
89
|
+
*
|
|
90
|
+
* @typedef {{
|
|
91
|
+
* handoff?: HandoffContext,
|
|
92
|
+
* sessionId?: string,
|
|
93
|
+
* timeoutMs?: number,
|
|
94
|
+
* model?: string,
|
|
95
|
+
* cwd?: string,
|
|
96
|
+
* }} ExecuteOptions
|
|
97
|
+
*/
|
|
98
|
+
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
// API principal
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Ejecuta un agente SWL con una entrada dada y retorna su output.
|
|
105
|
+
* Implementa el contrato unificado execute(name, input) → string de Managed Agents.
|
|
106
|
+
*
|
|
107
|
+
* @param {string} agentName - Nombre del agente SWL (ej: 'planificador-swl').
|
|
108
|
+
* @param {string} input - Prompt de entrada para el agente.
|
|
109
|
+
* @param {ExecuteOptions} [opts] - Opciones de ejecución.
|
|
110
|
+
* @returns {Promise<ExecuteResult>}
|
|
111
|
+
*/
|
|
112
|
+
async function executeAgent(agentName, input, opts) {
|
|
113
|
+
const {
|
|
114
|
+
handoff = null,
|
|
115
|
+
sessionId = null,
|
|
116
|
+
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
117
|
+
model = DEFAULT_MODEL,
|
|
118
|
+
cwd = process.cwd(),
|
|
119
|
+
} = opts || {};
|
|
120
|
+
|
|
121
|
+
const startMs = Date.now();
|
|
122
|
+
|
|
123
|
+
// --- Validación de anti-loop de HandoffContext ---
|
|
124
|
+
if (handoff && handoff.transferCount >= MAX_TRANSFER_COUNT) {
|
|
125
|
+
return {
|
|
126
|
+
output: '',
|
|
127
|
+
sessionId,
|
|
128
|
+
status: 'failed',
|
|
129
|
+
handoff: null,
|
|
130
|
+
durationMs: 0,
|
|
131
|
+
error: `transferCount (${handoff.transferCount}) supera el máximo permitido (${MAX_TRANSFER_COUNT}). Posible loop de agentes.`,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// --- Verificar si hay abort activo ---
|
|
136
|
+
if (abortRegistry) {
|
|
137
|
+
try {
|
|
138
|
+
const estado = abortRegistry.getStatus();
|
|
139
|
+
if (estado === 'force' || estado === 'graceful') {
|
|
140
|
+
return {
|
|
141
|
+
output: '',
|
|
142
|
+
sessionId,
|
|
143
|
+
status: 'aborted',
|
|
144
|
+
handoff: null,
|
|
145
|
+
durationMs: Date.now() - startMs,
|
|
146
|
+
error: `Ejecución cancelada: abort ${estado} activo.`,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
} catch (_) { /* abort-registry no crítico */ }
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// --- Registrar invocación en run-log ---
|
|
153
|
+
if (runLog && sessionId) {
|
|
154
|
+
try {
|
|
155
|
+
runLog.agentInvoked(sessionId, agentName);
|
|
156
|
+
} catch (_) { /* observabilidad no crítica */ }
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// --- Construir HandoffContext actualizado ---
|
|
160
|
+
const handoffActualizado = handoff ? {
|
|
161
|
+
...handoff,
|
|
162
|
+
transferCount: handoff.transferCount + 1,
|
|
163
|
+
sessionId: sessionId || handoff.sessionId,
|
|
164
|
+
} : null;
|
|
165
|
+
|
|
166
|
+
// --- Construir prompt con contexto de agente ---
|
|
167
|
+
const promptCompleto = _buildPrompt(agentName, input, handoffActualizado);
|
|
168
|
+
|
|
169
|
+
// --- Ejecutar via claude CLI ---
|
|
170
|
+
return new Promise((resolve) => {
|
|
171
|
+
const args = [
|
|
172
|
+
'--print',
|
|
173
|
+
'--model', model,
|
|
174
|
+
'--no-update-check',
|
|
175
|
+
promptCompleto,
|
|
176
|
+
];
|
|
177
|
+
|
|
178
|
+
let stdout = '';
|
|
179
|
+
let stderr = '';
|
|
180
|
+
let timedOut = false;
|
|
181
|
+
|
|
182
|
+
const proc = execFile('claude', args, {
|
|
183
|
+
cwd,
|
|
184
|
+
timeout: timeoutMs,
|
|
185
|
+
maxBuffer: 10 * 1024 * 1024, // 10 MB
|
|
186
|
+
env: { ...process.env },
|
|
187
|
+
}, (err, out, err2) => {
|
|
188
|
+
stdout = out || '';
|
|
189
|
+
stderr = err2 || '';
|
|
190
|
+
|
|
191
|
+
const durationMs = Date.now() - startMs;
|
|
192
|
+
|
|
193
|
+
if (timedOut) {
|
|
194
|
+
resolve({ output: stdout, sessionId, status: 'timeout', handoff: handoffActualizado, durationMs, error: 'timeout' });
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (err && !stdout) {
|
|
199
|
+
resolve({
|
|
200
|
+
output: stderr || err.message,
|
|
201
|
+
sessionId,
|
|
202
|
+
status: 'failed',
|
|
203
|
+
handoff: handoffActualizado,
|
|
204
|
+
durationMs,
|
|
205
|
+
error: err.message,
|
|
206
|
+
});
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
resolve({
|
|
211
|
+
output: stdout.trim(),
|
|
212
|
+
sessionId,
|
|
213
|
+
status: 'completed',
|
|
214
|
+
handoff: handoffActualizado,
|
|
215
|
+
durationMs,
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
proc.on('error', (err) => {
|
|
220
|
+
if (err.code === 'ETIMEDOUT') timedOut = true;
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// ---------------------------------------------------------------------------
|
|
226
|
+
// Helpers
|
|
227
|
+
// ---------------------------------------------------------------------------
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Construye el prompt completo incluyendo el rol del agente y el HandoffContext.
|
|
231
|
+
* El agente recibirá suficiente contexto para actuar sin conocer la conversación padre.
|
|
232
|
+
*
|
|
233
|
+
* @param {string} agentName
|
|
234
|
+
* @param {string} input
|
|
235
|
+
* @param {HandoffContext|null} handoff
|
|
236
|
+
* @returns {string}
|
|
237
|
+
*/
|
|
238
|
+
function _buildPrompt(agentName, input, handoff) {
|
|
239
|
+
const partes = [];
|
|
240
|
+
|
|
241
|
+
if (handoff) {
|
|
242
|
+
partes.push(`[Contexto de transferencia]`);
|
|
243
|
+
partes.push(`Agente solicitante: ${handoff.parentAgent}`);
|
|
244
|
+
partes.push(`Motivo: ${handoff.reason}`);
|
|
245
|
+
partes.push(`Transferencia #${handoff.transferCount}`);
|
|
246
|
+
if (handoff.sessionId) partes.push(`Sesión: ${handoff.sessionId}`);
|
|
247
|
+
partes.push('');
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
partes.push(`Eres el agente ${agentName} del sistema SWL.`);
|
|
251
|
+
partes.push('');
|
|
252
|
+
partes.push(input);
|
|
253
|
+
|
|
254
|
+
return partes.join('\n');
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// ---------------------------------------------------------------------------
|
|
258
|
+
// Ejecución en lote (pipeline)
|
|
259
|
+
// ---------------------------------------------------------------------------
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Ejecuta una secuencia de agentes en pipeline, pasando el output de cada uno
|
|
263
|
+
* como parte del input del siguiente. Implementa el patrón PipelineContext.
|
|
264
|
+
*
|
|
265
|
+
* @param {Array<{ agentName: string, buildInput: (prevOutput: string, stepResults: object[]) => string }>} pasos
|
|
266
|
+
* @param {{ pipelineName: string, sessionId?: string, timeoutMs?: number }} opts
|
|
267
|
+
* @returns {Promise<{ status: string, stepResults: object[], finalOutput: string }>}
|
|
268
|
+
*/
|
|
269
|
+
async function executePipeline(pasos, opts) {
|
|
270
|
+
const { pipelineName, sessionId = null, timeoutMs = DEFAULT_TIMEOUT_MS } = opts || {};
|
|
271
|
+
const stepResults = [];
|
|
272
|
+
let prevOutput = '';
|
|
273
|
+
let finalOutput = '';
|
|
274
|
+
|
|
275
|
+
for (let i = 0; i < pasos.length; i++) {
|
|
276
|
+
const { agentName, buildInput } = pasos[i];
|
|
277
|
+
const input = buildInput(prevOutput, stepResults);
|
|
278
|
+
|
|
279
|
+
const handoff = {
|
|
280
|
+
reason: 'pipeline_execution',
|
|
281
|
+
parentAgent: pipelineName,
|
|
282
|
+
transferCount: i,
|
|
283
|
+
sessionId,
|
|
284
|
+
metadata: {
|
|
285
|
+
pipelineName,
|
|
286
|
+
currentStep: i + 1,
|
|
287
|
+
totalSteps: pasos.length,
|
|
288
|
+
isLastStep: i === pasos.length - 1,
|
|
289
|
+
},
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
const result = await executeAgent(agentName, input, { handoff, sessionId, timeoutMs });
|
|
293
|
+
|
|
294
|
+
stepResults.push({
|
|
295
|
+
step: i + 1,
|
|
296
|
+
agentName,
|
|
297
|
+
output: { text: result.output },
|
|
298
|
+
status: result.status === 'completed' ? 'completed' : 'failed',
|
|
299
|
+
durationMs: result.durationMs,
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
if (result.status !== 'completed') {
|
|
303
|
+
return { status: result.status, stepResults, finalOutput: result.output };
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
prevOutput = result.output;
|
|
307
|
+
finalOutput = result.output;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return { status: 'completed', stepResults, finalOutput };
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// ---------------------------------------------------------------------------
|
|
314
|
+
// Exports
|
|
315
|
+
// ---------------------------------------------------------------------------
|
|
316
|
+
|
|
317
|
+
module.exports = {
|
|
318
|
+
executeAgent,
|
|
319
|
+
executePipeline,
|
|
320
|
+
MAX_TRANSFER_COUNT,
|
|
321
|
+
DEFAULT_TIMEOUT_MS,
|
|
322
|
+
};
|