@capdiem/pi-repetition-guard 0.2.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.
- package/LICENSE +21 -0
- package/README.md +97 -0
- package/index.min.js +8 -0
- package/index.min.js.map +11 -0
- package/package.json +46 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 capdiem
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# pi-repetition-guard
|
|
2
|
+
|
|
3
|
+
A **repetition-loop guard** for the [Pi coding agent](https://pi.dev/). It
|
|
4
|
+
detects **thinking-runaway / 万字复读** — the failure mode where a model in a
|
|
5
|
+
long-thinking (or plain output) stage starts repeating the same text in a loop,
|
|
6
|
+
producing tens of thousands of redundant characters instead of converging to an
|
|
7
|
+
answer — and **aborts + re-steers** the model so you get a real answer instead
|
|
8
|
+
of a wall of repeated prose.
|
|
9
|
+
|
|
10
|
+
It is model-agnostic: it wraps whatever model Pi is calling, observes the
|
|
11
|
+
streamed output, and never rewrites mid-stream (Pi does not allow that — see
|
|
12
|
+
[ADR 0001](../../docs/adr/0001-ask-user-triggering-metadata-first.md)). It works
|
|
13
|
+
purely on the assistant message text, covering both the `thinking` stream and
|
|
14
|
+
the final `text` stream (whichever hits the threshold first).
|
|
15
|
+
|
|
16
|
+
> **Design note (ADR 0002):** this extension deliberately performs *active
|
|
17
|
+
> intervention* (`ctx.abort()` + a corrective `steer` message), which goes
|
|
18
|
+
> beyond the "no active injection" decision in ADR 0001. That ADR was scoped to
|
|
19
|
+
> `ask_user` triggering; the repetition guard is a safety mechanism where
|
|
20
|
+
> active abort is the whole point. See
|
|
21
|
+
> [docs/adr/0002-repetition-guard-active-abort.md](../../docs/adr/0002-repetition-guard-active-abort.md).
|
|
22
|
+
|
|
23
|
+
## Features
|
|
24
|
+
|
|
25
|
+
- **Hybrid detection** over the accumulated streamed text (`message_update`):
|
|
26
|
+
- *Block-repeat* (primary, low false-positive): a long block (line) repeated
|
|
27
|
+
2× → suspect, 3× → hard.
|
|
28
|
+
- *Shingle novelty* (confirmatory, earlier): the trailing ~40×40-char window
|
|
29
|
+
mostly re-emitting already-seen content → suspect at ≥80%, hard at ≥95%.
|
|
30
|
+
- **Two-stage trigger**: stage-1 "suspect" is **record-only** (a log line); only
|
|
31
|
+
stage-2 "hard" aborts. This gives normal long-form content (code blocks,
|
|
32
|
+
lists, prose) room before anything is interrupted.
|
|
33
|
+
- **Abort + re-steer** on hard trigger:
|
|
34
|
+
- `ctx.abort()` stops the runaway generation in `message_update`.
|
|
35
|
+
- A corrective `steer` message is sent from `message_end` (race-free — the
|
|
36
|
+
aborted message is finalized by then): *"you are in a repetition loop, stop
|
|
37
|
+
repeating, give a concise final answer"* plus a **truncated sample of the
|
|
38
|
+
repeated block** so the model can see exactly what it was repeating.
|
|
39
|
+
- **Retry budget**: max **2** steer retries per logical user turn, with
|
|
40
|
+
escalated wording on the 2nd; if it still runaways, it aborts without another
|
|
41
|
+
steer (no infinite abort↔steer loop).
|
|
42
|
+
- **User-only control**: `/runaway on|off` slash command, default **on**. The
|
|
43
|
+
guard cannot be disabled by the LLM itself (there is no LLM-callable toggle).
|
|
44
|
+
|
|
45
|
+
## Install
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pi install npm:@capdiem/pi-repetition-guard
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
To try it without installing:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pi -e ./extensions/pi-repetition-guard/index.ts
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Usage
|
|
58
|
+
|
|
59
|
+
Nothing to configure — it is on by default and runs silently in the background.
|
|
60
|
+
|
|
61
|
+
- **Normal operation:** you see nothing. Stage-1 "suspect" writes a
|
|
62
|
+
`[pi-repetition-guard]` log line only.
|
|
63
|
+
- **Runaway detected:** the generation is aborted and a `steer` retry runs
|
|
64
|
+
automatically. In the TUI you'll see the aborted message, then the corrective
|
|
65
|
+
message, then a fresh answer. (The aborted junk stays in history — no cleanup,
|
|
66
|
+
per the settled design; see ADR 0002.)
|
|
67
|
+
- **Disable / re-enable:**
|
|
68
|
+
```
|
|
69
|
+
/runaway off
|
|
70
|
+
/runaway on
|
|
71
|
+
```
|
|
72
|
+
A bare `/runaway` toggles. In the TUI the footer status shows `guard:on` /
|
|
73
|
+
`guard:off`.
|
|
74
|
+
|
|
75
|
+
## Behavior details
|
|
76
|
+
|
|
77
|
+
- **Budget reset:** a new logical user turn (a user message that is *not* our
|
|
78
|
+
own steer) resets the retry budget to 2.
|
|
79
|
+
- **Scope:** only the current assistant message's own text is compared — the
|
|
80
|
+
detector does not compare against the user's message or prior assistant
|
|
81
|
+
messages, so restating the user's words never false-triggers.
|
|
82
|
+
- **Wholesale repetition is always flagged:** re-emitting any content verbatim
|
|
83
|
+
(even a "checklist") is, by definition, the runaway signature.
|
|
84
|
+
- **Sampling parameters are untouched:** Pi exposes no built-in
|
|
85
|
+
`repetition_penalty` / `no_repeat_ngram_size`; this guard detects and
|
|
86
|
+
intervenes client-side instead of relying on provider-side sampling knobs.
|
|
87
|
+
|
|
88
|
+
## Diagnostics
|
|
89
|
+
|
|
90
|
+
Detection events (stage-1 suspect, stage-2 hard trigger, retry count, budget
|
|
91
|
+
exhaustion) are logged with a `[pi-repetition-guard]` prefix. These records
|
|
92
|
+
support tuning the fixed two-stage thresholds over time — consistent with the
|
|
93
|
+
repo's diagnostics discipline (ADR 0001).
|
|
94
|
+
|
|
95
|
+
## License
|
|
96
|
+
|
|
97
|
+
MIT
|
package/index.min.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
var $=2;class f{text="";lastChecked=0;seenShingles=new Set;lastResult={suspect:!1,hard:!1,sample:""};reset(){this.text="",this.lastChecked=0,this.seenShingles.clear(),this.lastResult={suspect:!1,hard:!1,sample:""}}ingest(J){if(this.text=J,this.text.length-this.lastChecked>=200)this.lastChecked=this.text.length,this.lastResult=this.scan();return this.lastResult}scan(){let J=!1,O=!1,z="",K=new Map,Q=0,Z="";for(let Y of this.text.split(/\n+/)){let j=Y.trim();if(j.length<60)continue;let q=(K.get(j)??0)+1;if(K.set(j,q),q>Q)Q=q,Z=j}if(Q>=3)O=!0,z=Z;else if(Q>=2)J=!0,z=Z;let P=[];for(let Y=0;Y+40<=this.text.length;Y++){let j=this.text.slice(Y,Y+40),q=this.seenShingles.has(j);if(P.length<40)P.push(q);else P.shift(),P.push(q);this.seenShingles.add(j)}let H=P.filter(Boolean).length,W=P.length>0?H/P.length:0;if(W>=0.95)O=!0;else if(W>=0.8)J=!0;return{suspect:J,hard:O,sample:z.length>200?`${z.slice(0,200)}…`:z}}}function w(J){if(!Array.isArray(J))return"";let O=[];for(let z of J){if(!z||typeof z!=="object")continue;let K=z;if(K.type==="text"&&typeof K.text==="string")O.push(K.text);else if(K.type==="thinking"){if(typeof K.thinking==="string")O.push(K.thinking);else if(typeof K.text==="string")O.push(K.text)}}return O.join(`
|
|
2
|
+
`)}function F(J,O){if(J>=2)return"[自动护栏] 你刚才再次陷入了复读循环,这是第 2 次。请立即停止重复,直接给出简洁、收敛的"+"最终回答。不要输出思考过程,不要复述任何已说过的内容,直接回答。";return"[自动护栏] 检测到你陷入了复读循环(重复输出相同内容)。请立即停止重复,直接给出简洁、收敛的"+`最终回答,不要复述或重复任何已说过的内容。
|
|
3
|
+
|
|
4
|
+
`+`刚才重复的片段(节选):
|
|
5
|
+
`+(O||"(无样例)")}function G(J){let O=new f,z=!0,K=$,Q,Z=!1,P=!1,H=!1,W=(j)=>{console.log(`[pi-repetition-guard] ${j}`)},Y=(j,q)=>{K-=1;let V=$-K;if(K>=0)Q=F(V,q),W(`hard trigger — aborting, will steer (retry ${V}/${$})`);else W("hard trigger — aborting, retry budget exhausted (giving up)");j.abort()};J.on("message_start",(j)=>{let q=j.message?.role;if(q==="assistant")O.reset(),P=!1,H=!1;else if(q==="user")if(Z)Z=!1;else K=$,W(`new user turn — budget reset to ${$}`)}),J.on("message_update",(j,q)=>{if(!z||H)return;if(j.message.role!=="assistant")return;let V=O.ingest(w(j.message.content));if(V.hard)H=!0,Y(q,V.sample);else if(V.suspect&&!P)P=!0,W("suspect (stage-1): possible repetition loop, recording only")}),J.on("message_end",(j)=>{if(!Q)return;if(j.message.role!=="assistant")return;let q=Q;Q=void 0,Z=!0,W("sending steer retry"),J.sendUserMessage(q,{deliverAs:"steer"})}),J.registerCommand("runaway",{description:"Toggle the repetition-loop guard (on | off). Default: on.",handler:async(j,q)=>{let V=j.trim().toLowerCase();if(V==="on")z=!0;else if(V==="off")z=!1;else z=!z;if(q.hasUI)q.ui.notify(`Repetition guard ${z?"ON":"OFF"}`,z?"info":"warning");if(q.mode==="tui")q.ui.setStatus("pi-repetition-guard",z?"guard:on":"guard:off")}})}export{G as default};
|
|
6
|
+
|
|
7
|
+
//# debugId=E00E93383848526F64756E2164756E21
|
|
8
|
+
//# sourceMappingURL=index.min.js.map
|
package/index.min.js.map
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["..\\..\\extensions\\pi-repetition-guard\\detector.ts", "..\\..\\extensions\\pi-repetition-guard\\index.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"// Pure detection logic for pi-repetition-guard — no pi extension APIs here,\n// so it is unit-testable in isolation (mirrors pi-todo's state.ts split).\n\n// ── Tunable defaults (two-stage trigger: fixed, not user-configurable) ──────\nexport const GUARD_STEER_MAX_RETRIES = 2;\n/** Rescan cadence — we re-scan only after this many new chars accumulate. */\nconst CHECKPOINT_CHARS = 200;\n/** A block must be at least this long (chars) to count as a repeatable block. */\nconst MIN_BLOCK_CHARS = 60;\n/** Block seen this many times → stage-1 suspect. */\nconst SUSPECT_BLOCK_REPEATS = 2;\n/** Block seen this many times → stage-2 hard trigger. */\nconst HARD_BLOCK_REPEATS = 3;\n/** Shingle length (chars) for the n-gram novelty signal. */\nconst SHINGLE_SIZE = 40;\n/** \"Recent\" window = this many trailing shingles (~RECENT_SHINGLES × 40 chars). */\nconst RECENT_SHINGLES = 40;\n/** Recent-window overlap ratio at or above this → stage-1 suspect. */\nconst SUSPECT_RECENT_RATIO = 0.8;\n/** Recent-window overlap ratio at or above this → stage-2 hard trigger. */\nconst HARD_RECENT_RATIO = 0.95;\n/** Repeated-block sample length for the steer message. */\nexport const SAMPLE_MAX_CHARS = 200;\n\nexport interface DetectionResult {\n suspect: boolean;\n hard: boolean;\n /** Most-repeated block, truncated to SAMPLE_MAX_CHARS, for the steer message. */\n sample: string;\n}\n\n/**\n * Hybrid repetition-loop detector over the accumulated text of one assistant\n * message (thinking + final blocks concatenated).\n *\n * Two signals:\n * 1. Block-repeat (primary, low false-positive): a long block (line) repeated\n * 2× → suspect, 3× → hard.\n * 2. Shingle novelty (confirmatory, earlier): over a trailing window of 40-char\n * shingles, the fraction that already appeared earlier in the message. ≥80%\n * → suspect, ≥95% → hard.\n */\nexport class RepetitionDetector {\n private text = \"\";\n private lastChecked = 0;\n private seenShingles = new Set<string>();\n private lastResult: DetectionResult = { suspect: false, hard: false, sample: \"\" };\n\n /** Start a fresh message: call on assistant message_start. */\n reset(): void {\n this.text = \"\";\n this.lastChecked = 0;\n this.seenShingles.clear();\n this.lastResult = { suspect: false, hard: false, sample: \"\" };\n }\n\n /** Feed the latest full accumulated text; returns the current verdict. */\n ingest(newText: string): DetectionResult {\n this.text = newText;\n if (this.text.length - this.lastChecked >= CHECKPOINT_CHARS) {\n this.lastChecked = this.text.length;\n this.lastResult = this.scan();\n }\n return this.lastResult;\n }\n\n private scan(): DetectionResult {\n let suspect = false;\n let hard = false;\n let sample = \"\";\n\n // 1) Block-repeat (primary signal).\n const blockCounts = new Map<string, number>();\n let maxCount = 0;\n let mostRepeated = \"\";\n for (const rawBlock of this.text.split(/\\n+/)) {\n const block = rawBlock.trim();\n if (block.length < MIN_BLOCK_CHARS) continue;\n const count = (blockCounts.get(block) ?? 0) + 1;\n blockCounts.set(block, count);\n if (count > maxCount) {\n maxCount = count;\n mostRepeated = block;\n }\n }\n if (maxCount >= HARD_BLOCK_REPEATS) {\n hard = true;\n sample = mostRepeated;\n } else if (maxCount >= SUSPECT_BLOCK_REPEATS) {\n suspect = true;\n sample = mostRepeated;\n }\n\n // 2) Shingle novelty (confirmatory signal). The seen-set persists across\n // scans, so trailing shingles that repeat earlier content get flagged.\n const recent: boolean[] = [];\n for (let i = 0; i + SHINGLE_SIZE <= this.text.length; i++) {\n const shingle = this.text.slice(i, i + SHINGLE_SIZE);\n const isRepeat = this.seenShingles.has(shingle);\n if (recent.length < RECENT_SHINGLES) {\n recent.push(isRepeat);\n } else {\n recent.shift();\n recent.push(isRepeat);\n }\n this.seenShingles.add(shingle);\n }\n const recentRepeats = recent.filter(Boolean).length;\n const recentRatio = recent.length > 0 ? recentRepeats / recent.length : 0;\n if (recentRatio >= HARD_RECENT_RATIO) hard = true;\n else if (recentRatio >= SUSPECT_RECENT_RATIO) suspect = true;\n\n return {\n suspect,\n hard,\n sample: sample.length > SAMPLE_MAX_CHARS ? `${sample.slice(0, SAMPLE_MAX_CHARS)}…` : sample,\n };\n }\n}\n\n/** Concatenate text + thinking blocks of an assistant message for detection. */\nexport function extractText(content: unknown): string {\n if (!Array.isArray(content)) return \"\";\n const parts: string[] = [];\n for (const item of content) {\n if (!item || typeof item !== \"object\") continue;\n const block = item as { type?: unknown; text?: unknown; thinking?: unknown };\n if (block.type === \"text\" && typeof block.text === \"string\") {\n parts.push(block.text);\n } else if (block.type === \"thinking\") {\n if (typeof block.thinking === \"string\") parts.push(block.thinking);\n else if (typeof block.text === \"string\") parts.push(block.text);\n }\n }\n return parts.join(\"\\n\");\n}\n\n/** Build the corrective steer message; retryNum is 1-based (1 = first retry). */\nexport function buildSteer(retryNum: number, sample: string): string {\n if (retryNum >= 2) {\n return (\n \"[自动护栏] 你刚才再次陷入了复读循环,这是第 2 次。请立即停止重复,直接给出简洁、收敛的\" +\n \"最终回答。不要输出思考过程,不要复述任何已说过的内容,直接回答。\"\n );\n }\n return (\n \"[自动护栏] 检测到你陷入了复读循环(重复输出相同内容)。请立即停止重复,直接给出简洁、收敛的\" +\n \"最终回答,不要复述或重复任何已说过的内容。\\n\\n\" +\n \"刚才重复的片段(节选):\\n\" +\n (sample || \"(无样例)\")\n );\n}\n",
|
|
6
|
+
"import type { ExtensionAPI, ExtensionContext } from \"@earendil-works/pi-coding-agent\";\nimport {\n GUARD_STEER_MAX_RETRIES,\n RepetitionDetector,\n buildSteer,\n extractText,\n} from \"./detector.ts\";\n\n/**\n * pi-repetition-guard — a repetition-loop guard for the Pi coding agent.\n *\n * Detects thinking-runaway / 万字复读 (the model repeating the same text in a\n * loop during long thinking or output) by observing `message_update` stream\n * events, then aborts the generation and re-steers the model with a corrective\n * steer message (ADR 0002: active abort, beyond ADR 0001's ask_user scope).\n *\n * Design (settled in grilling, see docs/adr/0002):\n * - Hybrid detection: block-repeat primary + n-gram shingle novelty confirmatory.\n * - Two-stage trigger: stage-1 \"suspect\" is record-only; stage-2 \"hard\" aborts.\n * - Intervention pipeline: ctx.abort() in message_update, then send the steer\n * from message_end (race-free: the aborted message is finalized by then).\n * - Retry budget: max 2 steer retries per logical user turn, escalated wording\n * on the 2nd, then give up (abort without steer).\n * - Control: `/runaway on|off` slash command, default on.\n */\nexport default function repetitionGuardExtension(pi: ExtensionAPI): void {\n const detector = new RepetitionDetector();\n let enabled = true;\n let steerBudget = GUARD_STEER_MAX_RETRIES;\n let pendingSteer: string | undefined;\n let steerInFlight = false;\n let suspectLogged = false;\n let hardTriggered = false;\n\n const diag = (message: string): void => {\n console.log(`[pi-repetition-guard] ${message}`);\n };\n\n const onHardTrigger = (ctx: ExtensionContext, sample: string): void => {\n steerBudget -= 1;\n const retryNum = GUARD_STEER_MAX_RETRIES - steerBudget; // 1-based: 1 or 2\n if (steerBudget >= 0) {\n pendingSteer = buildSteer(retryNum, sample);\n diag(`hard trigger — aborting, will steer (retry ${retryNum}/${GUARD_STEER_MAX_RETRIES})`);\n } else {\n diag(\"hard trigger — aborting, retry budget exhausted (giving up)\");\n }\n ctx.abort();\n };\n\n // Track per-message state and the retry budget across logical user turns.\n pi.on(\"message_start\", (event) => {\n const role = event.message?.role;\n if (role === \"assistant\") {\n detector.reset();\n suspectLogged = false;\n hardTriggered = false;\n } else if (role === \"user\") {\n if (steerInFlight) {\n steerInFlight = false; // our own steer — keep the budget\n } else {\n steerBudget = GUARD_STEER_MAX_RETRIES; // real user message — new turn\n diag(`new user turn — budget reset to ${GUARD_STEER_MAX_RETRIES}`);\n }\n }\n });\n\n // The one real-time observation point: stream updates with the accumulated\n // full message snapshot (thinking + final text).\n pi.on(\"message_update\", (event, ctx) => {\n if (!enabled || hardTriggered) return;\n if (event.message.role !== \"assistant\") return;\n const result = detector.ingest(extractText(event.message.content));\n if (result.hard) {\n hardTriggered = true;\n onHardTrigger(ctx, result.sample);\n } else if (result.suspect && !suspectLogged) {\n suspectLogged = true;\n diag(\"suspect (stage-1): possible repetition loop, recording only\");\n }\n });\n\n // Send the steer once the aborted message is finalized (avoids racing the\n // abort; message_end replacement is not used — ADR 0002, no cleanup).\n pi.on(\"message_end\", (event) => {\n if (!pendingSteer) return;\n if (event.message.role !== \"assistant\") return;\n const steer = pendingSteer;\n pendingSteer = undefined;\n steerInFlight = true;\n diag(\"sending steer retry\");\n pi.sendUserMessage(steer, { deliverAs: \"steer\" });\n });\n\n // User-only control surface: the guard cannot be disabled by the LLM.\n pi.registerCommand(\"runaway\", {\n description: \"Toggle the repetition-loop guard (on | off). Default: on.\",\n handler: async (args, ctx) => {\n const arg = args.trim().toLowerCase();\n if (arg === \"on\") enabled = true;\n else if (arg === \"off\") enabled = false;\n else enabled = !enabled;\n if (ctx.hasUI) {\n ctx.ui.notify(`Repetition guard ${enabled ? \"ON\" : \"OFF\"}`, enabled ? \"info\" : \"warning\");\n }\n if (ctx.mode === \"tui\") {\n ctx.ui.setStatus(\"pi-repetition-guard\", enabled ? \"guard:on\" : \"guard:off\");\n }\n },\n });\n}\n"
|
|
7
|
+
],
|
|
8
|
+
"mappings": "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",
|
|
9
|
+
"debugId": "E00E93383848526F64756E2164756E21",
|
|
10
|
+
"names": []
|
|
11
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@capdiem/pi-repetition-guard",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "A repetition-loop guard for the Pi coding agent: detects thinking-runaway / 万字复读 (the model repeating the same text in a loop) in streamed output via message_update, then aborts and re-steers the model with a corrective steer message",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"pi": {
|
|
7
|
+
"extensions": [
|
|
8
|
+
"./index.min.js"
|
|
9
|
+
]
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"pi-package",
|
|
13
|
+
"pi",
|
|
14
|
+
"coding-agent",
|
|
15
|
+
"repetition",
|
|
16
|
+
"repetition-loop",
|
|
17
|
+
"thinking-runaway",
|
|
18
|
+
"runaway",
|
|
19
|
+
"guard",
|
|
20
|
+
"abort",
|
|
21
|
+
"steer"
|
|
22
|
+
],
|
|
23
|
+
"author": "capdiem <capdiem@live.com>",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "git+https://github.com/capdiem/pi-extensions.git",
|
|
28
|
+
"directory": "extensions/pi-repetition-guard"
|
|
29
|
+
},
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public",
|
|
32
|
+
"registry": "https://registry.npmjs.org/"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"@earendil-works/pi-ai": "*",
|
|
36
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
37
|
+
"@earendil-works/pi-tui": "*",
|
|
38
|
+
"typebox": "*"
|
|
39
|
+
},
|
|
40
|
+
"files": [
|
|
41
|
+
"LICENSE",
|
|
42
|
+
"README.md",
|
|
43
|
+
"index.min.js",
|
|
44
|
+
"index.min.js.map"
|
|
45
|
+
]
|
|
46
|
+
}
|