pi-makora-provider 1.6.0 → 1.7.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/README.md +13 -8
- package/death-loop-guard.ts +196 -30
- package/index.ts +7 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -160,19 +160,21 @@ Do **not** edit `models.json` directly — it is auto-generated from the API. To
|
|
|
160
160
|
|
|
161
161
|
## Death-Loop Guard
|
|
162
162
|
|
|
163
|
-
GLM 5.2 (NVFP4 / FP8) occasionally degenerates into
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
163
|
+
GLM 5.2 (NVFP4 / FP8) occasionally degenerates into a repetition loop — an
|
|
164
|
+
unbroken run of a single character or token, spaced or not (observed: `!` →
|
|
165
|
+
`!!!!...`, `0` → `0000...`, and `0` → `0 0 0 ...`) — that eats the whole
|
|
166
|
+
response. This extension ships a guard that watches the streamed assistant
|
|
167
|
+
output (both the visible answer and the reasoning trace) and, when it detects a
|
|
168
|
+
long run of any one non-whitespace character **or token**, **aborts
|
|
167
169
|
the runaway generation and resumes the agentic loop invisibly** — no new user
|
|
168
170
|
message is injected, using the
|
|
169
171
|
[pi-invisible-continue](https://github.com/monotykamary/pi-invisible-continue)
|
|
170
172
|
pattern (`agent.prompt([])`).
|
|
171
173
|
|
|
172
174
|
On abort, pi finalizes the in-flight assistant message **with its accumulated
|
|
173
|
-
|
|
174
|
-
before resuming — otherwise the model would just re-read its
|
|
175
|
-
again. Recovery retries **indefinitely with exponential backoff (2s→60s,
|
|
175
|
+
repeated-character content** into the transcript, so the guard drops that
|
|
176
|
+
partial message before resuming — otherwise the model would just re-read its
|
|
177
|
+
own repeated text and loop again. Recovery retries **indefinitely with exponential backoff (2s→60s,
|
|
176
178
|
2×)**, like [pi-retry](https://github.com/monotykamary/pi-retry) — long-horizon
|
|
177
179
|
agent work can trip the loop many times in a session, so a retry cap would
|
|
178
180
|
strand the agent mid-task. The loop exits only on a clean turn, a user abort
|
|
@@ -186,7 +188,10 @@ constants at the top of [`death-loop-guard.ts`](./death-loop-guard.ts):
|
|
|
186
188
|
| Constant | Default | Meaning |
|
|
187
189
|
|---|---|---|
|
|
188
190
|
| `GUARDED_MODEL_IDS` | `zai-org/GLM-5.2-NVFP4`, `zai-org/GLM-5.2-FP8` | Which model IDs to guard. Add `'*'` to guard every Makora model. |
|
|
189
|
-
| `
|
|
191
|
+
| `REPEAT_THRESHOLD` | `40` | Consecutive identical characters that trip the guard. Applies to any non-whitespace character (whitespace can legitimately repeat). 40 is far above anything normal prose/code produces. (`BANG_THRESHOLD` is kept as a deprecated alias.) |
|
|
192
|
+
| `IGNORED_REPEAT_CHARS` | `space, tab, LF, CR, FF, VT` | Characters whose repetition never trips the guard. |
|
|
193
|
+
| `TOKEN_REPEAT_THRESHOLD` | `40` | Consecutive identical whitespace-delimited tokens that trip the guard. Catches spaced loops like `0 0 0` that the character run can't see. |
|
|
194
|
+
| `TOKEN_REPEAT_BUFFER_CHARS` | `1024` | Tail length kept for token-run detection. Must hold ≥ `TOKEN_REPEAT_THRESHOLD` copies of the repeated unit. |
|
|
190
195
|
| `BACKOFF_BASE_MS` | `2000` | Initial recovery backoff delay. |
|
|
191
196
|
| `BACKOFF_MAX_MS` | `60000` | Maximum backoff delay (cap). |
|
|
192
197
|
| `BACKOFF_MULTIPLIER` | `2` | Backoff growth factor per retry. |
|
package/death-loop-guard.ts
CHANGED
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
* Death-loop guard for Makora reasoning models.
|
|
3
3
|
*
|
|
4
4
|
* Some Makora models (notably GLM 5.2 NVFP4 / FP8) occasionally fall into a
|
|
5
|
-
* degenerate repetition loop, emitting an unbroken run of
|
|
6
|
-
* (
|
|
5
|
+
* degenerate repetition loop, emitting an unbroken run of a single character
|
|
6
|
+
* (observed: '!' -> "!!!!..." and '0' -> "0000...") or a repeated token
|
|
7
|
+
* (observed: '0' -> "0 0 0 ...") that consumes the whole response. This guard
|
|
8
|
+
* watches the
|
|
7
9
|
* streamed assistant output (both the visible answer and the reasoning
|
|
8
10
|
* trace); when that run is detected it aborts the runaway generation, drops
|
|
9
11
|
* the partial (toxic) assistant message from the transcript, and resumes the
|
|
@@ -55,9 +57,45 @@ export const GUARDED_MODEL_IDS = new Set<string>([
|
|
|
55
57
|
"zai-org/GLM-5.2-FP8",
|
|
56
58
|
]);
|
|
57
59
|
|
|
58
|
-
/** Trip after this many consecutive
|
|
59
|
-
* 40 is far above anything normal prose or code produces
|
|
60
|
-
|
|
60
|
+
/** Trip after this many consecutive identical characters in the streamed
|
|
61
|
+
* answer. 40 is far above anything normal prose or code produces for any
|
|
62
|
+
* single non-whitespace character. */
|
|
63
|
+
export const REPEAT_THRESHOLD = 40;
|
|
64
|
+
|
|
65
|
+
/** @deprecated alias kept for downstream forks; prefer REPEAT_THRESHOLD.
|
|
66
|
+
* The guard now catches runs of any non-whitespace character, not just '!'. */
|
|
67
|
+
export const BANG_THRESHOLD = REPEAT_THRESHOLD;
|
|
68
|
+
|
|
69
|
+
/** Characters whose repetition we never trip on. Whitespace can legitimately
|
|
70
|
+
* repeat (code indentation, blank lines, markdown padding), so a long
|
|
71
|
+
* whitespace run is not treated as a degenerate loop. Every other character
|
|
72
|
+
* is a candidate. Exported for tests/introspection. */
|
|
73
|
+
export const IGNORED_REPEAT_CHARS = new Set<string>([
|
|
74
|
+
" ",
|
|
75
|
+
"\t",
|
|
76
|
+
"\n",
|
|
77
|
+
"\r",
|
|
78
|
+
"\f",
|
|
79
|
+
"\v",
|
|
80
|
+
]);
|
|
81
|
+
|
|
82
|
+
/** A trailing run of one repeated character in streamed text. `char` is ""
|
|
83
|
+
* when `len` === 0. */
|
|
84
|
+
export interface TrailingRun {
|
|
85
|
+
char: string;
|
|
86
|
+
len: number;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Trip after this many consecutive identical whitespace-delimited tokens.
|
|
90
|
+
* Catches spaced repetition ("0 0 0 ...", "!!!! !!!! ...") that the
|
|
91
|
+
* single-character run can't see (the separator resets it). 40 mirrors
|
|
92
|
+
* REPEAT_THRESHOLD. */
|
|
93
|
+
export const TOKEN_REPEAT_THRESHOLD = 40;
|
|
94
|
+
|
|
95
|
+
/** Bounded tail kept for token-run detection. Must hold >=
|
|
96
|
+
* TOKEN_REPEAT_THRESHOLD copies of the repeated unit; 1024 comfortably fits 40
|
|
97
|
+
* copies of tokens up to ~25 chars. */
|
|
98
|
+
export const TOKEN_REPEAT_BUFFER_CHARS = 1024;
|
|
61
99
|
|
|
62
100
|
/** Exponential backoff for recovery retries. Mirrors pi-retry's defaults:
|
|
63
101
|
* 2s base, 60s cap, 2× multiplier. Tunable via these constants. */
|
|
@@ -97,8 +135,22 @@ let _agent: GuardedAgent | null = null;
|
|
|
97
135
|
* only the loop driver re-issues prompt([]). */
|
|
98
136
|
let _recovering = false;
|
|
99
137
|
|
|
100
|
-
/** Trailing
|
|
101
|
-
let
|
|
138
|
+
/** Trailing run of one repeated character in the current text block. */
|
|
139
|
+
let _trailingRunChar = "";
|
|
140
|
+
let _trailingRunLen = 0;
|
|
141
|
+
|
|
142
|
+
/** Bounded trailing buffer for token-level repetition detection (e.g.
|
|
143
|
+
* "0 0 0 ...", "!!!! !!!! ..."). Single-char runs don't see spaced
|
|
144
|
+
* repetition, so we also tokenize the recent tail and count consecutive
|
|
145
|
+
* identical tokens. Capped to keep memory/cost bounded on long streams. */
|
|
146
|
+
let _tokenRepeatBuffer = "";
|
|
147
|
+
|
|
148
|
+
/** Reset all per-turn/per-block detection state. */
|
|
149
|
+
function resetDetection(): void {
|
|
150
|
+
_trailingRunChar = "";
|
|
151
|
+
_trailingRunLen = 0;
|
|
152
|
+
_tokenRepeatBuffer = "";
|
|
153
|
+
}
|
|
102
154
|
|
|
103
155
|
/** Latch: already tripped for the current assistant message. */
|
|
104
156
|
let _tripped = false;
|
|
@@ -131,17 +183,35 @@ export function isGuardedModel(
|
|
|
131
183
|
return model.id != null && GUARDED_MODEL_IDS.has(model.id);
|
|
132
184
|
}
|
|
133
185
|
|
|
134
|
-
/** Update the trailing-
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
|
|
186
|
+
/** Update the trailing single-character run given a new text delta.
|
|
187
|
+
* O(len(delta)). Tracks runs of any one character (so the guard catches a
|
|
188
|
+
* degenerate loop regardless of which character the model fixates on),
|
|
189
|
+
* including whitespace -- the trip decision in isDegenerateRun filters those.
|
|
190
|
+
*
|
|
191
|
+
* Trailing run depends only on the delta's suffix: if the delta ends with a
|
|
192
|
+
* different character than the prior run, the prior run is cut off and the
|
|
193
|
+
* new run is the delta's trailing run of its last character; if the delta is
|
|
194
|
+
* entirely the prior run's character, it extends the prior run. */
|
|
195
|
+
export function nextTrailingRun(prev: TrailingRun, delta: string): TrailingRun {
|
|
139
196
|
const len = delta.length;
|
|
140
197
|
if (len === 0) return prev;
|
|
198
|
+
const lastCode = delta.charCodeAt(len - 1);
|
|
141
199
|
let i = len - 1;
|
|
142
|
-
while (i >= 0 && delta.charCodeAt(i) ===
|
|
200
|
+
while (i >= 0 && delta.charCodeAt(i) === lastCode) i--;
|
|
201
|
+
const lastChar = delta[len - 1];
|
|
143
202
|
const trailingInDelta = len - 1 - i;
|
|
144
|
-
|
|
203
|
+
// Delta is entirely one char and it matches the prior run: extend it.
|
|
204
|
+
if (i < 0 && prev.len > 0 && prev.char === lastChar) {
|
|
205
|
+
return { char: prev.char, len: prev.len + len };
|
|
206
|
+
}
|
|
207
|
+
return { char: lastChar, len: trailingInDelta };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** @deprecated '!'-only alias kept for downstream forks. Returns the trailing
|
|
211
|
+
* '!' run length (0 once the run switches to any other character). */
|
|
212
|
+
export function nextTrailingBangs(prev: number, delta: string): number {
|
|
213
|
+
const run = nextTrailingRun({ char: "!", len: prev }, delta);
|
|
214
|
+
return run.char === "!" ? run.len : 0;
|
|
145
215
|
}
|
|
146
216
|
|
|
147
217
|
export function extractText(content: unknown): string {
|
|
@@ -163,12 +233,93 @@ export function extractText(content: unknown): string {
|
|
|
163
233
|
return "";
|
|
164
234
|
}
|
|
165
235
|
|
|
166
|
-
/** Trailing
|
|
167
|
-
export function
|
|
236
|
+
/** Trailing single-character run of a finalized message's text content. */
|
|
237
|
+
export function messageTrailingRun(msg: GuardedMessage): TrailingRun {
|
|
168
238
|
const text = extractText(msg.content);
|
|
239
|
+
if (text.length === 0) return { char: "", len: 0 };
|
|
240
|
+
const lastCode = text.charCodeAt(text.length - 1);
|
|
169
241
|
let i = text.length - 1;
|
|
170
|
-
while (i >= 0 && text.charCodeAt(i) ===
|
|
171
|
-
return text.length - 1 - i;
|
|
242
|
+
while (i >= 0 && text.charCodeAt(i) === lastCode) i--;
|
|
243
|
+
return { char: text[text.length - 1], len: text.length - 1 - i };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** @deprecated '!'-only alias kept for downstream forks. */
|
|
247
|
+
export function messageTrailingBangs(msg: GuardedMessage): number {
|
|
248
|
+
const run = messageTrailingRun(msg);
|
|
249
|
+
return run.char === "!" ? run.len : 0;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** True when a trailing run is long enough to be a degenerate loop. Whitespace
|
|
253
|
+
* runs never count (see IGNORED_REPEAT_CHARS). Exported so the trip decision
|
|
254
|
+
* is unit-testable independently of the streaming handler. */
|
|
255
|
+
export function isDegenerateRun(
|
|
256
|
+
run: TrailingRun,
|
|
257
|
+
threshold: number = REPEAT_THRESHOLD,
|
|
258
|
+
): boolean {
|
|
259
|
+
return (
|
|
260
|
+
run.len >= threshold &&
|
|
261
|
+
run.char !== "" &&
|
|
262
|
+
!IGNORED_REPEAT_CHARS.has(run.char)
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** A trailing run of one repeated whitespace-delimited token. `token` is ""
|
|
267
|
+
* when `count` === 0. */
|
|
268
|
+
export interface TokenRun {
|
|
269
|
+
token: string;
|
|
270
|
+
count: number;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/** Is `code` a whitespace code unit we split tokens on? Mirrors
|
|
274
|
+
* IGNORED_REPEAT_CHARS (space, tab, LF, CR, FF, VT). */
|
|
275
|
+
function isWhitespaceChar(code: number): boolean {
|
|
276
|
+
switch (code) {
|
|
277
|
+
case 0x20: case 0x09: case 0x0a: case 0x0d: case 0x0c: case 0x0b:
|
|
278
|
+
return true;
|
|
279
|
+
default:
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/** Trailing run of one repeated whitespace-delimited token in `text`. Walks
|
|
285
|
+
* backward from the end: skips trailing whitespace, captures the last token,
|
|
286
|
+
* then counts consecutive preceding copies separated only by whitespace.
|
|
287
|
+
* O(run length * token length). Used on a bounded tail buffer, so cost stays
|
|
288
|
+
* bounded regardless of total stream length. */
|
|
289
|
+
export function trailingTokenRun(text: string): TokenRun {
|
|
290
|
+
const len = text.length;
|
|
291
|
+
let end = len;
|
|
292
|
+
while (end > 0 && isWhitespaceChar(text.charCodeAt(end - 1))) end--;
|
|
293
|
+
if (end === 0) return { token: "", count: 0 };
|
|
294
|
+
let start = end;
|
|
295
|
+
while (start > 0 && !isWhitespaceChar(text.charCodeAt(start - 1))) start--;
|
|
296
|
+
const token = text.slice(start, end);
|
|
297
|
+
let count = 1;
|
|
298
|
+
let i = start;
|
|
299
|
+
while (i > 0) {
|
|
300
|
+
let j = i;
|
|
301
|
+
while (j > 0 && isWhitespaceChar(text.charCodeAt(j - 1))) j--;
|
|
302
|
+
if (j === 0) break;
|
|
303
|
+
let prevEnd = j;
|
|
304
|
+
let prevStart = prevEnd;
|
|
305
|
+
while (prevStart > 0 && !isWhitespaceChar(text.charCodeAt(prevStart - 1))) {
|
|
306
|
+
prevStart--;
|
|
307
|
+
}
|
|
308
|
+
if (text.slice(prevStart, prevEnd) !== token) break;
|
|
309
|
+
count++;
|
|
310
|
+
i = prevStart;
|
|
311
|
+
}
|
|
312
|
+
return { token, count };
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** True when a trailing token run is long enough to be a degenerate loop.
|
|
316
|
+
* Tokens are non-whitespace by construction, so no whitespace exclusion is
|
|
317
|
+
* needed (unlike isDegenerateRun). Exported for unit testing. */
|
|
318
|
+
export function isDegenerateTokenRun(
|
|
319
|
+
run: TokenRun,
|
|
320
|
+
threshold: number = TOKEN_REPEAT_THRESHOLD,
|
|
321
|
+
): boolean {
|
|
322
|
+
return run.count >= threshold && run.token !== "";
|
|
172
323
|
}
|
|
173
324
|
|
|
174
325
|
/** Exponential backoff delay, capped at maxDelayMs. Mirrors pi-retry. */
|
|
@@ -219,7 +370,8 @@ function trimAbortedDeathLoop(agent: GuardedAgent): void {
|
|
|
219
370
|
last &&
|
|
220
371
|
last.role === "assistant" &&
|
|
221
372
|
(last.stopReason === "aborted" ||
|
|
222
|
-
|
|
373
|
+
isDegenerateRun(messageTrailingRun(last)) ||
|
|
374
|
+
isDegenerateTokenRun(trailingTokenRun(extractText(last.content))))
|
|
223
375
|
) {
|
|
224
376
|
agent.state.messages = msgs.slice(0, -1);
|
|
225
377
|
}
|
|
@@ -252,8 +404,8 @@ async function triggerRecovery(): Promise<void> {
|
|
|
252
404
|
const delay = calculateDelay(attempt);
|
|
253
405
|
if (_notifyFn) {
|
|
254
406
|
_notifyFn(
|
|
255
|
-
`Makora death-loop guard: resuming after runaway
|
|
256
|
-
`(retry ${attempt}, backoff ${formatDuration(delay)})...`,
|
|
407
|
+
`Makora death-loop guard: resuming after runaway repeated-character ` +
|
|
408
|
+
`output (retry ${attempt}, backoff ${formatDuration(delay)})...`,
|
|
257
409
|
"warning",
|
|
258
410
|
);
|
|
259
411
|
}
|
|
@@ -311,7 +463,7 @@ export function registerDeathLoopGuard(pi: ExtensionAPI): void {
|
|
|
311
463
|
// exits within 100ms (during backoff) or right after prompt([]) returns.
|
|
312
464
|
_sessionGeneration++;
|
|
313
465
|
_recovering = false;
|
|
314
|
-
|
|
466
|
+
resetDetection();
|
|
315
467
|
_tripped = false;
|
|
316
468
|
_weAborted = false;
|
|
317
469
|
_userAborted = false;
|
|
@@ -321,7 +473,7 @@ export function registerDeathLoopGuard(pi: ExtensionAPI): void {
|
|
|
321
473
|
// not for the recovery's direct agent.prompt([]), so these resets bound
|
|
322
474
|
// detection to the current user prompt without clearing mid-recovery.
|
|
323
475
|
pi.on("before_agent_start", () => {
|
|
324
|
-
|
|
476
|
+
resetDetection();
|
|
325
477
|
_tripped = false;
|
|
326
478
|
_weAborted = false;
|
|
327
479
|
// A new user prompt is fresh activity — clear a stale user-abort flag.
|
|
@@ -330,7 +482,7 @@ export function registerDeathLoopGuard(pi: ExtensionAPI): void {
|
|
|
330
482
|
|
|
331
483
|
pi.on("message_start", (event) => {
|
|
332
484
|
if (event.message?.role === "assistant") {
|
|
333
|
-
|
|
485
|
+
resetDetection();
|
|
334
486
|
_tripped = false;
|
|
335
487
|
_weAborted = false;
|
|
336
488
|
}
|
|
@@ -344,8 +496,8 @@ export function registerDeathLoopGuard(pi: ExtensionAPI): void {
|
|
|
344
496
|
if (_recovering || _tripped) return;
|
|
345
497
|
const ame = event.assistantMessageEvent;
|
|
346
498
|
if (ame.type === "text_start" || ame.type === "thinking_start") {
|
|
347
|
-
// New content block (answer or reasoning) —
|
|
348
|
-
|
|
499
|
+
// New content block (answer or reasoning) — detection starts fresh.
|
|
500
|
+
resetDetection();
|
|
349
501
|
return;
|
|
350
502
|
}
|
|
351
503
|
// Watch both the visible answer and the reasoning trace: GLM 5.2 is a
|
|
@@ -353,8 +505,22 @@ export function registerDeathLoopGuard(pi: ExtensionAPI): void {
|
|
|
353
505
|
if (ame.type !== "text_delta" && ame.type !== "thinking_delta") return;
|
|
354
506
|
if (!isGuardedModel(ctx.model)) return;
|
|
355
507
|
|
|
356
|
-
|
|
357
|
-
|
|
508
|
+
const run = nextTrailingRun(
|
|
509
|
+
{ char: _trailingRunChar, len: _trailingRunLen },
|
|
510
|
+
ame.delta,
|
|
511
|
+
);
|
|
512
|
+
_trailingRunChar = run.char;
|
|
513
|
+
_trailingRunLen = run.len;
|
|
514
|
+
|
|
515
|
+
// Token-level repetition: a spaced loop like "0 0 0 ..." or "!!!! !!!!"
|
|
516
|
+
// never builds a single-char run (the separator resets it), so track the
|
|
517
|
+
// recent tail as whitespace-delimited tokens and count consecutive copies.
|
|
518
|
+
_tokenRepeatBuffer = (_tokenRepeatBuffer + ame.delta).slice(
|
|
519
|
+
-TOKEN_REPEAT_BUFFER_CHARS,
|
|
520
|
+
);
|
|
521
|
+
const tokenRun = trailingTokenRun(_tokenRepeatBuffer);
|
|
522
|
+
|
|
523
|
+
if (!isDegenerateRun(run) && !isDegenerateTokenRun(tokenRun)) return;
|
|
358
524
|
|
|
359
525
|
// Trip: abort the runaway stream and kick off the recovery loop (once,
|
|
360
526
|
// mutex-gated). The loop driver handles re-detection on resumed turns.
|
|
@@ -363,8 +529,8 @@ export function registerDeathLoopGuard(pi: ExtensionAPI): void {
|
|
|
363
529
|
const agent = _agent;
|
|
364
530
|
if (!agent) {
|
|
365
531
|
ctx.ui.notify(
|
|
366
|
-
"Makora death-loop guard: runaway
|
|
367
|
-
"instance was not captured; cannot recover automatically.",
|
|
532
|
+
"Makora death-loop guard: runaway repeated-character output detected " +
|
|
533
|
+
"but the Agent instance was not captured; cannot recover automatically.",
|
|
368
534
|
"warning",
|
|
369
535
|
);
|
|
370
536
|
return;
|
package/index.ts
CHANGED
|
@@ -22,9 +22,11 @@
|
|
|
22
22
|
*
|
|
23
23
|
* A death-loop guard (see ./death-loop-guard.ts) is registered alongside the
|
|
24
24
|
* provider. It watches the assistant text stream on the GLM 5.2 family and,
|
|
25
|
-
* if the model falls into
|
|
26
|
-
*
|
|
27
|
-
*
|
|
25
|
+
* if the model falls into a degenerate repetition loop — an unbroken run of a
|
|
26
|
+
* single character or token, spaced or not (observed: `!!!!`, `0000`, `0 0 0`)
|
|
27
|
+
* — it aborts the runaway generation and resumes the agentic loop invisibly
|
|
28
|
+
* via agent.prompt([]) (the pi-invisible-continue pattern); no new user
|
|
29
|
+
* message is injected.
|
|
28
30
|
*
|
|
29
31
|
* Developer role is NOT supported by any of the chat templates on Makora's
|
|
30
32
|
* vLLM deployment (prompts with role: "developer" are silently dropped).
|
|
@@ -213,7 +215,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
213
215
|
models,
|
|
214
216
|
});
|
|
215
217
|
|
|
216
|
-
// Abort runaway
|
|
218
|
+
// Abort runaway repetition loops — a single character or token, spaced or
|
|
219
|
+
// not (e.g. '!!!!', '0000', '0 0 0') — on the GLM 5.2 family and resume the
|
|
217
220
|
// agentic loop invisibly (no new user message). See ./death-loop-guard.ts.
|
|
218
221
|
registerDeathLoopGuard(pi);
|
|
219
222
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-makora-provider",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "Makora provider extension for pi - Access DeepSeek V4, GLM 5.2, Kimi K2.7 Code, Llama 3.3, Qwen 3.6, and more through the Makora inference API",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|