pi-antiloop 1.4.0 → 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 (3) hide show
  1. package/README.md +27 -27
  2. package/package.json +1 -1
  3. package/src/commands.ts +80 -80
package/README.md CHANGED
@@ -106,35 +106,35 @@ Recent detections:
106
106
 
107
107
  ### `/antiloop config`
108
108
 
109
- Menú interactivo agrupado, con el valor actual en cada opción:
109
+ Grouped interactive menu showing the current value in each option:
110
110
 
111
111
  **🎛️ General**
112
- - **🟢/🔴 activar/desactivar** — enciende o apaga la detección
113
- - **⏳ ventana** — `5 / 10 / 15 / 20` — cuántos mensajes recientes se analizan (por defecto 10)
114
- - **🔔 avisos** — on/off — avisa cuando detecta un bucle
115
- - **📊 pie interactivo** — on/off — experimental: reemplaza el pie de pantalla de pi y captura el teclado para el atajo (si da problemas de rendimiento, déjalo en off)
116
- - **⌨️ atajo** — `esc+a` o desactivadopulsación para activar/apagar sin teclear nada
117
-
118
- **🎯 Detección**
119
- - **⚠️ umbral de aviso** — `1 / 2 / 3 / 5` — repeticiones antes de avisar (por defecto 2)
120
- - **🛑 umbral de corte** — `2 / 3 / 5 / 8` — repeticiones antes de forzar un cambio de rumbo (por defecto 3)
121
- - **🚨 umbral de aborto** — `desactivado / 5 / 8 / 10 / 15` — repeticiones antes de abortar (0 = desactivado)
122
- - **📏 parecido mínimo** — `50 / 60 / 70 / 75 / 80 / 90%` — qué tan parecidos deben ser dos mensajes para contar como bucle (por defecto 75%)
123
- - **🔧 parecido de llamadas** — `99 / 95 / 90 / 80%` — qué tan idénticas deben ser las llamadas para contar como la misma (por defecto 95%: solo repeticiones casi idénticas son bucle)
124
- - **🔁 repeticiones de llamada** — `1 / 2 / 3` — cuántas veces se repite la misma llamada antes de marcarla (por defecto 2)
125
- - **🧾 parecido de resultados** — `95 / 80 / 60%` — qué tan parecidos deben ser los resultados para contar como el *mismo resultado*; si un comando repetido empieza a dar otro resultado, es progreso y no bucle (por defecto 80%)
126
-
127
- **📋 Lotes de tareas** — el trabajo en lote (punched_log, plan_manager, …) es N tareas de un tipo, no un bucle
128
- - **📋 lotes de tareas** — on/off — reconoce ese trabajo en lote y se queda en silencio
129
- - **📋 llamadas mínimas** — `2 / 3 / 4 / 5` — llamadas de la misma herramienta antes de reconocer un lote (por defecto 3)
130
- - **📋 gemelos** — `99 / 95 / 90%` — llamadas más parecidas que esto cuentan como la *misma tarea* repetida; un gemelo invalida el lote y vuelve a la detección normal (por defecto 99%)
131
-
132
- **🔍 Detectores**
133
- - **📝 texto** — on/off — detecta mensajes de texto repetidos
134
- - **🔧 herramientas** — on/off — detecta llamadas a herramientas repetidas
135
- - **🧠 pensamiento** — on/off — detecta bloques de razonamiento repetidos
136
-
137
- **🧹 reiniciar estado** — borra contadores e historial
112
+ - **🟢/🔴 enable/disable** — turn detection on or off
113
+ - **⏳ window** — `5 / 10 / 15 / 20` — how many recent messages are analyzed (default 10)
114
+ - **🔔 notifications** — on/off — show a warning when a loop is detected
115
+ - **📊 interactive footer** — on/off — experimental: replaces pi's footer and captures keystrokes for the toggle shortcut (leave it off if it misbehaves)
116
+ - **⌨️ shortcut** — `esc+a` or offkey press to toggle without typing a command
117
+
118
+ **🎯 Detection**
119
+ - **⚠️ warn threshold** — `1 / 2 / 3 / 5` — repetitions before antiloop warns you (default 2)
120
+ - **🛑 force break threshold** — `2 / 3 / 5 / 8` — repetitions before forcing a change of approach (default 3)
121
+ - **🚨 abort threshold** — `off / 5 / 8 / 10 / 15` — repetitions before aborting (0 = disabled)
122
+ - **📏 text similarity** — `50 / 60 / 70 / 75 / 80 / 90%` — how similar two messages must be to count as a loop (default 75%)
123
+ - **🔧 call similarity** — `99 / 95 / 90 / 80%` — how identical tool-call *arguments* must be to count as the same call (default 95%: only near-identical repeats loop)
124
+ - **🔁 call repeats** — `1 / 2 / 3` — how many times the same call must repeat before it flags (default 2)
125
+ - **🧾 result similarity** — `95 / 80 / 60%` — how similar captured results must be to count as the *same outcome*; a repeated command that starts producing a different result is progress, not a loop (default 80%)
126
+
127
+ **📋 Task streams** — batch work (punched_log, plan_manager, …) is N tasks of one type, not a loop
128
+ - **📋 task streams** — on/off — recognize that batch work and stay silent
129
+ - **📋 stream min calls** — `2 / 3 / 4 / 5` — same-tool calls required before a batch is recognized (default 3)
130
+ - **📋 twin threshold** — `99 / 95 / 90%` — calls more similar than this count as the *same task* repeated; one twin invalidates the batch and normal detection resumes (default 99%)
131
+
132
+ **🔍 Detectors**
133
+ - **📝 text** — on/off — detect repeated text messages
134
+ - **🔧 tools** — on/off — detect repeated tool calls
135
+ - **🧠 thinking** — on/off — detect repeated internal reasoning
136
+
137
+ **🧹 reset state** — clear all counters and history
138
138
 
139
139
  ### `/antiloop log`
140
140
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-antiloop",
3
- "version": "1.4.0",
3
+ "version": "1.4.1",
4
4
  "description": "Antiloop: detect reasoning loops and force a break (warn \u2192 force \u2192 abort) across text, tool, thinking, and structural patterns. Tool-loop detection is result-aware: only near-identical repeated calls with the same outcome count, so sequential bash operations and retries that make progress don't false-positive. Task-stream recognition: when an extension (punched, plan, \u2026) makes the model call the SAME tool many times with DIFFERENT content \u2014 N distinct tasks of one type, e.g. appending lines or adding plan tasks \u2014 antiloop stays silent.",
5
5
  "keywords": [
6
6
  "pi-package",
package/src/commands.ts CHANGED
@@ -78,29 +78,29 @@ async function showConfigMenu(ctx: ExtensionCommandContext, rt: Runtime): Promis
78
78
  const c = rt.config;
79
79
  const picked = await selectFrom(ctx, "⚙️ antiloop config", [
80
80
  // ── 🎛️ General ──────────────────────────────────────────────
81
- { value: "toggle" as const, label: c.enabled ? "🟢 desactivar" : "🔴 activar", description: "enciende o apaga la detección" },
82
- { value: "window" as const, label: `⏳ ventana: ${c.detectionWindow} msgs`, description: "cuántos mensajes recientes se analizan" },
83
- { value: "notify" as const, label: `🔔 avisos: ${yn(c.notifyOnDetection)}`, description: "avisa cuando detecta un bucle" },
84
- { value: "footer" as const, label: `📊 pie interactivo: ${yn(c.interactiveFooter)}`, description: "experimental: reemplaza el pie de pi y captura el teclado (si da problemas, déjalo en off)" },
85
- { value: "shortcut" as const, label: `⌨️ atajo: ${c.toggleShortcut}`, description: "esc+a o desactivado" },
86
- // ── 🎯 Detección ────────────────────────────────────────────
87
- { value: "warn" as const, label: `⚠️ umbral aviso: ${c.warningThreshold}`, description: "repeticiones antes de avisar" },
88
- { value: "force" as const, label: `🛑 umbral corte: ${c.forceBreakThreshold}`, description: "repeticiones antes de forzar un cambio de rumbo" },
89
- { value: "abort" as const, label: `🚨 umbral abortar: ${c.abortThreshold || "off"}`, description: "repeticiones antes de abortar (0 = desactivado)" },
90
- { value: "sim" as const, label: `📏 parecido mínimo: ${(c.similarityThreshold * 100).toFixed(0)}%`, description: "qué tan parecidos deben ser dos mensajes para contar como bucle" },
91
- { value: "toolSim" as const, label: `🔧 parecido de llamadas: ${(c.toolSimilarityThreshold * 100).toFixed(0)}%`, description: "qué tan idénticas deben ser las llamadas para contar como la misma" },
92
- { value: "toolRepeat" as const, label: `🔁 repeticiones de llamada: ${c.minToolRepeatCount}+`, description: "cuántas veces se repite la misma llamada antes de marcarla" },
93
- { value: "resultSim" as const, label: `🧾 parecido de resultados: ${(c.resultSimilarityThreshold * 100).toFixed(0)}%`, description: "mismo comando + resultado distinto = progreso, no bucle" },
81
+ { value: "toggle" as const, label: c.enabled ? "🟢 disable" : "🔴 enable", description: "turn detection on or off" },
82
+ { value: "window" as const, label: `⏳ window: ${c.detectionWindow} msgs`, description: "how many recent messages are analyzed" },
83
+ { value: "notify" as const, label: `🔔 notifications: ${yn(c.notifyOnDetection)}`, description: "show a warning when a loop is detected" },
84
+ { value: "footer" as const, label: `📊 interactive footer: ${yn(c.interactiveFooter)}`, description: "experimental: replaces pi's footer and captures keystrokes (leave off if it misbehaves)" },
85
+ { value: "shortcut" as const, label: `⌨️ shortcut: ${c.toggleShortcut}`, description: "keys to toggle on/off without typing a command" },
86
+ // ── 🎯 Detection ────────────────────────────────────────────
87
+ { value: "warn" as const, label: `⚠️ warn after: ${c.warningThreshold}`, description: "repetitions before antiloop warns you" },
88
+ { value: "force" as const, label: `🛑 force break after: ${c.forceBreakThreshold}`, description: "repetitions before forcing a change of approach" },
89
+ { value: "abort" as const, label: `🚨 abort after: ${c.abortThreshold || "off"}`, description: "repetitions before aborting (0 = disabled)" },
90
+ { value: "sim" as const, label: `📏 text similarity: ${(c.similarityThreshold * 100).toFixed(0)}%`, description: "how similar two messages must be to count as a loop" },
91
+ { value: "toolSim" as const, label: `🔧 call similarity: ${(c.toolSimilarityThreshold * 100).toFixed(0)}%`, description: "how identical tool calls must be to count as the same call" },
92
+ { value: "toolRepeat" as const, label: `🔁 call repeats: ${c.minToolRepeatCount}+`, description: "how many times the same call must repeat before it flags" },
93
+ { value: "resultSim" as const, label: `🧾 result similarity: ${(c.resultSimilarityThreshold * 100).toFixed(0)}%`, description: "same command + different result = progress, not a loop" },
94
94
  // ── 📋 Task streams ─────────────────────────────────────────
95
- { value: "streams" as const, label: `📋 lotes de tareas: ${yn(c.detectTaskStreams)}`, description: "trabajo en lote (punched_log / plan_manager / …) no es un bucle" },
96
- { value: "streamMin" as const, label: `📋 llamadas mínimas: ${c.taskStreamMinCalls}`, description: "llamadas de la misma herramienta antes de reconocer un lote" },
97
- { value: "streamTwin" as const, label: `📋 gemelos: ${(c.taskStreamTwinThreshold * 100).toFixed(0)}%`, description: "llamadas más parecidas que esto = la misma tarea repetida, no un lote" },
98
- // ── 🔍 Detectores ───────────────────────────────────────────
99
- { value: "text" as const, label: `📝 texto: ${yn(c.detectTextLoops)}`, description: "detecta mensajes de texto repetidos" },
100
- { value: "tool" as const, label: `🔧 herramientas: ${yn(c.detectToolLoops)}`, description: "detecta llamadas repetidas a herramientas" },
101
- { value: "think" as const, label: `🧠 pensamiento: ${yn(c.detectThinkingLoops)}`, description: "detecta razonamiento interno repetido" },
95
+ { value: "streams" as const, label: `📋 task streams: ${yn(c.detectTaskStreams)}`, description: "batch work (punched_log / plan_manager / …) is not a loop" },
96
+ { value: "streamMin" as const, label: `📋 stream min calls: ${c.taskStreamMinCalls}`, description: "calls of the same tool before a batch is recognized" },
97
+ { value: "streamTwin" as const, label: `📋 twin threshold: ${(c.taskStreamTwinThreshold * 100).toFixed(0)}%`, description: "calls more similar than this = the same task repeated, not a batch" },
98
+ // ── 🔍 Detectors ────────────────────────────────────────────
99
+ { value: "text" as const, label: `📝 text: ${yn(c.detectTextLoops)}`, description: "detect repeated text messages" },
100
+ { value: "tool" as const, label: `🔧 tools: ${yn(c.detectToolLoops)}`, description: "detect repeated tool calls" },
101
+ { value: "think" as const, label: `🧠 thinking: ${yn(c.detectThinkingLoops)}`, description: "detect repeated internal reasoning" },
102
102
  // ── 🧹 ──────────────────────────────────────────────────────
103
- { value: "reset" as const, label: "🧹 reiniciar estado", description: "borra contadores e historial" },
103
+ { value: "reset" as const, label: "🧹 reset state", description: "clear counters and history" },
104
104
  ]);
105
105
  if (!picked) return;
106
106
  switch (picked) {
@@ -111,138 +111,138 @@ async function showConfigMenu(ctx: ExtensionCommandContext, rt: Runtime): Promis
111
111
  rt.updateStatus(ctx);
112
112
  break;
113
113
  case "window": {
114
- const v = await selectFrom(ctx, "⏳ ventana (mensajes a analizar)", [
114
+ const v = await selectFrom(ctx, "⏳ window (messages to analyze)", [
115
115
  { value: 5, label: "5" },
116
- { value: 10, label: "10 (por defecto)" },
116
+ { value: 10, label: "🎯 10 (default)" },
117
117
  { value: 15, label: "15" },
118
118
  { value: 20, label: "20" },
119
119
  ]);
120
- if (v !== undefined) { c.detectionWindow = v; saveConfig(c); ctx.ui.notify(`ventana: ${v}`, "info"); }
120
+ if (v !== undefined) { c.detectionWindow = v; saveConfig(c); ctx.ui.notify(`window: ${v}`, "info"); }
121
121
  break;
122
122
  }
123
123
  case "notify":
124
124
  c.notifyOnDetection = !c.notifyOnDetection; saveConfig(c);
125
- ctx.ui.notify(`avisos: ${yn(c.notifyOnDetection)}`, "info"); break;
125
+ ctx.ui.notify(`notifications: ${yn(c.notifyOnDetection)}`, "info"); break;
126
126
  case "footer":
127
127
  c.interactiveFooter = !c.interactiveFooter; saveConfig(c);
128
- ctx.ui.notify(`pie interactivo: ${yn(c.interactiveFooter)}`, "info");
128
+ ctx.ui.notify(`interactive footer: ${yn(c.interactiveFooter)}`, "info");
129
129
  rt.refreshFooter?.(ctx);
130
130
  rt.updateStatus(ctx);
131
131
  break;
132
132
  case "shortcut": {
133
- const v = await selectFrom(ctx, "⌨️ atajo para activar/apagar", [
134
- { value: "esc+a" as const, label: "⌨️ esc+a (por defecto)", description: "pulsa ESC y luego a" },
135
- { value: "off" as const, label: "🚫 desactivado" },
133
+ const v = await selectFrom(ctx, "⌨️ toggle shortcut", [
134
+ { value: "esc+a" as const, label: "⌨️ esc+a (default)", description: "press ESC then a" },
135
+ { value: "off" as const, label: "🚫 off" },
136
136
  ]);
137
- if (v !== undefined) { c.toggleShortcut = v; saveConfig(c); rt.refreshFooter?.(ctx); ctx.ui.notify(`atajo: ${v}`, "info"); }
137
+ if (v !== undefined) { c.toggleShortcut = v; saveConfig(c); rt.refreshFooter?.(ctx); ctx.ui.notify(`shortcut: ${v}`, "info"); }
138
138
  break;
139
139
  }
140
140
  case "warn": {
141
- const v = await selectFrom(ctx, "⚠️ umbral de aviso", [
142
- { value: 1, label: "⚡ 1 (sensible)" },
143
- { value: 2, label: "🎯 2 (por defecto)" },
141
+ const v = await selectFrom(ctx, "⚠️ warn threshold", [
142
+ { value: 1, label: "⚡ 1 (sensitive)" },
143
+ { value: 2, label: "🎯 2 (default)" },
144
144
  { value: 3, label: "3" },
145
- { value: 5, label: "🐢 5 (relajado)" },
145
+ { value: 5, label: "🐢 5 (relaxed)" },
146
146
  ]);
147
- if (v !== undefined) { c.warningThreshold = v; saveConfig(c); ctx.ui.notify(`aviso: ${v}`, "info"); }
147
+ if (v !== undefined) { c.warningThreshold = v; saveConfig(c); ctx.ui.notify(`warn: ${v}`, "info"); }
148
148
  break;
149
149
  }
150
150
  case "force": {
151
- const v = await selectFrom(ctx, "🛑 umbral de corte", [
152
- { value: 2, label: "⚡ 2 (sensible)" },
153
- { value: 3, label: "🎯 3 (por defecto)" },
151
+ const v = await selectFrom(ctx, "🛑 force break threshold", [
152
+ { value: 2, label: "⚡ 2 (sensitive)" },
153
+ { value: 3, label: "🎯 3 (default)" },
154
154
  { value: 5, label: "5" },
155
- { value: 8, label: "🐢 8 (relajado)" },
155
+ { value: 8, label: "🐢 8 (relaxed)" },
156
156
  ]);
157
- if (v !== undefined) { c.forceBreakThreshold = v; saveConfig(c); ctx.ui.notify(`corte: ${v}`, "info"); }
157
+ if (v !== undefined) { c.forceBreakThreshold = v; saveConfig(c); ctx.ui.notify(`force break: ${v}`, "info"); }
158
158
  break;
159
159
  }
160
160
  case "abort": {
161
- const v = await selectFrom(ctx, "🚨 umbral de aborto (0 = desactivado)", [
162
- { value: 0, label: "🚫 desactivado" },
161
+ const v = await selectFrom(ctx, "🚨 abort threshold (0 = disabled)", [
162
+ { value: 0, label: "🚫 off" },
163
163
  { value: 5, label: "5" },
164
164
  { value: 8, label: "8" },
165
165
  { value: 10, label: "10" },
166
166
  { value: 15, label: "15" },
167
167
  ]);
168
- if (v !== undefined) { c.abortThreshold = v; saveConfig(c); ctx.ui.notify(`aborto: ${v || "desactivado"}`, "info"); }
168
+ if (v !== undefined) { c.abortThreshold = v; saveConfig(c); ctx.ui.notify(`abort: ${v || "off"}`, "info"); }
169
169
  break;
170
170
  }
171
171
  case "sim": {
172
- const v = await selectFrom(ctx, "📏 parecido mínimo entre mensajes", [
173
- { value: 0.5, label: "⚡ 50% (sensible)" },
172
+ const v = await selectFrom(ctx, "📏 text similarity", [
173
+ { value: 0.5, label: "⚡ 50% (sensitive)" },
174
174
  { value: 0.6, label: "60%" },
175
175
  { value: 0.7, label: "70%" },
176
- { value: 0.75, label: "🎯 75% (por defecto)" },
176
+ { value: 0.75, label: "🎯 75% (default)" },
177
177
  { value: 0.8, label: "80%" },
178
- { value: 0.9, label: "🐢 90% (relajado)" },
178
+ { value: 0.9, label: "🐢 90% (relaxed)" },
179
179
  ]);
180
- if (v !== undefined) { c.similarityThreshold = v; saveConfig(c); ctx.ui.notify(`parecido: ${(v * 100).toFixed(0)}%`, "info"); }
180
+ if (v !== undefined) { c.similarityThreshold = v; saveConfig(c); ctx.ui.notify(`similarity: ${(v * 100).toFixed(0)}%`, "info"); }
181
181
  break;
182
182
  }
183
183
  case "toolSim": {
184
- const v = await selectFrom(ctx, "🔧 parecido de las llamadas (argumentos)", [
185
- { value: 0.99, label: "⚡ 99% (estricto)" },
186
- { value: 0.95, label: "🎯 95% (por defecto)" },
184
+ const v = await selectFrom(ctx, "🔧 call similarity (arguments)", [
185
+ { value: 0.99, label: "⚡ 99% (strict)" },
186
+ { value: 0.95, label: "🎯 95% (default)" },
187
187
  { value: 0.9, label: "90%" },
188
- { value: 0.8, label: "🐢 80% (sensible)" },
188
+ { value: 0.8, label: "🐢 80% (sensitive)" },
189
189
  ]);
190
- if (v !== undefined) { c.toolSimilarityThreshold = v; saveConfig(c); ctx.ui.notify(`parecido de llamadas: ${(v * 100).toFixed(0)}%`, "info"); }
190
+ if (v !== undefined) { c.toolSimilarityThreshold = v; saveConfig(c); ctx.ui.notify(`call similarity: ${(v * 100).toFixed(0)}%`, "info"); }
191
191
  break;
192
192
  }
193
193
  case "toolRepeat": {
194
- const v = await selectFrom(ctx, "🔁 repeticiones de la misma llamada", [
195
- { value: 1, label: "⚡ 1 (sensible)" },
196
- { value: 2, label: "🎯 2 (por defecto)" },
197
- { value: 3, label: "🐢 3 (relajado)" },
194
+ const v = await selectFrom(ctx, "🔁 call repeats", [
195
+ { value: 1, label: "⚡ 1 (sensitive)" },
196
+ { value: 2, label: "🎯 2 (default)" },
197
+ { value: 3, label: "🐢 3 (relaxed)" },
198
198
  ]);
199
- if (v !== undefined) { c.minToolRepeatCount = v; saveConfig(c); ctx.ui.notify(`repeticiones: ${v}+`, "info"); }
199
+ if (v !== undefined) { c.minToolRepeatCount = v; saveConfig(c); ctx.ui.notify(`call repeats: ${v}+`, "info"); }
200
200
  break;
201
201
  }
202
202
  case "resultSim": {
203
- const v = await selectFrom(ctx, "🧾 parecido de resultados (veto de progreso)", [
204
- { value: 0.95, label: "⚡ 95% (estrictosolo resultados casi idénticos cuentan como igual)" },
205
- { value: 0.8, label: "🎯 80% (por defecto)" },
206
- { value: 0.6, label: "🐢 60% (relajadotolera más ruido de salida)" },
203
+ const v = await selectFrom(ctx, "🧾 result similarity (progress veto)", [
204
+ { value: 0.95, label: "⚡ 95% (strictonly near-identical results count as the same)" },
205
+ { value: 0.8, label: "🎯 80% (default)" },
206
+ { value: 0.6, label: "🐢 60% (relaxedtolerates more output noise)" },
207
207
  ]);
208
- if (v !== undefined) { c.resultSimilarityThreshold = v; saveConfig(c); ctx.ui.notify(`parecido de resultados: ${(v * 100).toFixed(0)}%`, "info"); }
208
+ if (v !== undefined) { c.resultSimilarityThreshold = v; saveConfig(c); ctx.ui.notify(`result similarity: ${(v * 100).toFixed(0)}%`, "info"); }
209
209
  break;
210
210
  }
211
211
  case "streams":
212
212
  c.detectTaskStreams = !c.detectTaskStreams; saveConfig(c);
213
- ctx.ui.notify(`lotes de tareas: ${yn(c.detectTaskStreams)}`, "info"); break;
213
+ ctx.ui.notify(`task streams: ${yn(c.detectTaskStreams)}`, "info"); break;
214
214
  case "streamMin": {
215
- const v = await selectFrom(ctx, "📋 llamadas mínimas para reconocer un lote", [
216
- { value: 2, label: "⚡ 2 (sensible)" },
217
- { value: 3, label: "🎯 3 (por defecto)" },
215
+ const v = await selectFrom(ctx, "📋 stream min calls", [
216
+ { value: 2, label: "⚡ 2 (sensitive)" },
217
+ { value: 3, label: "🎯 3 (default)" },
218
218
  { value: 4, label: "4" },
219
- { value: 5, label: "🐢 5 (conservador)" },
219
+ { value: 5, label: "🐢 5 (conservative)" },
220
220
  ]);
221
- if (v !== undefined) { c.taskStreamMinCalls = v; saveConfig(c); ctx.ui.notify(`llamadas mínimas: ${v}`, "info"); }
221
+ if (v !== undefined) { c.taskStreamMinCalls = v; saveConfig(c); ctx.ui.notify(`stream min calls: ${v}`, "info"); }
222
222
  break;
223
223
  }
224
224
  case "streamTwin": {
225
- const v = await selectFrom(ctx, "📋 umbral de gemelos (argumentos)", [
226
- { value: 0.99, label: "🎯 99% (por defecto cualquier diferencia real = tarea distinta)" },
227
- { value: 0.95, label: "95% (argumentos casi idénticos cuentan como la misma tarea)" },
228
- { value: 0.9, label: "⚡ 90% (detección de bucles más agresiva)" },
225
+ const v = await selectFrom(ctx, "📋 twin threshold (arguments)", [
226
+ { value: 0.99, label: "🎯 99% (defaultany real difference = distinct task)" },
227
+ { value: 0.95, label: "95% (near-identical arguments count as the same task)" },
228
+ { value: 0.9, label: "⚡ 90% (more aggressive loop detection)" },
229
229
  ]);
230
- if (v !== undefined) { c.taskStreamTwinThreshold = v; saveConfig(c); ctx.ui.notify(`gemelos: ${(v * 100).toFixed(0)}%`, "info"); }
230
+ if (v !== undefined) { c.taskStreamTwinThreshold = v; saveConfig(c); ctx.ui.notify(`twin threshold: ${(v * 100).toFixed(0)}%`, "info"); }
231
231
  break;
232
232
  }
233
233
  case "text":
234
234
  c.detectTextLoops = !c.detectTextLoops; saveConfig(c);
235
- ctx.ui.notify(`texto: ${yn(c.detectTextLoops)}`, "info"); break;
235
+ ctx.ui.notify(`text: ${yn(c.detectTextLoops)}`, "info"); break;
236
236
  case "tool":
237
237
  c.detectToolLoops = !c.detectToolLoops; saveConfig(c);
238
- ctx.ui.notify(`herramientas: ${yn(c.detectToolLoops)}`, "info"); break;
238
+ ctx.ui.notify(`tools: ${yn(c.detectToolLoops)}`, "info"); break;
239
239
  case "think":
240
240
  c.detectThinkingLoops = !c.detectThinkingLoops; saveConfig(c);
241
- ctx.ui.notify(`pensamiento: ${yn(c.detectThinkingLoops)}`, "info"); break;
241
+ ctx.ui.notify(`thinking: ${yn(c.detectThinkingLoops)}`, "info"); break;
242
242
  case "reset":
243
243
  resetState(rt.state);
244
244
  rt.pendingIntervention = null;
245
- ctx.ui.notify("🧹 estado reiniciado", "info");
245
+ ctx.ui.notify("🧹 state reset", "info");
246
246
  rt.updateStatus(ctx);
247
247
  break;
248
248
  }
@@ -250,7 +250,7 @@ async function showConfigMenu(ctx: ExtensionCommandContext, rt: Runtime): Promis
250
250
 
251
251
  async function showLog(ctx: ExtensionCommandContext, rt: Runtime): Promise<void> {
252
252
  if (!rt.state.detections.length) {
253
- ctx.ui.notify("no hay detecciones en esta sesión", "info");
253
+ ctx.ui.notify("no detections this session", "info");
254
254
  return;
255
255
  }
256
256
  const items = rt.state.detections.slice(-30).reverse().map((d) => ({
@@ -258,7 +258,7 @@ async function showLog(ctx: ExtensionCommandContext, rt: Runtime): Promise<void>
258
258
  label: `[${d.type}] ${d.description}`,
259
259
  description: `${(d.similarity * 100).toFixed(0)}% · ${formatDuration(Date.now() - d.timestamp)} ago`,
260
260
  }));
261
- await selectFrom(ctx, `🕵️ detecciones (${rt.state.detections.length} en total)`, items);
261
+ await selectFrom(ctx, `🕵️ detections (${rt.state.detections.length} total)`, items);
262
262
  }
263
263
 
264
264
  export function resetState(state: AntiloopState): void {