@diegopetrucci/pi-extensions 0.1.72 → 0.1.74
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 +2 -2
- package/extensions/agent-workflow-audit/index.ts +41 -8
- package/extensions/agent-workflow-audit/package.json +1 -1
- package/extensions/annotate-git-diff/README.md +7 -3
- package/extensions/annotate-git-diff/git.ts +3 -2
- package/extensions/annotate-git-diff/index.ts +366 -81
- package/extensions/annotate-git-diff/package.json +2 -2
- package/extensions/annotate-git-diff/quiet-glimpse.ts +4 -2
- package/extensions/annotate-git-diff/types.ts +6 -0
- package/extensions/annotate-git-diff/web/app.js +54 -4
- package/extensions/annotate-git-diff/web/index.html +2 -2
- package/extensions/annotate-last-message/README.md +9 -1
- package/extensions/annotate-last-message/index.ts +53 -13
- package/extensions/annotate-last-message/package.json +2 -1
- package/extensions/annotate-last-message/quiet-glimpse.ts +4 -2
- package/extensions/annotate-last-message/theme.ts +247 -0
- package/extensions/annotate-last-message/ui.ts +28 -2
- package/extensions/annotate-last-message/web/app.js +351 -162
- package/extensions/annotate-last-message/web/index.html +153 -24
- package/extensions/annotate-last-message/web/md-renderer.js +458 -0
- package/extensions/code-reviewer/index.ts +12 -1
- package/extensions/code-reviewer/package.json +2 -1
- package/extensions/context-cap/README.md +33 -4
- package/extensions/context-cap/index.ts +271 -68
- package/extensions/context-cap/package.json +1 -1
- package/extensions/context-inspector/README.md +2 -0
- package/extensions/context-inspector/index.ts +90 -9
- package/extensions/context-inspector/package.json +1 -1
- package/extensions/contrarian/index.ts +21 -15
- package/extensions/contrarian/package.json +1 -1
- package/extensions/dynamic-context-pruning/README.md +17 -5
- package/extensions/dynamic-context-pruning/index.ts +17 -4
- package/extensions/dynamic-context-pruning/package.json +1 -1
- package/extensions/fast/README.md +36 -3
- package/extensions/fast/index.ts +6 -2
- package/extensions/fast/package.json +1 -1
- package/extensions/git-footer/README.md +1 -0
- package/extensions/git-footer/package.json +1 -1
- package/extensions/librarian/index.ts +7 -0
- package/extensions/librarian/package.json +2 -1
- package/extensions/minimal-footer/README.md +16 -4
- package/extensions/minimal-footer/index.ts +263 -14
- package/extensions/minimal-footer/openai-usage.ts +62 -8
- package/extensions/minimal-footer/package.json +1 -1
- package/extensions/oracle/index.ts +19 -18
- package/extensions/oracle/package.json +1 -1
- package/extensions/permission-gate/README.md +6 -0
- package/extensions/permission-gate/index.ts +37 -16
- package/extensions/permission-gate/package.json +1 -1
- package/extensions/quiet-tools/README.md +1 -1
- package/extensions/quiet-tools/package.json +1 -1
- package/extensions/triage-comments/package.json +2 -1
- package/package.json +4 -4
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
/* global globalThis */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* TLH annotate-last-message: pure markdown line parser.
|
|
5
|
+
*
|
|
6
|
+
* Single source of truth for the parsing logic. ui.ts reads this file and
|
|
7
|
+
* inlines it as a dedicated script before app.js, so globalThis.__tlhMdRenderer
|
|
8
|
+
* is available when the app script runs. Tests load this file directly in a
|
|
9
|
+
* Node.js vm context (no DOM required).
|
|
10
|
+
*/
|
|
11
|
+
(function (global) {
|
|
12
|
+
"use strict";
|
|
13
|
+
|
|
14
|
+
// -------------------------------------------------------------------------
|
|
15
|
+
// Fence-state machine
|
|
16
|
+
//
|
|
17
|
+
// Applied over the full ordered line list before per-line rendering begins.
|
|
18
|
+
// Each line is tagged as: 'plain' | 'fence-open' | 'fence-body' | 'fence-close'
|
|
19
|
+
//
|
|
20
|
+
// Fence-delimiter lines (the ``` or ~~~ lines) are tagged 'fence-open' or
|
|
21
|
+
// 'fence-close'; they are rendered visible but styled as block-edge markers
|
|
22
|
+
// (--mdCodeBlockBorder colour) rather than suppressed. Fence-body lines skip
|
|
23
|
+
// inline markdown processing and receive code-block styling instead.
|
|
24
|
+
//
|
|
25
|
+
// A fenced block that reaches end-of-input without a closing delimiter is
|
|
26
|
+
// left open; all remaining lines are tagged 'fence-body'.
|
|
27
|
+
// -------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
function isFenceDelimiter(text) {
|
|
30
|
+
// CommonMark §4.5: opening/closing fences allow at most 3 spaces of
|
|
31
|
+
// indentation. A line with 4+ leading spaces is an indented code block
|
|
32
|
+
// and must not be treated as a fence delimiter.
|
|
33
|
+
const m = /^ {0,3}(```+|~~~+)/.exec(text);
|
|
34
|
+
return m ? { char: m[1][0], len: m[1].length } : null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* applyFenceState(lineTexts) → Array<{ text: string, lineType: string }>
|
|
39
|
+
*
|
|
40
|
+
* One output entry per input entry; the count is always preserved.
|
|
41
|
+
*/
|
|
42
|
+
function applyFenceState(lineTexts) {
|
|
43
|
+
const result = [];
|
|
44
|
+
let inFence = false;
|
|
45
|
+
let fenceChar = "";
|
|
46
|
+
let fenceLen = 0;
|
|
47
|
+
|
|
48
|
+
for (const text of lineTexts) {
|
|
49
|
+
const fence = isFenceDelimiter(text);
|
|
50
|
+
|
|
51
|
+
if (!inFence) {
|
|
52
|
+
if (fence) {
|
|
53
|
+
inFence = true;
|
|
54
|
+
fenceChar = fence.char;
|
|
55
|
+
fenceLen = fence.len;
|
|
56
|
+
result.push({ text, lineType: "fence-open" });
|
|
57
|
+
} else {
|
|
58
|
+
result.push({ text, lineType: "plain" });
|
|
59
|
+
}
|
|
60
|
+
} else {
|
|
61
|
+
// Close if same delimiter character, at least as many chars, and
|
|
62
|
+
// nothing but optional whitespace after the delimiter (CommonMark
|
|
63
|
+
// §4.5: a closing fence must not have trailing non-whitespace).
|
|
64
|
+
const afterDelim = fence ? text.replace(/^ {0,3}(?:```+|~~~+)/, "") : "";
|
|
65
|
+
if (
|
|
66
|
+
fence &&
|
|
67
|
+
fence.char === fenceChar &&
|
|
68
|
+
fence.len >= fenceLen &&
|
|
69
|
+
/^\s*$/.test(afterDelim)
|
|
70
|
+
) {
|
|
71
|
+
inFence = false;
|
|
72
|
+
fenceChar = "";
|
|
73
|
+
fenceLen = 0;
|
|
74
|
+
result.push({ text, lineType: "fence-close" });
|
|
75
|
+
} else {
|
|
76
|
+
result.push({ text, lineType: "fence-body" });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// -------------------------------------------------------------------------
|
|
85
|
+
// Line classifier (called only on 'plain'-typed lines)
|
|
86
|
+
// -------------------------------------------------------------------------
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* classifyLine(text) → LineInfo
|
|
90
|
+
*
|
|
91
|
+
* Returned shapes:
|
|
92
|
+
* { type: 'blank' }
|
|
93
|
+
* { type: 'heading', level: 1-6, text: string }
|
|
94
|
+
* { type: 'hr' }
|
|
95
|
+
* { type: 'blockquote', text: string }
|
|
96
|
+
* { type: 'ul', indent: string, bullet: string, text: string }
|
|
97
|
+
* { type: 'ol', indent: string, bullet: string, text: string }
|
|
98
|
+
* { type: 'plain', text: string }
|
|
99
|
+
*/
|
|
100
|
+
function classifyLine(text) {
|
|
101
|
+
if (text.trim().length === 0) {
|
|
102
|
+
return { type: "blank" };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const trimmed = text.trimStart();
|
|
106
|
+
const indentLen = text.length - trimmed.length;
|
|
107
|
+
|
|
108
|
+
// CommonMark block-indentation rule: constructs that HIDE or REPLACE
|
|
109
|
+
// source characters (headings hide '#', thematic breaks replace the
|
|
110
|
+
// whole line, blockquotes hide '>') must have at most 3 spaces of
|
|
111
|
+
// leading indentation. At 4+ spaces the line is indented code and
|
|
112
|
+
// must render literally. List markers are exempt: they preserve every
|
|
113
|
+
// source character and only restyle the bullet, so nested-list
|
|
114
|
+
// indentation stays lenient.
|
|
115
|
+
const blockSafe = indentLen <= 3;
|
|
116
|
+
|
|
117
|
+
// ATX heading: # … through ###### …
|
|
118
|
+
// The heading content may optionally end with a closing # sequence.
|
|
119
|
+
if (blockSafe) {
|
|
120
|
+
const headingMatch = /^(#{1,6})\s+(.+?)(?:\s+#+\s*)?$/.exec(trimmed);
|
|
121
|
+
if (headingMatch) {
|
|
122
|
+
return { type: "heading", level: headingMatch[1].length, text: headingMatch[2] };
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Thematic break: 3+ dashes, stars, or underscores with optional spaces.
|
|
127
|
+
if (blockSafe) {
|
|
128
|
+
if (/^(?:(?:-[ \t]*){3,}|(?:\*[ \t]*){3,}|(?:_[ \t]*){3,})$/.test(trimmed)) {
|
|
129
|
+
return { type: "hr" };
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Blockquote: > text
|
|
134
|
+
if (blockSafe) {
|
|
135
|
+
const bqMatch = /^>[ \t]?(.*)/.exec(trimmed);
|
|
136
|
+
if (bqMatch) {
|
|
137
|
+
return { type: "blockquote", text: bqMatch[1] };
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Unordered list item: optional indent + (- * +) + one or more spaces + text
|
|
142
|
+
const ulMatch = /^(\s*)([-*+])[ \t]+(.*)/.exec(text);
|
|
143
|
+
if (ulMatch) {
|
|
144
|
+
return { type: "ul", indent: ulMatch[1], bullet: ulMatch[2], text: ulMatch[3] };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Ordered list item: optional indent + number. + one or more spaces + text
|
|
148
|
+
const olMatch = /^(\s*)(\d+\.)[ \t]+(.*)/.exec(text);
|
|
149
|
+
if (olMatch) {
|
|
150
|
+
return { type: "ol", indent: olMatch[1], bullet: olMatch[2], text: olMatch[3] };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return { type: "plain", text };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// -------------------------------------------------------------------------
|
|
157
|
+
// Inline tokenizer
|
|
158
|
+
// -------------------------------------------------------------------------
|
|
159
|
+
|
|
160
|
+
function isMarkdownSpecial(ch) {
|
|
161
|
+
return ch === "*" || ch === "_" || ch === "~" || ch === "`" || ch === "[" || ch === "\\";
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Try to parse an inline code span starting at position i.
|
|
166
|
+
* Returns { codeText, end } on success, null on failure.
|
|
167
|
+
*/
|
|
168
|
+
function tryParseCodeSpan(text, i) {
|
|
169
|
+
// Count opening backtick run.
|
|
170
|
+
let tickCount = 0;
|
|
171
|
+
while (i + tickCount < text.length && text[i + tickCount] === "`") tickCount++;
|
|
172
|
+
const contentStart = i + tickCount;
|
|
173
|
+
|
|
174
|
+
// Find a closing run of exactly the same length.
|
|
175
|
+
let j = contentStart;
|
|
176
|
+
while (j < text.length) {
|
|
177
|
+
if (text[j] !== "`") {
|
|
178
|
+
j++;
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
let runLen = 0;
|
|
182
|
+
while (j + runLen < text.length && text[j + runLen] === "`") runLen++;
|
|
183
|
+
if (runLen === tickCount) {
|
|
184
|
+
return { codeText: text.slice(contentStart, j), end: j + tickCount };
|
|
185
|
+
}
|
|
186
|
+
j += runLen;
|
|
187
|
+
}
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Find the closing position of an emphasis/strikethrough marker.
|
|
193
|
+
* Skips over backslash escapes and inline code spans.
|
|
194
|
+
* For single-char markers (star or underscore) the closer must be an
|
|
195
|
+
* isolated run of exactly 1; for 2- or 3-char markers the run must be
|
|
196
|
+
* exactly that length. Returns -1 if not found.
|
|
197
|
+
*
|
|
198
|
+
* When checkIntraword is true (used for underscore-based delimiters),
|
|
199
|
+
* a candidate closing run that is flanked by alphanumerics on both sides
|
|
200
|
+
* is skipped — CommonMark's intraword-underscore rule.
|
|
201
|
+
*/
|
|
202
|
+
function findEmphasisCloser(text, marker, from, checkIntraword = false) {
|
|
203
|
+
const mLen = marker.length;
|
|
204
|
+
const mChar = marker[0];
|
|
205
|
+
let j = from;
|
|
206
|
+
|
|
207
|
+
while (j < text.length) {
|
|
208
|
+
// Backslash escape: skip the next character.
|
|
209
|
+
if (text[j] === "\\" && j + 1 < text.length) {
|
|
210
|
+
j += 2;
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Inline code span: skip its entire content.
|
|
215
|
+
if (text[j] === "`") {
|
|
216
|
+
let tickCount = 0;
|
|
217
|
+
while (j + tickCount < text.length && text[j + tickCount] === "`") tickCount++;
|
|
218
|
+
const closeAt = text.indexOf("`".repeat(tickCount), j + tickCount);
|
|
219
|
+
j = closeAt === -1 ? text.length : closeAt + tickCount;
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (text[j] === mChar) {
|
|
224
|
+
// Measure the run length at this position.
|
|
225
|
+
let runLen = 0;
|
|
226
|
+
while (j + runLen < text.length && text[j + runLen] === mChar) runLen++;
|
|
227
|
+
|
|
228
|
+
if (runLen === mLen) {
|
|
229
|
+
if (checkIntraword) {
|
|
230
|
+
// Underscore-based closing delimiter flanked by alphanumerics
|
|
231
|
+
// on both sides is intraword and cannot close emphasis.
|
|
232
|
+
const charBefore = j > 0 ? text[j - 1] : "";
|
|
233
|
+
const charAfter = j + mLen < text.length ? text[j + mLen] : "";
|
|
234
|
+
if (/[a-zA-Z0-9]/.test(charBefore) && /[a-zA-Z0-9]/.test(charAfter)) {
|
|
235
|
+
j += runLen;
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return j;
|
|
240
|
+
}
|
|
241
|
+
// Wrong run length — skip the whole run and keep scanning.
|
|
242
|
+
j += runLen;
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
j++;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return -1;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* tokenizeLine(text) → Token[]
|
|
254
|
+
*
|
|
255
|
+
* Parses inline markdown and returns a token tree. Falls back to a single
|
|
256
|
+
* plain-text token if an error is thrown.
|
|
257
|
+
*
|
|
258
|
+
* Token shapes:
|
|
259
|
+
* { type: 'text', text: string }
|
|
260
|
+
* { type: 'code', text: string }
|
|
261
|
+
* { type: 'bold', children: Token[] }
|
|
262
|
+
* { type: 'italic', children: Token[] }
|
|
263
|
+
* { type: 'boldItalic', children: Token[] }
|
|
264
|
+
* { type: 'strikethrough', children: Token[] }
|
|
265
|
+
* { type: 'link', labelTokens: Token[], url: string }
|
|
266
|
+
*/
|
|
267
|
+
function tokenizeLine(text) {
|
|
268
|
+
try {
|
|
269
|
+
return parseInlineSpans(text);
|
|
270
|
+
} catch {
|
|
271
|
+
return [{ type: "text", text }];
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function parseInlineSpans(text) {
|
|
276
|
+
const tokens = [];
|
|
277
|
+
let i = 0;
|
|
278
|
+
let buf = "";
|
|
279
|
+
let bracketMatches = null;
|
|
280
|
+
|
|
281
|
+
// Match raw brackets once per parse call; suffix rescans make runs quadratic.
|
|
282
|
+
function getBracketMatches() {
|
|
283
|
+
if (bracketMatches !== null) return bracketMatches;
|
|
284
|
+
|
|
285
|
+
bracketMatches = new Map();
|
|
286
|
+
const stack = [];
|
|
287
|
+
for (let k = 0; k < text.length; k++) {
|
|
288
|
+
if (text[k] === "[") {
|
|
289
|
+
stack.push(k);
|
|
290
|
+
} else if (text[k] === "]" && stack.length > 0) {
|
|
291
|
+
bracketMatches.set(stack.pop(), k);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return bracketMatches;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function flush() {
|
|
298
|
+
if (buf.length > 0) {
|
|
299
|
+
tokens.push({ type: "text", text: buf });
|
|
300
|
+
buf = "";
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
while (i < text.length) {
|
|
305
|
+
const ch = text[i];
|
|
306
|
+
|
|
307
|
+
// Backslash escape: treat next markdown-special character as literal.
|
|
308
|
+
if (ch === "\\" && i + 1 < text.length && isMarkdownSpecial(text[i + 1])) {
|
|
309
|
+
buf += text[i + 1];
|
|
310
|
+
i += 2;
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Inline code span.
|
|
315
|
+
if (ch === "`") {
|
|
316
|
+
const result = tryParseCodeSpan(text, i);
|
|
317
|
+
if (result) {
|
|
318
|
+
flush();
|
|
319
|
+
tokens.push({ type: "code", text: result.codeText });
|
|
320
|
+
i = result.end;
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
buf += ch;
|
|
324
|
+
i++;
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Link: [label](url)
|
|
329
|
+
if (ch === "[") {
|
|
330
|
+
const closeLabel = getBracketMatches().get(i) ?? -1;
|
|
331
|
+
if (closeLabel !== -1 && text[closeLabel + 1] === "(") {
|
|
332
|
+
const urlStart = closeLabel + 2;
|
|
333
|
+
const closeUrl = text.indexOf(")", urlStart);
|
|
334
|
+
if (closeUrl !== -1) {
|
|
335
|
+
flush();
|
|
336
|
+
const labelText = text.slice(i + 1, closeLabel);
|
|
337
|
+
const url = text.slice(urlStart, closeUrl);
|
|
338
|
+
tokens.push({ type: "link", labelTokens: parseInlineSpans(labelText), url });
|
|
339
|
+
i = closeUrl + 1;
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
buf += ch;
|
|
344
|
+
i++;
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Bold+italic: ***text*** or ___text___
|
|
349
|
+
if (
|
|
350
|
+
(ch === "*" && text[i + 1] === "*" && text[i + 2] === "*") ||
|
|
351
|
+
(ch === "_" && text[i + 1] === "_" && text[i + 2] === "_")
|
|
352
|
+
) {
|
|
353
|
+
// Underscore-based opening: skip if flanked by alphanumerics
|
|
354
|
+
// on both sides (CommonMark intraword-underscore rule).
|
|
355
|
+
if (ch === "_") {
|
|
356
|
+
const charBefore = i > 0 ? text[i - 1] : "";
|
|
357
|
+
const charAfter = i + 3 < text.length ? text[i + 3] : "";
|
|
358
|
+
if (/[a-zA-Z0-9]/.test(charBefore) && /[a-zA-Z0-9]/.test(charAfter)) {
|
|
359
|
+
buf += ch.repeat(3);
|
|
360
|
+
i += 3;
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
const marker = ch.repeat(3);
|
|
365
|
+
const closeIdx = findEmphasisCloser(text, marker, i + 3, ch === "_");
|
|
366
|
+
if (closeIdx !== -1) {
|
|
367
|
+
const inner = text.slice(i + 3, closeIdx);
|
|
368
|
+
if (inner.length > 0 && inner[0] !== " " && inner[inner.length - 1] !== " ") {
|
|
369
|
+
flush();
|
|
370
|
+
tokens.push({ type: "boldItalic", children: parseInlineSpans(inner) });
|
|
371
|
+
i = closeIdx + 3;
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Bold: **text** or __text__
|
|
378
|
+
if (
|
|
379
|
+
(ch === "*" && text[i + 1] === "*" && text[i + 2] !== "*") ||
|
|
380
|
+
(ch === "_" && text[i + 1] === "_" && text[i + 2] !== "_")
|
|
381
|
+
) {
|
|
382
|
+
// Underscore-based opening: skip if flanked by alphanumerics
|
|
383
|
+
// on both sides (CommonMark intraword-underscore rule).
|
|
384
|
+
if (ch === "_") {
|
|
385
|
+
const charBefore = i > 0 ? text[i - 1] : "";
|
|
386
|
+
const charAfter = i + 2 < text.length ? text[i + 2] : "";
|
|
387
|
+
if (/[a-zA-Z0-9]/.test(charBefore) && /[a-zA-Z0-9]/.test(charAfter)) {
|
|
388
|
+
buf += ch.repeat(2);
|
|
389
|
+
i += 2;
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
const marker = ch.repeat(2);
|
|
394
|
+
const closeIdx = findEmphasisCloser(text, marker, i + 2, ch === "_");
|
|
395
|
+
if (closeIdx !== -1) {
|
|
396
|
+
const inner = text.slice(i + 2, closeIdx);
|
|
397
|
+
if (inner.length > 0 && inner[0] !== " " && inner[inner.length - 1] !== " ") {
|
|
398
|
+
flush();
|
|
399
|
+
tokens.push({ type: "bold", children: parseInlineSpans(inner) });
|
|
400
|
+
i = closeIdx + 2;
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Strikethrough: ~~text~~
|
|
407
|
+
if (ch === "~" && text[i + 1] === "~") {
|
|
408
|
+
const closeIdx = text.indexOf("~~", i + 2);
|
|
409
|
+
if (closeIdx !== -1) {
|
|
410
|
+
const inner = text.slice(i + 2, closeIdx);
|
|
411
|
+
if (inner.length > 0) {
|
|
412
|
+
flush();
|
|
413
|
+
tokens.push({ type: "strikethrough", children: parseInlineSpans(inner) });
|
|
414
|
+
i = closeIdx + 2;
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Italic: *text* or _text_ (single marker, not start of ** or ***)
|
|
421
|
+
if ((ch === "*" && text[i + 1] !== "*") || (ch === "_" && text[i + 1] !== "_")) {
|
|
422
|
+
// Underscore-based opening: skip if flanked by alphanumerics
|
|
423
|
+
// on both sides (CommonMark intraword-underscore rule).
|
|
424
|
+
if (ch === "_") {
|
|
425
|
+
const charBefore = i > 0 ? text[i - 1] : "";
|
|
426
|
+
const charAfter = i + 1 < text.length ? text[i + 1] : "";
|
|
427
|
+
if (/[a-zA-Z0-9]/.test(charBefore) && /[a-zA-Z0-9]/.test(charAfter)) {
|
|
428
|
+
buf += ch;
|
|
429
|
+
i += 1;
|
|
430
|
+
continue;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
const closeIdx = findEmphasisCloser(text, ch, i + 1, ch === "_");
|
|
434
|
+
if (closeIdx !== -1) {
|
|
435
|
+
const inner = text.slice(i + 1, closeIdx);
|
|
436
|
+
if (inner.length > 0 && inner[0] !== " " && inner[inner.length - 1] !== " ") {
|
|
437
|
+
flush();
|
|
438
|
+
tokens.push({ type: "italic", children: parseInlineSpans(inner) });
|
|
439
|
+
i = closeIdx + 1;
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
buf += ch;
|
|
446
|
+
i++;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
flush();
|
|
450
|
+
return tokens;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
global.__tlhMdRenderer = {
|
|
454
|
+
applyFenceState,
|
|
455
|
+
classifyLine,
|
|
456
|
+
tokenizeLine,
|
|
457
|
+
};
|
|
458
|
+
})(globalThis);
|
|
@@ -29,6 +29,11 @@ const CODE_REVIEWER_MODEL_PREFERENCES = [
|
|
|
29
29
|
"openai.gpt-6-astra ",
|
|
30
30
|
"gpt-6-astra ",
|
|
31
31
|
"gpt-6-astra",
|
|
32
|
+
"openai/gpt-6.1-sol ",
|
|
33
|
+
"global.openai.gpt-6.1-sol ",
|
|
34
|
+
"us.openai.gpt-6.1-sol ",
|
|
35
|
+
"openai.gpt-6.1-sol ",
|
|
36
|
+
"gpt-6.1-sol ",
|
|
32
37
|
"openai/gpt-6-sol ",
|
|
33
38
|
"gpt-6-sol ",
|
|
34
39
|
"gpt-6-sol",
|
|
@@ -100,6 +105,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
|
|
|
100
105
|
"azure-openai-responses": [
|
|
101
106
|
"gpt-6-astra ",
|
|
102
107
|
"gpt-6-astra",
|
|
108
|
+
"gpt-6.1-sol ",
|
|
103
109
|
"gpt-6-sol ",
|
|
104
110
|
"gpt-6-sol",
|
|
105
111
|
"gpt-5.6-sol",
|
|
@@ -137,6 +143,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
|
|
|
137
143
|
"github-copilot": [
|
|
138
144
|
"gpt-6-astra ",
|
|
139
145
|
"gpt-6-astra",
|
|
146
|
+
"gpt-6.1-sol ",
|
|
140
147
|
"gpt-6-sol ",
|
|
141
148
|
"gpt-6-sol",
|
|
142
149
|
"gpt-5.6-sol",
|
|
@@ -159,6 +166,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
|
|
|
159
166
|
opencode: [
|
|
160
167
|
"gpt-6-astra ",
|
|
161
168
|
"gpt-6-astra",
|
|
169
|
+
"gpt-6.1-sol ",
|
|
162
170
|
"gpt-6-sol ",
|
|
163
171
|
"gpt-6-sol",
|
|
164
172
|
"gpt-5.6-sol",
|
|
@@ -183,6 +191,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
|
|
|
183
191
|
openrouter: [
|
|
184
192
|
"openai/gpt-6-astra ",
|
|
185
193
|
"openai/gpt-6-astra",
|
|
194
|
+
"openai/gpt-6.1-sol ",
|
|
186
195
|
"openai/gpt-6-sol ",
|
|
187
196
|
"openai/gpt-6-sol",
|
|
188
197
|
"openai/gpt-5.6-sol-pro",
|
|
@@ -221,10 +230,11 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
|
|
|
221
230
|
"gpt-5.3-codex",
|
|
222
231
|
"gpt-5.4-mini",
|
|
223
232
|
],
|
|
224
|
-
openai: ["gpt-6-astra ", "gpt-6-astra", "gpt-6-sol ", "gpt-6-sol", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-6-luna ", "gpt-6-luna", "gpt-5.6-luna", "gpt-5.5", "gpt-5", "gpt-4.1", "o3", "o4-mini"],
|
|
233
|
+
openai: ["gpt-6-astra ", "gpt-6-astra", "gpt-6.1-sol ", "gpt-6-sol ", "gpt-6-sol", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-6-luna ", "gpt-6-luna", "gpt-5.6-luna", "gpt-5.5", "gpt-5", "gpt-4.1", "o3", "o4-mini"],
|
|
225
234
|
"openai-codex": [
|
|
226
235
|
"gpt-6-astra ",
|
|
227
236
|
"gpt-6-astra",
|
|
237
|
+
"gpt-6.1-sol ",
|
|
228
238
|
"gpt-6-sol ",
|
|
229
239
|
"gpt-6-sol",
|
|
230
240
|
"gpt-5.6-sol",
|
|
@@ -247,6 +257,7 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
|
|
|
247
257
|
"anthropic/claude-sonnet-5",
|
|
248
258
|
"openai/gpt-6-astra ",
|
|
249
259
|
"openai/gpt-6-astra",
|
|
260
|
+
"openai/gpt-6.1-sol ",
|
|
250
261
|
"openai/gpt-6-sol ",
|
|
251
262
|
"openai/gpt-6-sol",
|
|
252
263
|
"openai/gpt-5.6-sol",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@diegopetrucci/pi-code-reviewer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
4
4
|
"description": "A standalone pi extension that adds a read-only code_reviewer subagent tool for isolated code reviews.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
]
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
32
|
+
"@earendil-works/pi-ai": "*",
|
|
32
33
|
"@earendil-works/pi-coding-agent": "*",
|
|
33
34
|
"typebox": "*"
|
|
34
35
|
},
|
|
@@ -25,7 +25,7 @@ With pi's default `reserveTokens` of 16,384, models larger than 200k will proact
|
|
|
25
25
|
/context-cap toggle
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
The extension starts enabled by default.
|
|
28
|
+
The extension starts enabled by default. `/context-cap off` releases this extension instance's hold; a shared model remains capped while another extension instance still holds it. After `/reload`, `/new`, `/resume`, or `/fork`, a new extension instance starts enabled again.
|
|
29
29
|
|
|
30
30
|
## Install
|
|
31
31
|
|
|
@@ -55,6 +55,35 @@ Then reload pi:
|
|
|
55
55
|
|
|
56
56
|
## Notes
|
|
57
57
|
|
|
58
|
-
- This extension mutates
|
|
59
|
-
- The cap
|
|
60
|
-
|
|
58
|
+
- This extension mutates only the active model handle's in-memory metadata. It does not edit `models.json` and does not pre-cap every model in the registry.
|
|
59
|
+
- The cap applies only when the active model is a physical model with a finite, positive `contextWindow`. Virtual selections and missing/incompatible limits are left unchanged because Pi 0.99 does not expose a routed physical model before compaction.
|
|
60
|
+
|
|
61
|
+
### Process-wide scope
|
|
62
|
+
|
|
63
|
+
- The cap is process-wide for physical model objects shared through Pi's `ModelRuntime`, not session-local. Sessions without this extension—including child sessions launched by `code-reviewer`, `agent-workflow-audit`, `librarian`, and `triage-comments` with `noExtensions`—can observe the cap. Concurrent-session isolation is not claimed; this is an accepted Pi 0.99 host/API limitation.
|
|
64
|
+
- A process-wide `WeakMap` keeps the first holder's original window and reference-counts extension instances. Only the last holder restores it, and only when the current value is still the cap written by the extension. External writes are not overwritten during restore, duplicate release is safe, and restarting the process clears the in-memory coordination. Extension copies with incompatible older state cannot coordinate.
|
|
65
|
+
|
|
66
|
+
### Settings guard and alternative
|
|
67
|
+
|
|
68
|
+
- On Pi hosts exposing `getSettings()`, the extension resolves `reserveTokens` using Pi's precedence: `compaction.modelOverrides["provider/model-id"].reserveTokens`, then `compaction.reserveTokens`, then the default `16,384`. It skips the cap when the resolved value is at least `200,000` (which would make the threshold zero or negative), or when any explicit entry exists for that model. Hosts without `getSettings()` use the older cap behavior without this guard.
|
|
69
|
+
- Pi's provider request path also derives an output-token budget from the effective window (`contextWindow - contextTokens - 4096`, with any provider-specific clamp applied afterward). The cap can therefore reduce response or thinking-token budgets near the limit, in addition to causing earlier compaction; Pi's overflow handling may also treat a request above 200k as overflow and retry after compaction. Use `/context-cap off` when you need the full model window temporarily.
|
|
70
|
+
- As a session-scoped alternative, configure Pi's own `compaction.modelOverrides` instead of this extension. The exact key is `provider/model-id`; to match this extension's default 200k threshold for a model with original window `W`, let `R` be the configured `reserveTokens` value (default `16,384`) and use:
|
|
71
|
+
|
|
72
|
+
```text
|
|
73
|
+
reserveTokens = W - 200000 + R
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
For example, with the default `R`, a 1,000,000-token model uses `816384`, yielding the same `183616` threshold as `200000 - 16384`:
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"compaction": {
|
|
81
|
+
"modelOverrides": {
|
|
82
|
+
"provider/model-id": { "reserveTokens": 816384 }
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Choose this alternative or the extension; do not combine them. The extension deliberately skips a model with an explicit `modelOverrides` entry, and older extension copies may not detect it.
|
|
89
|
+
- `/context-cap status` reports the process-wide original/effective window and how many other extension holders still hold the cap. No settings or session files are written by this extension.
|