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.
- package/README.md +27 -27
- package/package.json +1 -1
- 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
|
-
|
|
109
|
+
Grouped interactive menu showing the current value in each option:
|
|
110
110
|
|
|
111
111
|
**🎛️ General**
|
|
112
|
-
- **🟢/🔴
|
|
113
|
-
- **⏳
|
|
114
|
-
- **🔔
|
|
115
|
-
- **📊
|
|
116
|
-
- **⌨️
|
|
117
|
-
|
|
118
|
-
**🎯
|
|
119
|
-
- **⚠️
|
|
120
|
-
- **🛑
|
|
121
|
-
- **🚨
|
|
122
|
-
- **📏
|
|
123
|
-
- **🔧
|
|
124
|
-
- **🔁
|
|
125
|
-
- **🧾
|
|
126
|
-
|
|
127
|
-
**📋
|
|
128
|
-
- **📋
|
|
129
|
-
- **📋
|
|
130
|
-
- **📋
|
|
131
|
-
|
|
132
|
-
**🔍
|
|
133
|
-
- **📝
|
|
134
|
-
- **🔧
|
|
135
|
-
- **🧠
|
|
136
|
-
|
|
137
|
-
**🧹
|
|
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 off — key 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.
|
|
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 ? "🟢
|
|
82
|
-
{ value: "window" as const, label: `⏳
|
|
83
|
-
{ value: "notify" as const, label: `🔔
|
|
84
|
-
{ value: "footer" as const, label: `📊
|
|
85
|
-
{ value: "shortcut" as const, label: `⌨️
|
|
86
|
-
// ── 🎯
|
|
87
|
-
{ value: "warn" as const, label: `⚠️
|
|
88
|
-
{ value: "force" as const, label: `🛑
|
|
89
|
-
{ value: "abort" as const, label: `🚨
|
|
90
|
-
{ value: "sim" as const, label: `📏
|
|
91
|
-
{ value: "toolSim" as const, label: `🔧
|
|
92
|
-
{ value: "toolRepeat" as const, label: `🔁
|
|
93
|
-
{ value: "resultSim" as const, label: `🧾
|
|
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: `📋
|
|
96
|
-
{ value: "streamMin" as const, label: `📋
|
|
97
|
-
{ value: "streamTwin" as const, label: `📋
|
|
98
|
-
// ── 🔍
|
|
99
|
-
{ value: "text" as const, label: `📝
|
|
100
|
-
{ value: "tool" as const, label: `🔧
|
|
101
|
-
{ value: "think" as const, label: `🧠
|
|
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: "🧹
|
|
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, "⏳
|
|
114
|
+
const v = await selectFrom(ctx, "⏳ window (messages to analyze)", [
|
|
115
115
|
{ value: 5, label: "5" },
|
|
116
|
-
{ value: 10, label: "10 (
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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, "⌨️
|
|
134
|
-
{ value: "esc+a" as const, label: "⌨️ esc+a (
|
|
135
|
-
{ value: "off" as const, label: "🚫
|
|
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(`
|
|
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, "⚠️
|
|
142
|
-
{ value: 1, label: "⚡ 1 (
|
|
143
|
-
{ value: 2, label: "🎯 2 (
|
|
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 (
|
|
145
|
+
{ value: 5, label: "🐢 5 (relaxed)" },
|
|
146
146
|
]);
|
|
147
|
-
if (v !== undefined) { c.warningThreshold = v; saveConfig(c); ctx.ui.notify(`
|
|
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, "🛑
|
|
152
|
-
{ value: 2, label: "⚡ 2 (
|
|
153
|
-
{ value: 3, label: "🎯 3 (
|
|
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 (
|
|
155
|
+
{ value: 8, label: "🐢 8 (relaxed)" },
|
|
156
156
|
]);
|
|
157
|
-
if (v !== undefined) { c.forceBreakThreshold = v; saveConfig(c); ctx.ui.notify(`
|
|
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, "🚨
|
|
162
|
-
{ value: 0, label: "🚫
|
|
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(`
|
|
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, "📏
|
|
173
|
-
{ value: 0.5, label: "⚡ 50% (
|
|
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% (
|
|
176
|
+
{ value: 0.75, label: "🎯 75% (default)" },
|
|
177
177
|
{ value: 0.8, label: "80%" },
|
|
178
|
-
{ value: 0.9, label: "🐢 90% (
|
|
178
|
+
{ value: 0.9, label: "🐢 90% (relaxed)" },
|
|
179
179
|
]);
|
|
180
|
-
if (v !== undefined) { c.similarityThreshold = v; saveConfig(c); ctx.ui.notify(`
|
|
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, "🔧
|
|
185
|
-
{ value: 0.99, label: "⚡ 99% (
|
|
186
|
-
{ value: 0.95, label: "🎯 95% (
|
|
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% (
|
|
188
|
+
{ value: 0.8, label: "🐢 80% (sensitive)" },
|
|
189
189
|
]);
|
|
190
|
-
if (v !== undefined) { c.toolSimilarityThreshold = v; saveConfig(c); ctx.ui.notify(`
|
|
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, "🔁
|
|
195
|
-
{ value: 1, label: "⚡ 1 (
|
|
196
|
-
{ value: 2, label: "🎯 2 (
|
|
197
|
-
{ value: 3, label: "🐢 3 (
|
|
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(`
|
|
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, "🧾
|
|
204
|
-
{ value: 0.95, label: "⚡ 95% (
|
|
205
|
-
{ value: 0.8, label: "🎯 80% (
|
|
206
|
-
{ value: 0.6, label: "🐢 60% (
|
|
203
|
+
const v = await selectFrom(ctx, "🧾 result similarity (progress veto)", [
|
|
204
|
+
{ value: 0.95, label: "⚡ 95% (strict — only near-identical results count as the same)" },
|
|
205
|
+
{ value: 0.8, label: "🎯 80% (default)" },
|
|
206
|
+
{ value: 0.6, label: "🐢 60% (relaxed — tolerates more output noise)" },
|
|
207
207
|
]);
|
|
208
|
-
if (v !== undefined) { c.resultSimilarityThreshold = v; saveConfig(c); ctx.ui.notify(`
|
|
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(`
|
|
213
|
+
ctx.ui.notify(`task streams: ${yn(c.detectTaskStreams)}`, "info"); break;
|
|
214
214
|
case "streamMin": {
|
|
215
|
-
const v = await selectFrom(ctx, "📋
|
|
216
|
-
{ value: 2, label: "⚡ 2 (
|
|
217
|
-
{ value: 3, label: "🎯 3 (
|
|
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 (
|
|
219
|
+
{ value: 5, label: "🐢 5 (conservative)" },
|
|
220
220
|
]);
|
|
221
|
-
if (v !== undefined) { c.taskStreamMinCalls = v; saveConfig(c); ctx.ui.notify(`
|
|
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, "📋
|
|
226
|
-
{ value: 0.99, label: "🎯 99% (
|
|
227
|
-
{ value: 0.95, label: "95% (
|
|
228
|
-
{ value: 0.9, label: "⚡ 90% (
|
|
225
|
+
const v = await selectFrom(ctx, "📋 twin threshold (arguments)", [
|
|
226
|
+
{ value: 0.99, label: "🎯 99% (default — any 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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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("🧹
|
|
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
|
|
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, `🕵️
|
|
261
|
+
await selectFrom(ctx, `🕵️ detections (${rt.state.detections.length} total)`, items);
|
|
262
262
|
}
|
|
263
263
|
|
|
264
264
|
export function resetState(state: AntiloopState): void {
|