openprompt-lang 1.2.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 (79) hide show
  1. package/README.md +62 -8
  2. package/bin/cli.js +2 -0
  3. package/docs/00-ARCHITECTURE/OPL-BOOST-MULTI-AGENT.md +406 -0
  4. package/docs/02-STANDARDS/AGENTS.template.md +89 -0
  5. package/docs/02-STANDARDS/ticket-driven-development.md +99 -0
  6. package/docs/04-TICKETS/BOOST-001-profile-registry.md +66 -0
  7. package/docs/04-TICKETS/BOOST-002-context-compression.md +58 -0
  8. package/docs/04-TICKETS/BOOST-003-template-hydration.md +69 -0
  9. package/docs/04-TICKETS/BOOST-004-fewshot-engine.md +58 -0
  10. package/docs/04-TICKETS/BOOST-005-agent-pool.md +69 -0
  11. package/docs/04-TICKETS/BOOST-006-specialized-agents.md +53 -0
  12. package/docs/04-TICKETS/BOOST-007-validation-loop.md +56 -0
  13. package/docs/04-TICKETS/BOOST-008-orchestrator.md +71 -0
  14. package/docs/04-TICKETS/BOOST-009-cache-system.md +56 -0
  15. package/docs/04-TICKETS/BOOST-010-cli-mcp.md +67 -0
  16. package/docs/04-TICKETS/BOOST-011-self-learning.md +50 -0
  17. package/docs/04-TICKETS/BOOST-012-prompt-preamble.md +109 -0
  18. package/docs/04-TICKETS/BOOST-013-hydrator-duplicate-code.md +132 -0
  19. package/docs/04-TICKETS/BOOST-014-multiagent-missing-parts.md +87 -0
  20. package/docs/04-TICKETS/BOOST-015-skeleton-type-missing.md +76 -0
  21. package/docs/04-TICKETS/BOOST-016-output-path-duplicate.md +68 -0
  22. package/docs/04-TICKETS/INDEX.md +89 -0
  23. package/docs/04-TICKETS/_archive/BOOST-005-micro-tasking.md +67 -0
  24. package/docs/04-TICKETS/_archive/BOOST-006-validation-loop.md +66 -0
  25. package/docs/04-TICKETS/_archive/BOOST-007-progressive-pipeline.md +69 -0
  26. package/docs/04-TICKETS/_archive/BOOST-008-cli-mcp-integration.md +74 -0
  27. package/docs/AI_CONTEXT.md +16 -0
  28. package/docs/EMBEDDINGS.md +214 -0
  29. package/docs/ONBOARDING_WORKFLOW.md +151 -0
  30. package/docs/OPL_ACADEMIC_ISSUES.md +158 -0
  31. package/docs/WEB_SCRAPER_PLAN.md +454 -0
  32. package/package.json +9 -2
  33. package/scripts/postinstall.js +37 -0
  34. package/src/boost/agent-pool.js +442 -0
  35. package/src/boost/agents/index.js +79 -0
  36. package/src/boost/cache.js +241 -0
  37. package/src/boost/context-compressor.js +354 -0
  38. package/src/boost/fewshot-retriever.js +332 -0
  39. package/src/boost/hardware-detector.js +486 -0
  40. package/src/boost/hydrator.js +398 -0
  41. package/src/boost/index.js +60 -0
  42. package/src/boost/orchestrator.js +615 -0
  43. package/src/boost/preamble.js +217 -0
  44. package/src/boost/profile-registry.js +264 -0
  45. package/src/boost/self-learn.js +247 -0
  46. package/src/boost/skeletons/component.skeleton.js +24 -0
  47. package/src/boost/skeletons/hook.skeleton.js +27 -0
  48. package/src/boost/skeletons/index.js +67 -0
  49. package/src/boost/skeletons/page.skeleton.js +22 -0
  50. package/src/boost/skeletons/service.skeleton.js +20 -0
  51. package/src/boost/skeletons/store.skeleton.js +18 -0
  52. package/src/boost/skeletons/type.skeleton.js +11 -0
  53. package/src/boost/task-dispatcher.js +142 -0
  54. package/src/boost/validation-loop.js +495 -0
  55. package/src/cli/commands-boost.js +394 -0
  56. package/src/cli/commands-knowledge.js +1 -0
  57. package/src/cli/commands-opl.js +79 -1
  58. package/src/cli/commands-workflow.js +125 -6
  59. package/src/commands/init-core.js +169 -5
  60. package/src/commands/knowledge-ops.js +52 -0
  61. package/src/commands/opl-embeddings.js +556 -0
  62. package/src/commands/opl-help.js +26 -2
  63. package/src/commands/opl-search.js +106 -2
  64. package/src/commands/opl-webscrape.js +390 -0
  65. package/src/commands/workflow/epic-cli.js +192 -0
  66. package/src/commands/workflow/select.js +146 -0
  67. package/src/commands/workflow/sprint-cli.js +174 -0
  68. package/src/core/webscrape/analyzer.js +481 -0
  69. package/src/core/webscrape/deep-scraper.js +1027 -0
  70. package/src/core/workflow/epic-manager.js +845 -0
  71. package/src/core/workflow/gates.js +180 -1
  72. package/src/core/workflow/selector.js +707 -0
  73. package/src/embeddings/chunker.js +450 -0
  74. package/src/embeddings/embedder.js +431 -0
  75. package/src/embeddings/index-pipeline.js +320 -0
  76. package/src/embeddings/vector-store.js +505 -0
  77. package/src/mcp-refactor/handlers/boost.js +295 -0
  78. package/src/mcp-refactor/router.js +19 -0
  79. package/src/mcp-refactor/tools.js +113 -0
@@ -0,0 +1,486 @@
1
+ // @use(kind, contract, limit)
2
+ // @kind(util)
3
+ // @contract(in: none -> out: detectHardware, getRuntimeStatus, getSafeParallelism, getRAMWarning, generateBoostConfig, saveBoostConfig, getSetupMessage)
4
+ // @limit(lines: 350)
5
+
6
+ /**
7
+ * Hardware Detector — Módulo OPL Boost
8
+ *
9
+ * Detecta las capacidades del PC del usuario para recomendar
10
+ * la configuración óptima de Boost: perfil, paralelismo, modelo.
11
+ *
12
+ * Se ejecuta en:
13
+ * - Primera instalación (postinstall / opl boost setup)
14
+ * - Bajo demanda (opl boost setup --re-detect)
15
+ *
16
+ * La configuración se guarda en prompt-lang.json > boost.hardware
17
+ */
18
+
19
+ import os from "os"
20
+ import { execSync } from "child_process"
21
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs"
22
+ import { join } from "path"
23
+ import { PROFILES } from "./profile-registry.js"
24
+
25
+ // ──────────────────────────────────────────────
26
+ // Detección de hardware
27
+ // ──────────────────────────────────────────────
28
+
29
+ function bytesToGB(bytes) {
30
+ return Math.round(bytes / (1024 * 1024 * 1024))
31
+ }
32
+
33
+ function detectRAM() {
34
+ return {
35
+ totalBytes: os.totalmem(),
36
+ totalGB: bytesToGB(os.totalmem()),
37
+ freeBytes: os.freemem(),
38
+ freeGB: bytesToGB(os.freemem()),
39
+ }
40
+ }
41
+
42
+ function detectCPU() {
43
+ const cpus = os.cpus()
44
+ return {
45
+ cores: cpus.length,
46
+ model: cpus[0]?.model?.trim() || "Desconocido",
47
+ arch: process.arch,
48
+ platform: process.platform,
49
+ }
50
+ }
51
+
52
+ function detectGPU() {
53
+ try {
54
+ if (process.platform === "win32") {
55
+ const output = execSync(
56
+ 'wmic path win32_VideoController get name,adapterram 2>nul',
57
+ { encoding: "utf-8", timeout: 5000 }
58
+ )
59
+ const lines = output.split("\n").filter((l) => l.trim() && !l.includes("Name"))
60
+ if (lines.length > 0) {
61
+ const parts = lines[0].trim().split(/\s{2,}/)
62
+ return {
63
+ name: parts[0] || "GPU detectada",
64
+ vramBytes: parts[1] ? parseInt(parts[1]) : null,
65
+ vramGB: parts[1] ? bytesToGB(parseInt(parts[1])) : null,
66
+ }
67
+ }
68
+ } else {
69
+ // Linux / macOS — nvidia-smi
70
+ const output = execSync(
71
+ 'nvidia-smi --query-gpu=name,memory.total --format=csv,noheader 2>/dev/null || echo ""',
72
+ { encoding: "utf-8", timeout: 5000 }
73
+ )
74
+ const line = output.trim()
75
+ if (line) {
76
+ const [name, memStr] = line.split(", ")
77
+ const vramMB = memStr ? parseInt(memStr.replace(" MiB", "").trim()) : null
78
+ return {
79
+ name: name?.trim() || "NVIDIA GPU detectada",
80
+ vramBytes: vramMB ? vramMB * 1024 * 1024 : null,
81
+ vramGB: vramMB ? Math.round(vramMB / 1024) : null,
82
+ }
83
+ }
84
+
85
+ // Intel ARC / AMD — intentar con glxinfo
86
+ const glxOutput = execSync(
87
+ 'glxinfo -B 2>/dev/null | grep "Device:" || echo ""',
88
+ { encoding: "utf-8", timeout: 5000 }
89
+ )
90
+ if (glxOutput.trim()) {
91
+ return { name: glxOutput.trim().replace("Device:", "").trim(), vramBytes: null, vramGB: null }
92
+ }
93
+ }
94
+ } catch {
95
+ // No GPU detectada o no hay drivers
96
+ }
97
+ return null
98
+ }
99
+
100
+ function detectOllamaStatus() {
101
+ try {
102
+ const output = execSync(
103
+ 'curl -s http://localhost:11434/api/tags 2>/dev/null || echo ""',
104
+ { encoding: "utf-8", timeout: 3000 }
105
+ )
106
+ if (output) {
107
+ const data = JSON.parse(output)
108
+ const models = (data.models || []).map((m) => ({
109
+ name: m.name,
110
+ sizeGB: Math.round(m.size / (1024 * 1024 * 1024) * 10) / 10,
111
+ }))
112
+ return { installed: true, models }
113
+ }
114
+ } catch {
115
+ // Ollama no está corriendo
116
+ }
117
+ return { installed: false, models: [] }
118
+ }
119
+
120
+ // ──────────────────────────────────────────────
121
+ // Recomendación de configuración
122
+ // ──────────────────────────────────────────────
123
+
124
+ function recommendProfile(ram, cpu, gpu) {
125
+ const ramGB = ram.totalGB
126
+ const cores = cpu.cores
127
+ const hasGPU = gpu !== null
128
+ const vramGB = gpu?.vramGB || 0
129
+
130
+ // Reglas de recomendación basadas en hardware real
131
+ if (ramGB >= 32 && cores >= 8 && hasGPU && vramGB >= 8) {
132
+ return { profile: "large", reason: "PC de alta capacidad: RAM ≥32GB, GPU ≥8GB VRAM, ≥8 núcleos" }
133
+ }
134
+ if (ramGB >= 16 && cores >= 6) {
135
+ return { profile: "medium", reason: "PC de capacidad media: RAM ≥16GB, ≥6 núcleos" }
136
+ }
137
+ if (ramGB >= 8 && cores >= 4) {
138
+ return { profile: "medium", reason: "PC estándar: RAM ≥8GB, ≥4 núcleos. Se usará modo cola para no saturar." }
139
+ }
140
+ return { profile: "small", reason: "PC con recursos limitados: se usará el modelo más pequeño y modo cola" }
141
+ }
142
+
143
+ function recommendParallelism(ram, gpu, profile) {
144
+ const ramGB = ram.totalGB
145
+ const hasGPU = gpu !== null
146
+ const vramGB = gpu?.vramGB || 0
147
+
148
+ if (profile === "large" && hasGPU && vramGB >= 12) {
149
+ return { mode: "parallel", maxConcurrency: 3, reason: "GPU con suficiente VRAM para 3 instancias" }
150
+ }
151
+ if (profile === "large" && hasGPU) {
152
+ return { mode: "parallel", maxConcurrency: 2, reason: "GPU disponible, 2 instancias en paralelo" }
153
+ }
154
+ if (ramGB >= 32 && hasGPU) {
155
+ return { mode: "parallel", maxConcurrency: 2, reason: "Suficiente RAM y GPU para 2 instancias" }
156
+ }
157
+ if (ramGB >= 16) {
158
+ return { mode: "queue", maxConcurrency: 1, reason: "RAM suficiente para cola, no para paralelo real" }
159
+ }
160
+ return { mode: "queue", maxConcurrency: 1, reason: "Modo cola: un agente a la vez para no saturar el sistema" }
161
+ }
162
+
163
+ function recommendModel(gpu, ram, profile) {
164
+ const hasGPU = gpu !== null
165
+ const vramGB = gpu?.vramGB || 0
166
+
167
+ if (profile === "large" && hasGPU && vramGB >= 16) {
168
+ return "qwen2.5-coder:7b" // o 14b si está disponible
169
+ }
170
+ if (profile === "medium" && hasGPU && vramGB >= 6) {
171
+ return "qwen2.5-coder:7b"
172
+ }
173
+ if (profile === "medium" && ram.totalGB >= 16) {
174
+ return "qwen2.5-coder:7b" // CPU con suficiente RAM
175
+ }
176
+ return "llama3.2:latest" // Modelo pequeño para CPU o RAM limitada
177
+ }
178
+
179
+ // ──────────────────────────────────────────────
180
+ // API pública
181
+ // ──────────────────────────────────────────────
182
+
183
+ export function detectHardware() {
184
+ const ram = detectRAM()
185
+ const cpu = detectCPU()
186
+ const gpu = detectGPU()
187
+ const ollama = detectOllamaStatus()
188
+ const profileRec = recommendProfile(ram, cpu, gpu)
189
+ const parallelRec = recommendParallelism(ram, gpu, profileRec.profile)
190
+ const modelRec = recommendModel(gpu, ram, profileRec.profile)
191
+
192
+ return {
193
+ timestamp: new Date().toISOString(),
194
+ os: `${process.platform} ${process.arch}`,
195
+ ram,
196
+ cpu,
197
+ gpu,
198
+ ollama,
199
+ recommendation: {
200
+ profile: profileRec.profile,
201
+ profileReason: profileRec.reason,
202
+ agentMode: parallelRec.mode,
203
+ maxConcurrency: parallelRec.maxConcurrency,
204
+ concurrencyReason: parallelRec.reason,
205
+ recommendedModel: modelRec,
206
+ },
207
+ }
208
+ }
209
+
210
+ export function generateBoostConfig(hardware) {
211
+ const { recommendation, ollama } = hardware
212
+
213
+ return {
214
+ enabled: true,
215
+ defaultProfile: "auto",
216
+ hardware: {
217
+ totalRamGB: hardware.ram.totalGB,
218
+ cpuCores: hardware.cpu.cores,
219
+ gpuName: hardware.gpu?.name || null,
220
+ gpuVramGB: hardware.gpu?.vramGB || null,
221
+ hasOllama: ollama.installed,
222
+ ollamaModels: ollama.models.map((m) => m.name),
223
+ detectedAt: hardware.timestamp,
224
+ },
225
+ profiles: {
226
+ small: PROFILES.small,
227
+ medium: PROFILES.medium,
228
+ large: PROFILES.large,
229
+ },
230
+ agentPool: {
231
+ mode: recommendation.agentMode,
232
+ maxConcurrency: recommendation.maxConcurrency,
233
+ modelName: recommendation.recommendedModel,
234
+ timeout: 30000,
235
+ },
236
+ }
237
+ }
238
+
239
+ export function saveBoostConfig(boostConfig) {
240
+ const configPath = join(process.cwd(), "prompt-lang.json")
241
+
242
+ let config
243
+ if (existsSync(configPath)) {
244
+ config = JSON.parse(readFileSync(configPath, "utf-8"))
245
+ } else {
246
+ config = {}
247
+ }
248
+
249
+ config.boost = boostConfig
250
+ writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8")
251
+ return configPath
252
+ }
253
+
254
+ export function getSetupMessage(hardware) {
255
+ const { ram, cpu, gpu, ollama, recommendation } = hardware
256
+ const lines = []
257
+
258
+ lines.push("")
259
+ lines.push("╔══════════════════════════════════════════════════════════╗")
260
+ lines.push("║ 🚀 OPL Boost — Setup de Hardware ║")
261
+ lines.push("╚══════════════════════════════════════════════════════════╝")
262
+ lines.push("")
263
+
264
+ lines.push("📊 Hardware detectado:")
265
+ lines.push(` RAM: ${ram.totalGB} GB (${ram.freeGB} GB libres)`)
266
+ lines.push(` CPU: ${cpu.cores} núcleos — ${cpu.model}`)
267
+ lines.push(` GPU: ${gpu ? `${gpu.name} (${gpu.vramGB ? gpu.vramGB + " GB VRAM" : "desconocida"})` : "No detectada"}`)
268
+
269
+ if (ollama.installed) {
270
+ lines.push(` 🟢 Ollama: INSTALADO (${ollama.models.length} modelos disponibles)`)
271
+ for (const m of ollama.models) {
272
+ lines.push(` • ${m.name} (${m.sizeGB} GB)`)
273
+ }
274
+ } else {
275
+ lines.push(` 🔴 Ollama: NO DETECTADO`)
276
+ }
277
+
278
+ lines.push("")
279
+ lines.push("💡 ¿Por qué necesitamos Ollima?")
280
+ lines.push(" OPL Boost usa modelos locales de IA para generar código.")
281
+ lines.push(" En lugar de gastar tokens de un modelo grande (GPT-4, Claude),")
282
+ lines.push(" Boost reparte el trabajo entre modelos pequeños (7B) que corren")
283
+ lines.push(" en tu PC. Es más lento pero MUCHO más barato, y te permite")
284
+ lines.push(" trabajar sin conexión ni límites de API.")
285
+ lines.push("")
286
+
287
+ lines.push("📋 Configuración recomendada para tu PC:")
288
+ lines.push(` Perfil: ${recommendation.profile}`)
289
+ lines.push(` Modo agentes: ${recommendation.agentMode}`)
290
+ lines.push(` Concurrencia: ${recommendation.maxConcurrency} agente(s) simultáneo(s)`)
291
+ lines.push(` Modelo: ${recommendation.recommendedModel}`)
292
+ lines.push(` Razón: ${recommendation.profileReason}`)
293
+ lines.push("")
294
+
295
+ if (!ollama.installed) {
296
+ lines.push("⚠️ IMPORTANTE: Ollama NO está instalado.")
297
+ lines.push(" Boost necesita Ollama para funcionar. Instálalo con:")
298
+ lines.push("")
299
+ lines.push(" curl -fsSL https://ollama.com/install.sh | sh")
300
+ lines.push("")
301
+ lines.push(" Luego descarga un modelo:")
302
+ lines.push(` ollama pull ${recommendation.recommendedModel}`)
303
+ lines.push("")
304
+ lines.push(" Después, ejecuta: opl boost setup")
305
+ lines.push("")
306
+ }
307
+
308
+ lines.push("✅ Configuración guardada en prompt-lang.json")
309
+ lines.push(" Puedes re-ejecutar: opl boost setup --re-detect")
310
+ lines.push("")
311
+
312
+ return lines.join("\n")
313
+ }
314
+
315
+ // ──────────────────────────────────────────────
316
+ // Monitoreo en tiempo real
317
+ // ──────────────────────────────────────────────
318
+
319
+ /**
320
+ * Estado actual del sistema en tiempo real.
321
+ * A diferencia de detectHardware() (que es estático), esto se llama
322
+ * en cada solicitud de agente para decidir si ejecutar ahora o esperar.
323
+ */
324
+ // Modelos de Ollama en RAM estimado (valores típicos)
325
+ const MODEL_RAM_ESTIMATE = {
326
+ "llama3.2:latest": 2.5, // GB
327
+ "qwen2.5-coder:7b": 5.5, // GB
328
+ "llama3:latest": 6.0, // GB
329
+ "deepseek-coder:7b": 5.0, // GB
330
+ "default": 4.0, // GB estimado genérico
331
+ }
332
+
333
+ export function getRuntimeStatus() {
334
+ const freeBytes = os.freemem()
335
+ const totalBytes = os.totalmem()
336
+ const freeGB = bytesToGB(freeBytes)
337
+ const totalGB = bytesToGB(totalBytes)
338
+ const usedGB = totalGB - freeGB
339
+ const usagePercent = Math.round((1 - freeBytes / totalBytes) * 100)
340
+
341
+ // Carga del sistema
342
+ const loadAvg = os.loadavg ? os.loadavg() : [0, 0, 0]
343
+ const cpuCount = os.cpus().length
344
+
345
+ return {
346
+ timestamp: new Date().toISOString(),
347
+ ram: {
348
+ totalGB,
349
+ freeGB,
350
+ usedGB,
351
+ usagePercent,
352
+ isCritical: freeGB < 2,
353
+ isLow: freeGB < 4,
354
+ isHealthy: freeGB >= 6,
355
+ },
356
+ cpu: {
357
+ cores: cpuCount,
358
+ loadAvg1m: loadAvg[0]?.toFixed(1) || "0.0",
359
+ loadAvg5m: loadAvg[1]?.toFixed(1) || "0.0",
360
+ isLoaded: cpuCount > 0 && loadAvg[0] > cpuCount * 0.8,
361
+ },
362
+ safeToRunAgent: freeGB >= 4,
363
+ warning: null,
364
+ }
365
+ }
366
+
367
+ /**
368
+ * Determina cuántos agentes pueden ejecutarse AHORA sin saturar el PC.
369
+ * Considera RAM libre actual + carga actual de CPU.
370
+ *
371
+ * @param {object} hardwareConfig - Config de hardware guardada (opcional)
372
+ * @returns {{ safe: boolean, maxAgents: number, mode: string, reason: string }}
373
+ */
374
+ export function getSafeParallelism(hardwareConfig) {
375
+ const runtime = getRuntimeStatus()
376
+
377
+ // Reglas basadas en RAM libre ACTUAL
378
+ if (runtime.ram.isCritical) {
379
+ return {
380
+ safe: false,
381
+ maxAgents: 0,
382
+ mode: "blocked",
383
+ reason: `RAM crítica: ${runtime.ram.freeGB}GB libres. Libera memoria antes de ejecutar agentes.`,
384
+ }
385
+ }
386
+
387
+ if (runtime.ram.isLow) {
388
+ return {
389
+ safe: true,
390
+ maxAgents: 1,
391
+ mode: "queue",
392
+ reason: `RAM baja: ${runtime.ram.freeGB}GB libres. Solo 1 agente en cola.`,
393
+ }
394
+ }
395
+
396
+ if (runtime.cpu.isLoaded) {
397
+ return {
398
+ safe: true,
399
+ maxAgents: 1,
400
+ mode: "queue",
401
+ reason: `CPU cargada (load: ${runtime.cpu.loadAvg1m}). Reduciendo a 1 agente.`,
402
+ }
403
+ }
404
+
405
+ // RAM saludable — usar configuración guardada
406
+ const configuredMax = hardwareConfig?.agentPool?.maxConcurrency || 2
407
+
408
+ if (runtime.ram.freeGB >= 16 && configuredMax >= 2) {
409
+ return {
410
+ safe: true,
411
+ maxAgents: Math.min(configuredMax, 3),
412
+ mode: "parallel",
413
+ reason: `RAM saludable: ${runtime.ram.freeGB}GB libres. Hasta ${Math.min(configuredMax, 3)} agentes en paralelo.`,
414
+ }
415
+ }
416
+
417
+ if (runtime.ram.freeGB >= 8) {
418
+ return {
419
+ safe: true,
420
+ maxAgents: Math.min(configuredMax, 2),
421
+ mode: "parallel",
422
+ reason: `RAM suficiente: ${runtime.ram.freeGB}GB libres. Hasta ${Math.min(configuredMax, 2)} agentes.`,
423
+ }
424
+ }
425
+
426
+ return {
427
+ safe: true,
428
+ maxAgents: 1,
429
+ mode: "queue",
430
+ reason: `RAM moderada: ${runtime.ram.freeGB}GB libres. 1 agente por vez.`,
431
+ }
432
+ }
433
+
434
+ /**
435
+ * Genera una advertencia si el sistema está muy cargado.
436
+ * @returns {string|null} Mensaje de advertencia o null si todo bien
437
+ */
438
+ export function getRAMWarning() {
439
+ const runtime = getRuntimeStatus()
440
+
441
+ if (runtime.ram.isCritical) {
442
+ return (
443
+ `⚠️ RAM CRÍTICA: Solo ${runtime.ram.freeGB}GB libres de ${runtime.ram.totalGB}GB (${runtime.ram.usagePercent}% usado).\n` +
444
+ ` Los agentes Boost NO se ejecutarán hasta que liberes memoria.\n` +
445
+ ` → Cierra navegadores, Docker, o programas pesados.\n` +
446
+ ` → Luego ejecuta: opl boost check`
447
+ )
448
+ }
449
+
450
+ if (runtime.ram.isLow && runtime.cpu.isLoaded) {
451
+ return (
452
+ `⚠️ RAM baja (${runtime.ram.freeGB}GB libres) y CPU cargada (load: ${runtime.cpu.loadAvg1m}).\n` +
453
+ ` Los agentes se ejecutarán en MODO COLA (1 por vez) para no saturar.\n` +
454
+ ` Será más lento, pero tu PC se mantendrá usable.`
455
+ )
456
+ }
457
+
458
+ if (runtime.ram.isLow) {
459
+ return (
460
+ `ℹ️ RAM baja: ${runtime.ram.freeGB}GB libres de ${runtime.ram.totalGB}GB.\n` +
461
+ ` Modo cola activado. Si notas lentitud, cierra otras aplicaciones.`
462
+ )
463
+ }
464
+
465
+ if (runtime.cpu.isLoaded) {
466
+ return (
467
+ `ℹ️ CPU cargada (load: ${runtime.cpu.loadAvg1m}/${runtime.cpu.cores} núcleos).\n` +
468
+ ` Los agentes se ralentizarán automáticamente.`
469
+ )
470
+ }
471
+
472
+ return null
473
+ }
474
+
475
+ /**
476
+ * Calcula cuánta RAM消耗rá un modelo específico en Ollama.
477
+ * @param {string} modelName - Nombre del modelo
478
+ * @returns {number} RAM estimada en GB
479
+ */
480
+ export function estimateModelRAM(modelName) {
481
+ const name = modelName?.toLowerCase() || ""
482
+ for (const [key, gb] of Object.entries(MODEL_RAM_ESTIMATE)) {
483
+ if (name.includes(key.replace(":latest", ""))) return gb
484
+ }
485
+ return MODEL_RAM_ESTIMATE.default
486
+ }