@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.
Files changed (129) hide show
  1. package/CLAUDE.md +12 -4
  2. package/README.md +1 -1
  3. package/bin/swl-mcp-server.js +187 -187
  4. package/bin/swl-webhook-server.js +198 -0
  5. package/comandos/swl/.evolved.json +22 -22
  6. package/comandos/swl/adoptar-proyecto.md +21 -1
  7. package/comandos/swl/claudemd.md +14 -1
  8. package/comandos/swl/contribuir.md +233 -233
  9. package/comandos/swl/exportar-vault.md +207 -7
  10. package/comandos/swl/nuevo-proyecto.md +24 -2
  11. package/gateway/adapters/base.js +109 -0
  12. package/gateway/adapters/discord.js +167 -0
  13. package/gateway/adapters/email.js +221 -0
  14. package/gateway/adapters/slack.js +192 -0
  15. package/gateway/adapters/telegram.js +183 -0
  16. package/gateway/adapters/webhook.js +113 -0
  17. package/gateway/adapters/whatsapp.js +214 -0
  18. package/gateway/agent-executor.js +322 -0
  19. package/gateway/command-relay.js +271 -0
  20. package/gateway/cron/jobs.js +263 -0
  21. package/gateway/cron/scheduler.js +322 -0
  22. package/gateway/cron/store.js +335 -0
  23. package/gateway/index.js +320 -0
  24. package/gateway/lib/event-channel.js +191 -0
  25. package/gateway/session.js +131 -0
  26. package/gateway/webhook-server.js +324 -0
  27. package/habilidades/backend-production-resilience/SKILL.md +288 -288
  28. package/habilidades/benchmark-memoria/SKILL.md +186 -186
  29. package/habilidades/build-errors-nextjs/SKILL.md +55 -1
  30. package/habilidades/diagrama-arquitectura/assets/template.html +276 -276
  31. package/habilidades/doubt-driven-review/SKILL.md +171 -171
  32. package/habilidades/doubt-driven-review/recursos/EXAMPLES.md +130 -130
  33. package/habilidades/eval-framework/SKILL.md +212 -212
  34. package/habilidades/extractor-de-aprendizajes/SKILL.md +24 -10
  35. package/habilidades/harness-claude-code/SKILL.md +299 -299
  36. package/habilidades/infra-github-actions/SKILL.md +166 -166
  37. package/habilidades/legacy-code-rescue/SKILL.md +267 -267
  38. package/habilidades/manejo-errores/.evolved.json +8 -8
  39. package/habilidades/meta-skills-estandar/recursos/convencion-examples.md +93 -93
  40. package/habilidades/meta-skills-estandar/recursos/skills-as-agents.md +163 -163
  41. package/habilidades/nextjs-testing/SKILL.md +89 -5
  42. package/habilidades/node-experto/SKILL.md +37 -1
  43. package/habilidades/patrones-python/SKILL.md +229 -229
  44. package/habilidades/patrones-python/recursos/patrones-avanzados.md +469 -469
  45. package/habilidades/planear-fase/SKILL.md +319 -319
  46. package/habilidades/react-experto/SKILL.md +45 -4
  47. package/habilidades/release-semver/.evolved.json +8 -8
  48. package/habilidades/swl-claudemd/SKILL.md +15 -1
  49. package/habilidades/tdd-workflow/SKILL.md +36 -4
  50. package/habilidades/testing-python/SKILL.md +340 -340
  51. package/hooks/claudemd-bloat-detector.js +161 -161
  52. package/hooks/inyeccion-contexto.js +8 -3
  53. package/hooks/lib/agent-routing.js +107 -107
  54. package/hooks/lib/auto-consolidator.js +335 -335
  55. package/hooks/lib/error-classifier.js +308 -308
  56. package/hooks/lib/merkle-audit.js +96 -96
  57. package/hooks/lib/provenance-tracker.js +191 -191
  58. package/hooks/lib/rate-limit-ip.js +177 -0
  59. package/hooks/lib/rate-limit-tracker.js +253 -253
  60. package/hooks/lib/resource-quota.js +122 -122
  61. package/hooks/lib/retry-jitter.js +165 -165
  62. package/hooks/lib/skill-auditor.js +588 -588
  63. package/hooks/lib/sync-status.js +228 -228
  64. package/hooks/lib/taint-tracker.js +107 -107
  65. package/hooks/lib/text-similarity.js +241 -241
  66. package/hooks/lib/toon-compressor.js +245 -245
  67. package/hooks/lib/webhook-dedup.js +184 -0
  68. package/hooks/lib/webhook-verify.js +123 -0
  69. package/hooks/proteccion-rutas.js +120 -15
  70. package/hooks/registro-turnos.js +209 -209
  71. package/hooks/sugerir-regenerar-inventario.js +170 -170
  72. package/hooks/validar-formato-post-subagente.js +140 -140
  73. package/hooks/validar-memoria-hook.js +218 -218
  74. package/instintos/prompt-appendices.yaml +57 -57
  75. package/manifiestos/agent-output-schemas.json +57 -57
  76. package/manifiestos/modulos.json +1 -0
  77. package/manifiestos/skills-lock.json +37 -37
  78. package/package.json +5 -3
  79. package/plantillas/auditor-veto-template.md +105 -105
  80. package/plantillas/github-workflows/README.md +47 -47
  81. package/plantillas/github-workflows/release-please.yml +44 -44
  82. package/plantillas/github-workflows/swl-ci.yml +107 -107
  83. package/plantillas/github-workflows/swl-security.yml +51 -51
  84. package/plugin.json +1 -1
  85. package/reglas/analisis-previo-tareas-grandes.md +172 -172
  86. package/reglas/arreglar-al-detectar.md +147 -147
  87. package/reglas/fragmentos-compartidos.md +152 -152
  88. package/reglas/harness-claude-code.md +213 -213
  89. package/reglas/usar-context7.md +226 -226
  90. package/reglas/usar-sistema-swl.md +251 -0
  91. package/schemas/diary-entry.schema.json +80 -80
  92. package/scripts/benchmark-memoria.js +167 -167
  93. package/scripts/comandos/skills.js +251 -2
  94. package/scripts/configurar-branch-protection.js +418 -418
  95. package/scripts/detectar-aprendizajes-duplicados.js +151 -151
  96. package/scripts/field-report.js +199 -199
  97. package/scripts/generar-checklists-consolidados.js +273 -273
  98. package/scripts/generar-inventario.js +420 -420
  99. package/scripts/generar-matriz-lenguajes.js +271 -271
  100. package/scripts/lib/artefactos-python.js +43 -43
  101. package/scripts/lib/benchmark-metrics.js +160 -160
  102. package/scripts/lib/budget-enforcer.js +252 -252
  103. package/scripts/lib/configurar-ci.js +380 -380
  104. package/scripts/lib/contadores-inventario.js +217 -217
  105. package/scripts/lib/detectar-stack-detallado.js +307 -307
  106. package/scripts/lib/diary-entry.js +234 -234
  107. package/scripts/lib/eval-metrics-store.js +218 -218
  108. package/scripts/lib/eval-quality.js +171 -171
  109. package/scripts/lib/eval-schemas.js +144 -144
  110. package/scripts/lib/eval-self-correct.js +106 -106
  111. package/scripts/lib/eval-validator.js +185 -185
  112. package/scripts/lib/jaccard-similarity.js +98 -98
  113. package/scripts/lib/longmemeval-runner.js +125 -125
  114. package/scripts/lib/npm-version.js +261 -261
  115. package/scripts/lib/paquetes-conocidos.js +50 -50
  116. package/scripts/lib/prompt-builder.js +264 -264
  117. package/scripts/lib/rrf-fusion.js +175 -175
  118. package/scripts/lib/scoring-instintos.js +277 -277
  119. package/scripts/lib/semantic-search.js +252 -252
  120. package/scripts/limpiar-artefactos-python.js +131 -131
  121. package/scripts/mcp-server/README.md +128 -128
  122. package/scripts/mcp-server/handlers.js +206 -206
  123. package/scripts/migrar-csv-a-array.js +168 -168
  124. package/scripts/migrar-fase-dominio.js +201 -201
  125. package/scripts/publicar.js +511 -511
  126. package/scripts/run-eval.js +141 -141
  127. package/scripts/validar-manifest.js +195 -195
  128. package/scripts/validar-userland-vacio.js +110 -110
  129. 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
+ };