openprompt-lang 1.3.0 → 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 (52) hide show
  1. package/bin/cli.js +2 -0
  2. package/docs/00-ARCHITECTURE/OPL-BOOST-MULTI-AGENT.md +406 -0
  3. package/docs/02-STANDARDS/AGENTS.template.md +89 -0
  4. package/docs/02-STANDARDS/ticket-driven-development.md +99 -0
  5. package/docs/04-TICKETS/BOOST-001-profile-registry.md +66 -0
  6. package/docs/04-TICKETS/BOOST-002-context-compression.md +58 -0
  7. package/docs/04-TICKETS/BOOST-003-template-hydration.md +69 -0
  8. package/docs/04-TICKETS/BOOST-004-fewshot-engine.md +58 -0
  9. package/docs/04-TICKETS/BOOST-005-agent-pool.md +69 -0
  10. package/docs/04-TICKETS/BOOST-006-specialized-agents.md +53 -0
  11. package/docs/04-TICKETS/BOOST-007-validation-loop.md +56 -0
  12. package/docs/04-TICKETS/BOOST-008-orchestrator.md +71 -0
  13. package/docs/04-TICKETS/BOOST-009-cache-system.md +56 -0
  14. package/docs/04-TICKETS/BOOST-010-cli-mcp.md +67 -0
  15. package/docs/04-TICKETS/BOOST-011-self-learning.md +50 -0
  16. package/docs/04-TICKETS/BOOST-012-prompt-preamble.md +109 -0
  17. package/docs/04-TICKETS/BOOST-013-hydrator-duplicate-code.md +132 -0
  18. package/docs/04-TICKETS/BOOST-014-multiagent-missing-parts.md +87 -0
  19. package/docs/04-TICKETS/BOOST-015-skeleton-type-missing.md +76 -0
  20. package/docs/04-TICKETS/BOOST-016-output-path-duplicate.md +68 -0
  21. package/docs/04-TICKETS/INDEX.md +89 -0
  22. package/docs/04-TICKETS/_archive/BOOST-005-micro-tasking.md +67 -0
  23. package/docs/04-TICKETS/_archive/BOOST-006-validation-loop.md +66 -0
  24. package/docs/04-TICKETS/_archive/BOOST-007-progressive-pipeline.md +69 -0
  25. package/docs/04-TICKETS/_archive/BOOST-008-cli-mcp-integration.md +74 -0
  26. package/docs/AI_CONTEXT.md +16 -0
  27. package/package.json +3 -2
  28. package/src/boost/agent-pool.js +442 -0
  29. package/src/boost/agents/index.js +79 -0
  30. package/src/boost/cache.js +241 -0
  31. package/src/boost/context-compressor.js +354 -0
  32. package/src/boost/fewshot-retriever.js +332 -0
  33. package/src/boost/hardware-detector.js +486 -0
  34. package/src/boost/hydrator.js +398 -0
  35. package/src/boost/index.js +60 -0
  36. package/src/boost/orchestrator.js +615 -0
  37. package/src/boost/preamble.js +217 -0
  38. package/src/boost/profile-registry.js +264 -0
  39. package/src/boost/self-learn.js +247 -0
  40. package/src/boost/skeletons/component.skeleton.js +24 -0
  41. package/src/boost/skeletons/hook.skeleton.js +27 -0
  42. package/src/boost/skeletons/index.js +67 -0
  43. package/src/boost/skeletons/page.skeleton.js +22 -0
  44. package/src/boost/skeletons/service.skeleton.js +20 -0
  45. package/src/boost/skeletons/store.skeleton.js +18 -0
  46. package/src/boost/skeletons/type.skeleton.js +11 -0
  47. package/src/boost/task-dispatcher.js +142 -0
  48. package/src/boost/validation-loop.js +495 -0
  49. package/src/cli/commands-boost.js +394 -0
  50. package/src/mcp-refactor/handlers/boost.js +295 -0
  51. package/src/mcp-refactor/router.js +19 -0
  52. package/src/mcp-refactor/tools.js +113 -0
@@ -0,0 +1,66 @@
1
+ # BOOST-006 — Validation Feedback Loop
2
+
3
+ ## Metadatos
4
+
5
+ | Campo | Valor |
6
+ |-------|-------|
7
+ | **ID** | BOOST-006 |
8
+ | **Título** | Validation Feedback Loop |
9
+ | **Épica** | Módulo OPL Boost |
10
+ | **Prioridad** | Alta |
11
+ | **Estado** | 🔴 Pendiente |
12
+ | **Depende de** | BOOST-005 |
13
+ | **Archivos** | `src/boost/validation-loop.js` |
14
+
15
+ ## Descripción
16
+
17
+ Sistema post-generación que corre validación OPL y TypeScript sobre el código generado, y retroalimenta los errores específicos al modelo para que los corrija. Los modelos pequeños son significativamente mejores corrigiendo errores específicos que generando código correcto la primera vez.
18
+
19
+ Si tras N reintentos el código sigue fallando, escala hacia abajo la complejidad de la tarea.
20
+
21
+ ## Criterios de Aceptación
22
+
23
+ ### CA-1: Validación post-generación
24
+ - [ ] `validate(code, kind)` → ejecuta validación OPL sobre el código generado
25
+ - [ ] Detecta: errores de anotaciones, errores de tipo, errores de sintaxis, violaciones de @limit
26
+ - [ ] Retorna array de errores con: tipo, mensaje, línea, sugerencia de fix
27
+
28
+ ### CA-2: Feedback loop
29
+ - [ ] `feedbackLoop(code, errors, profile)` → intenta corregir errores iterativamente
30
+ - [ ] Por cada error, genera un mensaje de feedback claro para el modelo
31
+ - [ ] El feedback dice exactamente qué está mal y da una sugerencia de cómo arreglarlo
32
+ - [ ] El modelo corrige y se re-valida
33
+ - [ ] Número de reintentos según perfil: small=3, medium=2, large=1
34
+
35
+ ### CA-3: Escalado de complejidad
36
+ - [ ] Si tras 3 reintentos (small) el código sigue fallando, reduce complejidad
37
+ - [ ] Estrategias de escalado: simplificar lógica, usar skeleton más básico, dividir en más micro-tareas
38
+ - [ ] `escalateDown(task, attempt)` → devuelve versión simplificada de la tarea
39
+ - [ ] El escalado se registra como @learn-error para futuras sesiones
40
+
41
+ ### CA-4: Integración con micro-tasker
42
+ - [ ] Cuando una micro-tarea individual falla la validación, se reintenta antes de pasar a la siguiente
43
+ - [ ] Si la micro-tarea falla consistentemente, el orquestador (BOOST-005) decide escalar
44
+
45
+ ### CA-5: Reporte de calidad
46
+ - [ ] `generateReport(codeHistory, errors)` → genera reporte de cuántos intentos tomó, qué errores se corrigieron
47
+ - [ ] El reporte se puede incluir en la documentación de la sesión
48
+
49
+ ### CA-6: Tests
50
+ - [ ] Test de detección de errores de anotaciones
51
+ - [ ] Test de feedback loop con mock de correcciones
52
+ - [ ] Test de escalado tras N reintentos fallidos
53
+ - [ ] Test de reporte de calidad
54
+
55
+ ## Archivos a crear/modificar
56
+
57
+ | Archivo | Acción |
58
+ |---------|--------|
59
+ | `src/boost/validation-loop.js` | ➕ Crear |
60
+
61
+ ## Notas técnicas
62
+
63
+ - La validación OPL se hace invocando `npx openprompt-lang validate` o usando el módulo de validación directamente
64
+ - El feedback loop necesita acceso al modelo para re-generar — pero en esta fase inicial, solo prepara los mensajes de feedback (la re-generación la hace quien llame al boost)
65
+ - El escalado de complejidad es determinista (no necesita IA): simplifica basado en reglas
66
+ - Los errores más comunes en modelos pequeños: @kind faltante, @limit excedido, tipos incorrectos
@@ -0,0 +1,69 @@
1
+ # BOOST-007 — Progressive Disclosure Pipeline
2
+
3
+ ## Metadatos
4
+
5
+ | Campo | Valor |
6
+ |-------|-------|
7
+ | **ID** | BOOST-007 |
8
+ | **Título** | Progressive Disclosure Pipeline |
9
+ | **Épica** | Módulo OPL Boost |
10
+ | **Prioridad** | Alta |
11
+ | **Estado** | 🔴 Pendiente |
12
+ | **Depende de** | BOOST-002, BOOST-006 |
13
+ | **Archivos** | `src/boost/progressive-pipeline.js`, `src/boost/index.js` |
14
+
15
+ ## Descripción
16
+
17
+ Pipeline multi-etapa que orquesta todos los componentes Boost en un flujo coherente. Cada etapa expone gradualmente más complejidad al modelo, de modo que nunca ve el problema completo de golpe.
18
+
19
+ El `index.js` es el punto de entrada unificado del módulo Boost.
20
+
21
+ ## Criterios de Aceptación
22
+
23
+ ### CA-1: Pipeline multi-etapa
24
+ - [ ] **Stage 1 — Diseño**: el modelo define props, tipos, interface, contract sin implementar lógica
25
+ - [ ] **Stage 2 — Implementación**: el modelo rellena el skeleton con lógica de negocio
26
+ - [ ] **Stage 3 — Polish**: el modelo recibe feedback de validación y corrige errores
27
+ - [ ] Cada etapa es más específica que la anterior
28
+
29
+ ### CA-2: Orchestrador unificado (index.js)
30
+ - [ ] `boost(task, options)` → orquesta todo el pipeline
31
+ - [ ] `options.profile` → perfil a usar
32
+ - [ ] `options.kind` → tipo de archivo a generar
33
+ - [ ] `options.dryRun` → mostrar plan sin ejecutar
34
+ - [ ] `options.output` → archivo de salida (opcional)
35
+ - [ ] Retorna: `{ code, metadata, report }`
36
+
37
+ ### CA-3: Metadata de ejecución
38
+ - [ ] `{ profile, stages: [{name, duration, result}], totalTime, compressionRatio, validationAttempts }`
39
+ - [ ] Permite comparar rendimiento entre perfiles
40
+ - [ ] Se puede exportar como JSON
41
+
42
+ ### CA-4: Modo dry-run
43
+ - [ ] `boost(task, { dryRun: true })` → muestra qué haría en cada etapa sin ejecutar
44
+ - [ ] Muestra: plan de micro-tareas, skeletons a usar, ejemplos a inyectar
45
+
46
+ ### CA-5: Integración con compress
47
+ - [ ] Antes del Stage 1, comprime el contexto según perfil (usa BOOST-002)
48
+ - [ ] El Stage 1 recibe contexto mínimo
49
+ - [ ] Los Stages 2 y 3 pueden recibir contexto adicional si es necesario
50
+
51
+ ### CA-6: Tests
52
+ - [ ] Test de pipeline completo con mock de modelo
53
+ - [ ] Test de metadata de ejecución
54
+ - [ ] Test de dry-run
55
+ - [ ] Test de integración: pipeline completo produce código válido
56
+
57
+ ## Archivos a crear/modificar
58
+
59
+ | Archivo | Acción |
60
+ |---------|--------|
61
+ | `src/boost/index.js` | ➕ Crear (orquestador unificado) |
62
+ | `src/boost/progressive-pipeline.js` | ➕ Crear (pipeline multi-etapa) |
63
+
64
+ ## Notas técnicas
65
+
66
+ - El index.js es el API público del módulo Boost
67
+ - Cada etapa del pipeline es un plugin: `pipeline.use(stage)` para futura extensibilidad
68
+ - El pipeline registra tiempo de cada etapa para diagnóstico
69
+ - Si una etapa falla, el pipeline detiene la ejecución y devuelve error con estado parcial
@@ -0,0 +1,74 @@
1
+ # BOOST-008 — CLI + MCP Integration
2
+
3
+ ## Metadatos
4
+
5
+ | Campo | Valor |
6
+ |-------|-------|
7
+ | **ID** | BOOST-008 |
8
+ | **Título** | CLI + MCP Integration |
9
+ | **Épica** | Módulo OPL Boost |
10
+ | **Prioridad** | Media |
11
+ | **Estado** | 🔴 Pendiente |
12
+ | **Depende de** | BOOST-007 |
13
+ | **Archivos** | `src/cli/commands-boost.js`, `src/mcp-server.js`, `src/mcp-refactor/router.js`, `AGENTS.md` |
14
+
15
+ ## Descripción
16
+
17
+ Integrar el módulo Boost con la CLI de OPL (comandos `opl boost *`) y con el servidor MCP (tools `OPL_Boost_*`). También actualizar AGENTS.md con la sección Boost Workflow para que las IAs futuras sepan cómo usar el módulo.
18
+
19
+ ## Criterios de Aceptación
20
+
21
+ ### CA-1: CLI completa
22
+ - [ ] `opl boost check` → diagnóstico del perfil activo
23
+ - [ ] `opl boost profile <name>` → forzar perfil (small/medium/large/auto)
24
+ - [ ] `opl boost generate <desc>` → generar código con pipeline completo
25
+ - [ ] `opl boost microtask <task>` → descomponer tarea en micro-tareas
26
+ - [ ] Todos los comandos tienen `--help` descriptivo
27
+ - [ ] Todos los comandos tienen `--dry-run`
28
+
29
+ ### CA-2: MCP tools
30
+ - [ ] `OPL_Boost_profile` → mostrar perfil actual del modelo
31
+ - [ ] `OPL_Boost_compress` → comprimir contexto según perfil
32
+ - [ ] `OPL_Boost_microtask` → ejecutar pipeline de micro-tasking
33
+ - [ ] `OPL_Boost_hydrate` → hidratar un skeleton con lógica
34
+ - [ ] `OPL_Boost_validate` → loop de validación con retroalimentación
35
+ - [ ] Las tools aparecen en el servidor MCP y son invocables
36
+
37
+ ### CA-3: AGENTS.md actualizado
38
+ - [ ] Nueva sección "## 🚀 OPL Boost — Potenciar modelos pequeños" en AGENTS.md
39
+ - [ ] Describe: qué es, cuándo usarlo, cómo configurarlo
40
+ - [ ] Tabla de perfiles (small/medium/large)
41
+ - [ ] Workflow Boost para IAs que usan el módulo
42
+ - [ ] Referencia rápida de comandos `opl boost *`
43
+
44
+ ### CA-4: Registrar en bin/cli.js
45
+ - [ ] `registerBoost(program)` llamado desde `bin/cli.js`
46
+ - [ ] Import y registro consistente con los otros comandos
47
+
48
+ ### CA-5: Registrar en MCP server
49
+ - [ ] Tools de boost agregadas a TOOLS en `src/mcp-refactor/tools.js`
50
+ - [ ] Router en `src/mcp-refactor/router.js` maneja boost tools
51
+ - [ ] Workflow generator en `src/mcp-workflow.js` incluye boost
52
+
53
+ ### CA-6: Tests de integración
54
+ - [ ] Test de CLI: `opl boost check` funciona sin errores
55
+ - [ ] Test de MCP: tools registradas correctamente
56
+ - [ ] Test que todos los comandos tienen `--help`
57
+
58
+ ## Archivos a crear/modificar
59
+
60
+ | Archivo | Acción |
61
+ |---------|--------|
62
+ | `src/cli/commands-boost.js` | ➕ Crear (registro completo de comandos) |
63
+ | `bin/cli.js` | ✏️ Modificar (import + register) |
64
+ | `src/mcp-server.js` | ✏️ Modificar (boost tools en TOOLS) |
65
+ | `src/mcp-refactor/router.js` | ✏️ Modificar (boost routes) |
66
+ | `src/mcp-workflow.js` | ✏️ Modificar (boost instructions) |
67
+ | `AGENTS.md` | ✏️ Modificar (sección Boost) |
68
+
69
+ ## Notas técnicas
70
+
71
+ - Los MCP tools deben seguir el patrón existente en `src/mcp-refactor/tools.js`
72
+ - Los comandos CLI deben seguir el patrón de commander (`.command().description().action()`)
73
+ - La sección de AGENTS.md debe seguir el tono y formato del documento existente
74
+ - No debe romper comandos existentes ni tools MCP existentes
@@ -60,8 +60,24 @@ npx openPrompt-Lang validate # Validar anotaciones
60
60
  npx openPrompt-Lang lang list # Listar módulos
61
61
  npx openPrompt-Lang teach <id> # Aprender de un template
62
62
  npx openPrompt-Lang qa-gen # Generar tests de regresión
63
+
64
+ # OPL Boost (multi-agente para modelos pequeños)
65
+ opl boost check # Diagnóstico perfil + estado del sistema
66
+ opl boost profile [name] # Ver/forzar perfil (small/medium/large/auto)
67
+ opl boost setup # Detectar hardware + configurar
68
+ opl boost generate <desc> # Generar código con pipeline Boost
69
+ opl boost microtask <task> # Descomponer tarea en DAG
70
+ opl boost cache [action] # Gestionar caché (stats, clear, clean)
63
71
  ```
64
72
 
73
+ ## 5.5. Módulo OPL Boost
74
+ - **12 tickets implementados** (BOOST-001 a BOOST-012, todos completados)
75
+ - **Pipeline**: profile detection → context compression → few-shot → agent pool → skeleton hydration → validation loop
76
+ - **Dos modos**: single-pass (default) y multi-agent (`--multi-agent`)
77
+ - **Código**: `src/boost/` (17 archivos, ~3,700 líneas)
78
+ - **MCP tools**: `boost_generate`, `boost_compress`, `boost_profile`, `boost_plan`, `boost_validate`
79
+ - **Filosofía**: cada componente funciona independientemente; el multi-agente es opt-in
80
+
65
81
  ## 6. Referencia canónica
66
82
  - `.openprompt/FRAMEWORK.md` — Manual completo: anotaciones, comandos CLI, MCP, dominios, módulos, reglas estrictas.
67
83
  - `AGENTS.md` — Stack, convenciones, UI, calidad.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openprompt-lang",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "PromptLang CLI — Context Engine de anotaciones para desarrollo asistido por IA",
5
5
  "type": "module",
6
6
  "main": "./bin/cli.js",
@@ -55,7 +55,8 @@
55
55
  "opencode:wizard": "openprompt-lang wizard",
56
56
  "opencode:plan": "openprompt-lang plan",
57
57
  "opencode:execute": "openprompt-lang execute",
58
- "opencode:mode": "openprompt-lang mode"
58
+ "opencode:mode": "openprompt-lang mode",
59
+ "opl:enforce": "node scripts/opl-enforce-setup.mjs"
59
60
  },
60
61
  "keywords": [
61
62
  "prompt-lang",
@@ -0,0 +1,442 @@
1
+ // @use(kind, contract, limit, error)
2
+ // @kind(util)
3
+ // @contract(in: prompt, role, profile -> out: AgentPool instance + callOllama, buildPrompt, checkOllama @error: OllamaConnectionError, AgentTimeoutError)
4
+ // @limit(lines: 200)
5
+
6
+ /**
7
+ * Agent Pool — Módulo OPL Boost
8
+ *
9
+ * Conector a Ollama + pool de agentes para orquestación interna.
10
+ * Permite que OPL llame a modelos locales sin depender de OpenCode.
11
+ *
12
+ * Modo de uso:
13
+ * import { agentPool } from './agent-pool.js'
14
+ * const result = await agentPool.submit('types', 'hook useAuth')
15
+ *
16
+ * Modo cola (FIFO): default, una solicitud a la vez
17
+ * Modo paralelo: configurable via profile.agentPool.maxConcurrency
18
+ */
19
+
20
+ import { getProfile } from "./profile-registry.js"
21
+
22
+ // ──────────────────────────────────────────────
23
+ // Configuración
24
+ // ──────────────────────────────────────────────
25
+
26
+ const OLLAMA_HOST = process.env.OLLAMA_HOST || "http://localhost:11434"
27
+ const DEFAULT_TIMEOUT = 30_000 // 30s
28
+
29
+ // ──────────────────────────────────────────────
30
+ // System prompts por rol de agente
31
+ // ──────────────────────────────────────────────
32
+
33
+ const AGENT_SYSTEM_PROMPTS = {
34
+ types:
35
+ "Eres un experto en TypeScript. Tu única tarea es definir TYPES e INTERFACES. " +
36
+ "No implementes lógica. No generes JSX. Solo tipos, interfaces, y anotaciones de tipo. " +
37
+ "Output exacto: bloques de código TypeScript con export.",
38
+
39
+ state:
40
+ "Eres un experto en React state management. Recibes types ya definidos. " +
41
+ "Tu tarea es diseñar el ESTADO del componente/hook: useState, useReducer, useEffect. " +
42
+ "No implementes lógica de negocio. Solo estructura de estado y efectos. " +
43
+ "Output exacto: bloques de código con hooks de React.",
44
+
45
+ logic:
46
+ "Eres un experto en implementar lógica de negocio. Recibes types + estado ya definidos. " +
47
+ "Tu tarea es IMPLEMENTAR las funciones con lógica real. " +
48
+ "Usa los types y estado existentes. No los redefinas. " +
49
+ "Output exacto: la función/componente completo con implementación.",
50
+
51
+ cleaner:
52
+ "Eres un compresor de contexto para desarrollo asistido por IA. " +
53
+ "Recibes reglas extensas y debes devolver SOLO las reglas relevantes para la tarea específica. " +
54
+ "Prioriza: límites de líneas (@kind + @limit), reglas de enforcement, prohibiciones (NUNCA). " +
55
+ "Omite: stack, UI, colores, documentación de comandos no relevantes. " +
56
+ "Output exacto: markdown compacto con las reglas esenciales.",
57
+
58
+ validate:
59
+ "Eres un corrector de código. Recibes código que falló validación y los errores específicos. " +
60
+ "Tu tarea es CORREGIR solo los errores reportados, no reescribir todo. " +
61
+ "Conserva la estructura y lógica original. Arregla solo lo que falló. " +
62
+ "Output exacto: el código completo corregido.",
63
+ }
64
+
65
+ // ──────────────────────────────────────────────
66
+ // Builders de prompts
67
+ // ──────────────────────────────────────────────
68
+
69
+ const PROMPT_TEMPLATES = {
70
+ types: (input) =>
71
+ `Task: Define types for "${input}"
72
+
73
+ Rules:
74
+ - Export all interfaces
75
+ - Use TypeScript strict mode
76
+ - Add JSDoc comments
77
+ - Maximum 60 lines
78
+
79
+ Output ONLY TypeScript types.`,
80
+
81
+ state: (input) =>
82
+ `Task: Design state management
83
+
84
+ Context: ${input}
85
+
86
+ Rules:
87
+ - Use React hooks (useState, useEffect, useCallback)
88
+ - Reference existing types
89
+ - No business logic
90
+ - Maximum 60 lines
91
+
92
+ Output ONLY React hooks and state.`,
93
+
94
+ logic: (input) =>
95
+ `Task: Implement business logic
96
+
97
+ Context: ${input}
98
+
99
+ Rules:
100
+ - Use existing types and state
101
+ - Implement full logic
102
+ - Handle errors and edge cases
103
+ - Maximum 80 lines
104
+
105
+ Output ONLY the implementation.`,
106
+
107
+ cleaner: (input) =>
108
+ `Task: Compress context for code generation
109
+
110
+ Target task: ${input.substring(0, 200)}
111
+
112
+ Rules to preserve (if present in context):
113
+ - @kind limits (hook:80, component:120, page:200, service:150, store:100, util:100)
114
+ - @limit blocks commits, NOT a suggestion
115
+ - @use() required at file top
116
+ - Named exports over default
117
+ - NUNCA rules (any, template files, etc.)
118
+ - Annotations first, implementation second
119
+
120
+ Compress to <200 lines. Preserve critical rules only.`,
121
+
122
+ validate: (input) =>
123
+ `Task: Fix validation errors
124
+
125
+ Code with errors:
126
+ \`\`\`
127
+ ${input.substring(0, 1500)}
128
+ \`\`\`
129
+
130
+ Fix ONLY the reported errors. Do not rewrite.
131
+ Output the ENTIRE corrected file.`,
132
+ }
133
+
134
+ export function buildPrompt(role, input) {
135
+ const systemPrompt = AGENT_SYSTEM_PROMPTS[role]
136
+ const template = PROMPT_TEMPLATES[role]
137
+
138
+ if (!systemPrompt || !template) {
139
+ throw new Error(`Rol de agente desconocido: "${role}". Roles: ${Object.keys(AGENT_SYSTEM_PROMPTS).join(", ")}`)
140
+ }
141
+
142
+ return {
143
+ system: systemPrompt,
144
+ prompt: template(input),
145
+ role,
146
+ }
147
+ }
148
+
149
+ // ──────────────────────────────────────────────
150
+ // Ollama connector
151
+ // ──────────────────────────────────────────────
152
+
153
+ export class OllamaConnectionError extends Error {
154
+ constructor(message, cause) {
155
+ super(message)
156
+ this.name = "OllamaConnectionError"
157
+ this.cause = cause
158
+ }
159
+ }
160
+
161
+ export class AgentTimeoutError extends Error {
162
+ constructor(role, timeout) {
163
+ super(`Agent "${role}" timed out after ${timeout}ms`)
164
+ this.name = "AgentTimeoutError"
165
+ this.role = role
166
+ this.timeout = timeout
167
+ }
168
+ }
169
+
170
+ // Cache de modelo seleccionado para no re-detectar en cada llamada
171
+ let _bestModelCache = null
172
+
173
+ export async function callOllama(promptText, options = {}) {
174
+ const {
175
+ model = _bestModelCache || "llama3.2:latest", // default seguro siempre disponible
176
+ system = "",
177
+ temperature = 0.2,
178
+ maxTokens = 4096,
179
+ timeout = DEFAULT_TIMEOUT,
180
+ } = options
181
+
182
+ const controller = new AbortController()
183
+ const timeoutId = setTimeout(() => controller.abort(), timeout)
184
+
185
+ try {
186
+ const body = {
187
+ model,
188
+ prompt: promptText,
189
+ system,
190
+ stream: false,
191
+ options: {
192
+ temperature,
193
+ num_predict: maxTokens,
194
+ },
195
+ }
196
+
197
+ const response = await fetch(`${OLLAMA_HOST}/api/generate`, {
198
+ method: "POST",
199
+ headers: { "Content-Type": "application/json" },
200
+ body: JSON.stringify(body),
201
+ signal: controller.signal,
202
+ })
203
+
204
+ if (!response.ok) {
205
+ throw new OllamaConnectionError(
206
+ `Ollama responded with ${response.status}: ${response.statusText}`
207
+ )
208
+ }
209
+
210
+ const data = await response.json()
211
+
212
+ if (data.error) {
213
+ throw new OllamaConnectionError(`Ollama error: ${data.error}`)
214
+ }
215
+
216
+ return {
217
+ text: data.response || "",
218
+ totalDuration: data.total_duration || 0,
219
+ tokensPerSecond: data.tokens_per_second || 0,
220
+ tokenCount: (data.tokens_evaluated || 0) + (data.prompt_eval_count || 0),
221
+ }
222
+ } catch (err) {
223
+ if (err.name === "AbortError") {
224
+ throw new AgentTimeoutError(options.role || "unknown", timeout)
225
+ }
226
+ if (err instanceof OllamaConnectionError || err instanceof AgentTimeoutError) {
227
+ throw err
228
+ }
229
+ throw new OllamaConnectionError(
230
+ `Failed to connect to Ollama at ${OLLAMA_HOST}: ${err.message}`,
231
+ err
232
+ )
233
+ } finally {
234
+ clearTimeout(timeoutId)
235
+ }
236
+ }
237
+
238
+ // ──────────────────────────────────────────────
239
+ // Pool de agentes (cola FIFO)
240
+ // ──────────────────────────────────────────────
241
+
242
+ class AgentPool {
243
+ constructor() {
244
+ this._queue = []
245
+ this._running = false
246
+ this._currentTask = null
247
+ this._stats = { completed: 0, failed: 0, totalTime: 0 }
248
+ }
249
+
250
+ /**
251
+ * Encola una solicitud de agente y retorna el resultado.
252
+ * Antes de encolar, verifica el estado del sistema (RAM, CPU).
253
+ *
254
+ * @param {string} role - Rol del agente (types, state, logic, cleaner, validate)
255
+ * @param {string} input - Input para el agente
256
+ * @param {object} [profile] - Perfil Boost (opcional, auto-detecta si se omite)
257
+ * @returns {Promise<{text: string, role: string, duration: number, metrics: object}>}
258
+ */
259
+ async submit(role, input, profile) {
260
+ const prof = profile || getProfile()
261
+ const agentConfig = prof?.agentPool || {}
262
+
263
+ // Verificar estado del sistema antes de encolar
264
+ const { getSafeParallelism, getRAMWarning } = await import("./hardware-detector.js")
265
+ const safety = getSafeParallelism(prof)
266
+
267
+ if (!safety.safe) {
268
+ const warning = getRAMWarning()
269
+ throw new Error(
270
+ `Boost bloqueado por recursos insuficientes.\n${warning}\n` +
271
+ `→ Libera memoria y vuelve a intentarlo.`
272
+ )
273
+ }
274
+
275
+ return new Promise((resolve, reject) => {
276
+ this._queue.push({
277
+ role,
278
+ input,
279
+ profile: prof,
280
+ agentConfig,
281
+ safety, // guardamos la recomendación de seguridad
282
+ resolve,
283
+ reject,
284
+ })
285
+ this._processQueue()
286
+ })
287
+ }
288
+
289
+ async _processQueue() {
290
+ if (this._running || this._queue.length === 0) return
291
+ this._running = true
292
+
293
+ while (this._queue.length > 0) {
294
+ const task = this._queue.shift()
295
+ this._currentTask = task
296
+ const startTime = Date.now()
297
+
298
+ // Re-verificar sistema antes de CADA agente (por si cambió)
299
+ try {
300
+ const { getSafeParallelism, getRAMWarning } = await import("./hardware-detector.js")
301
+ const currentSafety = getSafeParallelism(task.profile)
302
+
303
+ if (!currentSafety.safe) {
304
+ // Si el sistema se saturó mientras esperaba, re-encolar
305
+ const warning = getRAMWarning()
306
+ console.warn(`\n ⏸ Agente "${task.role}" pausado — ${warning}`)
307
+ // Esperar 5 segundos y re-intentar
308
+ await new Promise((r) => setTimeout(r, 5000))
309
+ this._queue.unshift(task) // re-encolar al principio
310
+ continue
311
+ }
312
+
313
+ // Si hay demasiados agentes en paralelo y estamos en modo cola, esperar
314
+ if (currentSafety.mode === "queue" && task.safety?.mode === "parallel") {
315
+ // La situación cambió: degradar a cola automáticamente
316
+ console.warn(`\n ⏸ Cambiando a modo cola por carga del sistema.`)
317
+ }
318
+ } catch {
319
+ // Si falla la verificación, continuar de todas formas
320
+ }
321
+
322
+ try {
323
+ const prompt = buildPrompt(task.role, task.input)
324
+ const modelName =
325
+ task.agentConfig.modelName || task.profile?.agentPool?.modelName || _bestModelCache || await selectBestModel(task.profile) || "llama3.2:latest"
326
+
327
+ // Estimar RAM que usará el modelo
328
+ const { estimateModelRAM } = await import("./hardware-detector.js")
329
+ const estimatedRAM = estimateModelRAM(modelName)
330
+
331
+ const result = await callOllama(prompt.prompt, {
332
+ model: modelName,
333
+ system: prompt.system,
334
+ temperature: task.agentConfig.temperature || 0.2,
335
+ timeout: task.agentConfig.timeout || DEFAULT_TIMEOUT,
336
+ role: task.role,
337
+ })
338
+
339
+ const duration = Date.now() - startTime
340
+ this._stats.completed++
341
+ this._stats.totalTime += duration
342
+
343
+ task.resolve({
344
+ text: result.text.trim(),
345
+ role: task.role,
346
+ duration,
347
+ metrics: {
348
+ tokensPerSecond: result.tokensPerSecond,
349
+ tokenCount: result.tokenCount,
350
+ model: modelName,
351
+ estimatedRAMGB: estimatedRAM,
352
+ },
353
+ })
354
+ } catch (err) {
355
+ this._stats.failed++
356
+ task.reject(err)
357
+ }
358
+ }
359
+
360
+ this._running = false
361
+ this._currentTask = null
362
+ }
363
+
364
+ get stats() {
365
+ return { ...this._stats, queueLength: this._queue.length, isRunning: this._running }
366
+ }
367
+
368
+ async clear() {
369
+ this._queue = []
370
+ }
371
+ }
372
+
373
+ // Singleton
374
+ export const agentPool = new AgentPool()
375
+
376
+ // ──────────────────────────────────────────────
377
+ // Utilidades de detección
378
+ // ──────────────────────────────────────────────
379
+
380
+ export async function checkOllama() {
381
+ try {
382
+ const response = await fetch(`${OLLAMA_HOST}/api/tags`, {
383
+ signal: AbortSignal.timeout(5000),
384
+ })
385
+ return response.ok
386
+ } catch {
387
+ return false
388
+ }
389
+ }
390
+
391
+ export async function listOllamaModels() {
392
+ try {
393
+ const response = await fetch(`${OLLAMA_HOST}/api/tags`, {
394
+ signal: AbortSignal.timeout(5000),
395
+ })
396
+ if (!response.ok) return []
397
+ const data = await response.json()
398
+ return (data.models || []).map((m) => ({
399
+ name: m.name,
400
+ size: m.size,
401
+ modifiedAt: m.modified_at,
402
+ }))
403
+ } catch {
404
+ return []
405
+ }
406
+ }
407
+
408
+ export async function selectBestModel(profile) {
409
+ const models = await listOllamaModels()
410
+ if (models.length === 0) return null
411
+
412
+ const profileName = profile?.name || "medium"
413
+
414
+ // Preferencias según perfil — mapeadas a modelos que existen en esta instancia
415
+ const preferences = {
416
+ small: ["qwen2.5-coder:7b", "qwen2.5-coder", "llama3.2", "llama3.2:latest", "phi", "deepseek-coder"],
417
+ medium: ["qwen2.5-coder:7b", "qwen2.5-coder", "codellama", "mistral", "llama3"],
418
+ large: ["qwen2.5-72b", "llama-3-70b", "llama3:latest", "qwen2.5-coder:7b"],
419
+ }
420
+
421
+ const preferred = preferences[profileName] || preferences.medium
422
+
423
+ // Buscar el mejor modelo disponible según preferencias
424
+ for (const pref of preferred) {
425
+ const match = models.find((m) => m.name === pref || m.name.toLowerCase().includes(pref))
426
+ if (match) {
427
+ _bestModelCache = match.name
428
+ return match.name
429
+ }
430
+ }
431
+
432
+ // Fallback: primer modelo disponible (excluir embeddings)
433
+ const nonEmbedding = models.find((m) => !m.name.includes("nomic") && !m.name.includes("embed"))
434
+ if (nonEmbedding) {
435
+ _bestModelCache = nonEmbedding.name
436
+ return nonEmbedding.name
437
+ }
438
+
439
+ // Último recurso
440
+ _bestModelCache = models[0].name
441
+ return models[0].name
442
+ }