@saulwade/swl-ses 1.3.8 → 1.4.1

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