@diegopetrucci/pi-extensions 0.1.78 → 0.1.79
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 +1 -1
- package/extensions/agent-workflow-audit/index.ts +3 -20
- package/extensions/agent-workflow-audit/package.json +1 -1
- package/extensions/annotate-git-diff/glimpseui.d.ts +5 -65
- package/extensions/annotate-git-diff/package.json +1 -1
- package/extensions/annotate-last-message/glimpseui.d.ts +5 -65
- package/extensions/annotate-last-message/package.json +1 -1
- package/extensions/annotate-last-message/web/index.html +0 -27
- package/extensions/code-reviewer/index.ts +0 -34
- package/extensions/code-reviewer/package.json +1 -1
- package/extensions/context-cap/README.md +9 -9
- package/extensions/context-cap/index.ts +3 -3
- package/extensions/context-cap/package.json +2 -2
- package/extensions/context-inspector/index.ts +2 -46
- package/extensions/context-inspector/package.json +1 -1
- package/extensions/contrarian/index.ts +1 -27
- package/extensions/contrarian/package.json +1 -1
- package/extensions/dynamic-context-pruning/README.md +16 -7
- package/extensions/dynamic-context-pruning/index.ts +9 -114
- package/extensions/dynamic-context-pruning/package.json +1 -1
- package/extensions/fast/README.md +16 -9
- package/extensions/fast/index.ts +9 -5
- package/extensions/fast/package.json +1 -1
- package/extensions/git-footer/index.ts +2 -16
- package/extensions/git-footer/package.json +1 -1
- package/extensions/librarian/index.ts +7 -39
- package/extensions/librarian/package.json +1 -1
- package/extensions/minimal-footer/README.md +5 -5
- package/extensions/minimal-footer/index.ts +3 -17
- package/extensions/minimal-footer/minimal-footer.example.json +1 -1
- package/extensions/minimal-footer/openai-usage.ts +2 -11
- package/extensions/minimal-footer/package.json +1 -1
- package/extensions/oracle/index.ts +1 -27
- package/extensions/oracle/package.json +1 -1
- package/extensions/permission-gate/index.ts +31 -63
- package/extensions/permission-gate/package.json +1 -1
- package/extensions/review/README.md +2 -0
- package/extensions/review/package.json +1 -1
- package/extensions/triage-comments/index.ts +0 -15
- package/extensions/triage-comments/package.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -31,7 +31,7 @@ A collection of [pi](https://pi.dev) agent extensions I made. (Btw, [I am also b
|
|
|
31
31
|
|
|
32
32
|
### Context management
|
|
33
33
|
|
|
34
|
-
- [`context-cap`](./extensions/context-cap): Caps effective model context windows at
|
|
34
|
+
- [`context-cap`](./extensions/context-cap): Caps effective model context windows at 350k tokens by default so pi avoids the `dumb zone`; toggle temporarily with `/context-cap`.
|
|
35
35
|
- [`context-inspector`](./extensions/context-inspector): Adds `/context`, a local self-contained HTML dashboard that breaks down where the current session context is going, with category overview, top offenders, and drilldown search.
|
|
36
36
|
- [`dynamic-context-pruning`](./extensions/dynamic-context-pruning): Automatically prunes duplicate tool results, stale errored inputs, and superseded file-op outputs from long sessions behind a cache-aware net-benefit gate, with a `/prune` picker and `/context-pruning` status/control commands.
|
|
37
37
|
|
|
@@ -63,10 +63,8 @@ type AuditDetails = {
|
|
|
63
63
|
toolCalls: ToolCall[];
|
|
64
64
|
startedAt: number;
|
|
65
65
|
endedAt?: number;
|
|
66
|
-
error?: string;
|
|
67
66
|
initialGitStatus?: string;
|
|
68
67
|
finalGitStatus?: string;
|
|
69
|
-
reportLength?: number;
|
|
70
68
|
toolCallCount?: number;
|
|
71
69
|
};
|
|
72
70
|
|
|
@@ -250,14 +248,14 @@ function renderCollapsedReport(report: string, lineLimit = COLLAPSED_REPORT_LINE
|
|
|
250
248
|
].join("\n");
|
|
251
249
|
}
|
|
252
250
|
|
|
253
|
-
function summarizeGitStatus(status: string | undefined): {
|
|
251
|
+
function summarizeGitStatus(status: string | undefined): { dirtyCount: number } {
|
|
254
252
|
const lines =
|
|
255
253
|
status
|
|
256
254
|
?.split("\n")
|
|
257
255
|
.map((line) => line.trim())
|
|
258
256
|
.filter(Boolean) ?? [];
|
|
259
257
|
const dirty = lines.filter((line) => !line.startsWith("##"));
|
|
260
|
-
return {
|
|
258
|
+
return { dirtyCount: dirty.length };
|
|
261
259
|
}
|
|
262
260
|
|
|
263
261
|
function appendRunBoundary(report: string, details: AuditDetails): string {
|
|
@@ -373,12 +371,6 @@ function getReadPathCandidates(resolved: string): string[] {
|
|
|
373
371
|
];
|
|
374
372
|
}
|
|
375
373
|
|
|
376
|
-
function resolveToolPath(cwd: string, rawPath: unknown): string {
|
|
377
|
-
const input = typeof rawPath === "string" && rawPath.trim() ? rawPath.trim() : ".";
|
|
378
|
-
const normalized = input.startsWith("@") ? input.slice(1) : input;
|
|
379
|
-
return path.isAbsolute(normalized) ? path.resolve(normalized) : path.resolve(cwd, normalized);
|
|
380
|
-
}
|
|
381
|
-
|
|
382
374
|
function isInside(parent: string, child: string): boolean {
|
|
383
375
|
const parentResolved = path.resolve(parent);
|
|
384
376
|
const childResolved = path.resolve(child);
|
|
@@ -399,11 +391,6 @@ async function assertToolPathInsideCwd(
|
|
|
399
391
|
// where realpath fails and the lexical fallback starts with the alias prefix.
|
|
400
392
|
const root = await fs.realpath(cwd).catch(() => path.resolve(cwd));
|
|
401
393
|
const lexicalCwd = path.resolve(cwd);
|
|
402
|
-
const resolved = resolveToolPath(cwd, rawPath);
|
|
403
|
-
const realPath = await fs.realpath(resolved).catch(() => resolved);
|
|
404
|
-
if (!isInside(root, realPath) && !isInside(lexicalCwd, realPath))
|
|
405
|
-
return `${toolName} is limited to the audited checkout: ${realPath}`;
|
|
406
|
-
// Pi-effective path check (monotonic: block if either resolution is outside).
|
|
407
394
|
// For the read tool, also check Pi's resolveReadPathAsync fallback variants so
|
|
408
395
|
// a missing file whose variant resolves outside is not silently allowed.
|
|
409
396
|
try {
|
|
@@ -1157,7 +1144,7 @@ function hasInlineFlag(tokens: string[], flags: string[]): boolean {
|
|
|
1157
1144
|
function getBlockedInterpreterReason(commandName: string, tokens: string[]): string | undefined {
|
|
1158
1145
|
const lowerTokens = tokens.map((token) => token.toLowerCase());
|
|
1159
1146
|
if (
|
|
1160
|
-
["node", "
|
|
1147
|
+
["node", "bun"].includes(commandName) &&
|
|
1161
1148
|
hasInlineFlag(lowerTokens, [
|
|
1162
1149
|
"-e",
|
|
1163
1150
|
"--eval",
|
|
@@ -1718,17 +1705,14 @@ async function runAudit(
|
|
|
1718
1705
|
throw new Error(outcome.reason);
|
|
1719
1706
|
}
|
|
1720
1707
|
details.endedAt = Date.now();
|
|
1721
|
-
details.reportLength = lastContent.length;
|
|
1722
1708
|
emit();
|
|
1723
1709
|
return { report: lastContent, details };
|
|
1724
1710
|
} catch (error) {
|
|
1725
1711
|
const failure = classifyRunFailure(error, abortController.callerAborted);
|
|
1726
1712
|
const { message } = failure;
|
|
1727
1713
|
details.status = failure.status;
|
|
1728
|
-
details.error = failure.error;
|
|
1729
1714
|
details.endedAt = Date.now();
|
|
1730
1715
|
lastContent = message;
|
|
1731
|
-
details.reportLength = lastContent.length;
|
|
1732
1716
|
emit();
|
|
1733
1717
|
return { report: `## Agent Workflow Audit failed\n\n${message}`, details };
|
|
1734
1718
|
} finally {
|
|
@@ -1866,7 +1850,6 @@ export default function agentWorkflowAuditExtension(pi: ExtensionAPI) {
|
|
|
1866
1850
|
if (!details) return;
|
|
1867
1851
|
details.finalGitStatus = await gitStatusShort(pi, ctx.cwd, ctx.signal).catch(() => undefined);
|
|
1868
1852
|
report = appendRunBoundary(report, details);
|
|
1869
|
-
details.reportLength = report.length;
|
|
1870
1853
|
const parentDetails: AuditDetails = {
|
|
1871
1854
|
...details,
|
|
1872
1855
|
toolCallCount: details.toolCalls.length,
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
declare module "glimpseui" {
|
|
2
|
-
import { EventEmitter } from "node:events";
|
|
3
|
-
|
|
4
2
|
export type FollowMode = "snap" | "spring";
|
|
5
3
|
export type CursorAnchor =
|
|
6
4
|
| "top-left"
|
|
@@ -32,67 +30,9 @@ declare module "glimpseui" {
|
|
|
32
30
|
timeout?: number;
|
|
33
31
|
}
|
|
34
32
|
|
|
35
|
-
export
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
visibleY?: number;
|
|
41
|
-
visibleWidth?: number;
|
|
42
|
-
visibleHeight?: number;
|
|
43
|
-
x?: number;
|
|
44
|
-
y?: number;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export interface GlimpseAppearanceInfo {
|
|
48
|
-
darkMode: boolean;
|
|
49
|
-
accentColor: string;
|
|
50
|
-
reduceMotion: boolean;
|
|
51
|
-
increaseContrast: boolean;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export interface GlimpseCursorInfo {
|
|
55
|
-
x: number;
|
|
56
|
-
y: number;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export interface GlimpseCursorTip {
|
|
60
|
-
x: number;
|
|
61
|
-
y: number;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export interface GlimpseInfo {
|
|
65
|
-
screen: GlimpseScreenInfo;
|
|
66
|
-
screens: GlimpseScreenInfo[];
|
|
67
|
-
appearance: GlimpseAppearanceInfo;
|
|
68
|
-
cursor: GlimpseCursorInfo;
|
|
69
|
-
cursorTip: GlimpseCursorTip | null;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export class GlimpseWindow extends EventEmitter {
|
|
73
|
-
on(event: "ready", listener: (info: GlimpseInfo) => void): this;
|
|
74
|
-
on(event: "message", listener: (data: unknown) => void): this;
|
|
75
|
-
on(event: "info", listener: (info: GlimpseInfo) => void): this;
|
|
76
|
-
on(event: "closed", listener: () => void): this;
|
|
77
|
-
on(event: "error", listener: (error: Error) => void): this;
|
|
78
|
-
once(event: "ready", listener: (info: GlimpseInfo) => void): this;
|
|
79
|
-
once(event: "message", listener: (data: unknown) => void): this;
|
|
80
|
-
once(event: "info", listener: (info: GlimpseInfo) => void): this;
|
|
81
|
-
once(event: "closed", listener: () => void): this;
|
|
82
|
-
once(event: "error", listener: (error: Error) => void): this;
|
|
83
|
-
send(js: string): void;
|
|
84
|
-
setHTML(html: string): void;
|
|
85
|
-
show(options?: { title?: string }): void;
|
|
86
|
-
close(): void;
|
|
87
|
-
loadFile(path: string): void;
|
|
88
|
-
get info(): GlimpseInfo | null;
|
|
89
|
-
getInfo(): void;
|
|
90
|
-
followCursor(enabled: boolean, anchor?: CursorAnchor, mode?: FollowMode): void;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export function open(html: string, options?: GlimpseOpenOptions): GlimpseWindow;
|
|
94
|
-
export function prompt<T = unknown>(
|
|
95
|
-
html: string,
|
|
96
|
-
options?: GlimpseOpenOptions,
|
|
97
|
-
): Promise<T | null>;
|
|
33
|
+
export function getNativeHostInfo(): {
|
|
34
|
+
path: string;
|
|
35
|
+
extraArgs?: string[];
|
|
36
|
+
buildHint?: string;
|
|
37
|
+
};
|
|
98
38
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@diegopetrucci/pi-annotate-git-diff",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
4
4
|
"description": "A standalone pi extension that adds /annotate-git-diff, a native Glimpse UI for annotating git diffs and sending explicit feedback directly to the agent; close recovery keeps unsent drafts in the editor.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
declare module "glimpseui" {
|
|
2
|
-
import { EventEmitter } from "node:events";
|
|
3
|
-
|
|
4
2
|
export type FollowMode = "snap" | "spring";
|
|
5
3
|
export type CursorAnchor =
|
|
6
4
|
| "top-left"
|
|
@@ -32,67 +30,9 @@ declare module "glimpseui" {
|
|
|
32
30
|
timeout?: number;
|
|
33
31
|
}
|
|
34
32
|
|
|
35
|
-
export
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
visibleY?: number;
|
|
41
|
-
visibleWidth?: number;
|
|
42
|
-
visibleHeight?: number;
|
|
43
|
-
x?: number;
|
|
44
|
-
y?: number;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export interface GlimpseAppearanceInfo {
|
|
48
|
-
darkMode: boolean;
|
|
49
|
-
accentColor: string;
|
|
50
|
-
reduceMotion: boolean;
|
|
51
|
-
increaseContrast: boolean;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export interface GlimpseCursorInfo {
|
|
55
|
-
x: number;
|
|
56
|
-
y: number;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export interface GlimpseCursorTip {
|
|
60
|
-
x: number;
|
|
61
|
-
y: number;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export interface GlimpseInfo {
|
|
65
|
-
screen: GlimpseScreenInfo;
|
|
66
|
-
screens: GlimpseScreenInfo[];
|
|
67
|
-
appearance: GlimpseAppearanceInfo;
|
|
68
|
-
cursor: GlimpseCursorInfo;
|
|
69
|
-
cursorTip: GlimpseCursorTip | null;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export class GlimpseWindow extends EventEmitter {
|
|
73
|
-
on(event: "ready", listener: (info: GlimpseInfo) => void): this;
|
|
74
|
-
on(event: "message", listener: (data: unknown) => void): this;
|
|
75
|
-
on(event: "info", listener: (info: GlimpseInfo) => void): this;
|
|
76
|
-
on(event: "closed", listener: () => void): this;
|
|
77
|
-
on(event: "error", listener: (error: Error) => void): this;
|
|
78
|
-
once(event: "ready", listener: (info: GlimpseInfo) => void): this;
|
|
79
|
-
once(event: "message", listener: (data: unknown) => void): this;
|
|
80
|
-
once(event: "info", listener: (info: GlimpseInfo) => void): this;
|
|
81
|
-
once(event: "closed", listener: () => void): this;
|
|
82
|
-
once(event: "error", listener: (error: Error) => void): this;
|
|
83
|
-
send(js: string): void;
|
|
84
|
-
setHTML(html: string): void;
|
|
85
|
-
show(options?: { title?: string }): void;
|
|
86
|
-
close(): void;
|
|
87
|
-
loadFile(path: string): void;
|
|
88
|
-
get info(): GlimpseInfo | null;
|
|
89
|
-
getInfo(): void;
|
|
90
|
-
followCursor(enabled: boolean, anchor?: CursorAnchor, mode?: FollowMode): void;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export function open(html: string, options?: GlimpseOpenOptions): GlimpseWindow;
|
|
94
|
-
export function prompt<T = unknown>(
|
|
95
|
-
html: string,
|
|
96
|
-
options?: GlimpseOpenOptions,
|
|
97
|
-
): Promise<T | null>;
|
|
33
|
+
export function getNativeHostInfo(): {
|
|
34
|
+
path: string;
|
|
35
|
+
extraArgs?: string[];
|
|
36
|
+
buildHint?: string;
|
|
37
|
+
};
|
|
98
38
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@diegopetrucci/pi-annotate-last-message",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.13",
|
|
4
4
|
"description": "A standalone pi extension that adds /annotate-last-message, a native Glimpse UI for annotating the latest assistant reply.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -314,9 +314,6 @@
|
|
|
314
314
|
margin: 0;
|
|
315
315
|
}
|
|
316
316
|
|
|
317
|
-
.markdown-content p { margin: 0; }
|
|
318
|
-
.markdown-content p + p { margin-top: 4px; }
|
|
319
|
-
|
|
320
317
|
.markdown-content a {
|
|
321
318
|
color: var(--mdLink);
|
|
322
319
|
text-decoration: underline;
|
|
@@ -337,21 +334,6 @@
|
|
|
337
334
|
font-family: inherit;
|
|
338
335
|
}
|
|
339
336
|
|
|
340
|
-
.markdown-content pre {
|
|
341
|
-
background: transparent;
|
|
342
|
-
margin: 4px 0;
|
|
343
|
-
overflow-x: auto;
|
|
344
|
-
border-left: 3px solid var(--mdCodeBlockBorder);
|
|
345
|
-
padding-left: 8px;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
.markdown-content pre code {
|
|
349
|
-
display: block;
|
|
350
|
-
background: none;
|
|
351
|
-
color: var(--mdCodeBlock, var(--text));
|
|
352
|
-
padding: 0;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
337
|
.markdown-content blockquote {
|
|
356
338
|
border-left: 3px solid var(--mdQuoteBorder);
|
|
357
339
|
padding-left: 8px;
|
|
@@ -360,15 +342,6 @@
|
|
|
360
342
|
font-style: italic;
|
|
361
343
|
}
|
|
362
344
|
|
|
363
|
-
.markdown-content ul,
|
|
364
|
-
.markdown-content ol {
|
|
365
|
-
margin: 4px 0;
|
|
366
|
-
padding-left: 1.5em;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
.markdown-content li { margin: 0; }
|
|
370
|
-
.markdown-content li::marker { color: var(--mdListBullet); }
|
|
371
|
-
|
|
372
345
|
.markdown-content hr {
|
|
373
346
|
border: none;
|
|
374
347
|
border-top: 1px solid var(--mdHr);
|
|
@@ -503,12 +503,8 @@ const CodeReviewerParams = Type.Object({
|
|
|
503
503
|
});
|
|
504
504
|
|
|
505
505
|
type ToolCall = {
|
|
506
|
-
id: string;
|
|
507
506
|
name: string;
|
|
508
507
|
args: unknown;
|
|
509
|
-
startedAt: number;
|
|
510
|
-
endedAt?: number;
|
|
511
|
-
isError?: boolean;
|
|
512
508
|
};
|
|
513
509
|
|
|
514
510
|
type ReviewStatus = "running" | "done" | "error" | "aborted";
|
|
@@ -1033,12 +1029,6 @@ function getReadPathCandidates(resolved: string): string[] {
|
|
|
1033
1029
|
];
|
|
1034
1030
|
}
|
|
1035
1031
|
|
|
1036
|
-
function resolveToolPath(cwd: string, rawPath: string | undefined): string {
|
|
1037
|
-
const input = rawPath?.trim() || ".";
|
|
1038
|
-
const normalized = input.startsWith("@") ? input.slice(1) : input;
|
|
1039
|
-
return path.isAbsolute(normalized) ? path.resolve(normalized) : path.resolve(cwd, normalized);
|
|
1040
|
-
}
|
|
1041
|
-
|
|
1042
1032
|
async function assertToolPathInsideCwd(
|
|
1043
1033
|
cwd: string,
|
|
1044
1034
|
rawPath: unknown,
|
|
@@ -1046,24 +1036,11 @@ async function assertToolPathInsideCwd(
|
|
|
1046
1036
|
): Promise<string | undefined> {
|
|
1047
1037
|
if (rawPath !== undefined && typeof rawPath !== "string")
|
|
1048
1038
|
return `${toolName} path must be a string.`;
|
|
1049
|
-
if (typeof rawPath === "string") {
|
|
1050
|
-
const normalizedInput = rawPath.trim().startsWith("@")
|
|
1051
|
-
? rawPath.trim().slice(1)
|
|
1052
|
-
: rawPath.trim();
|
|
1053
|
-
if (/(^|[/\\])\.\.(?:[/\\]|$)/.test(normalizedInput)) {
|
|
1054
|
-
return `${toolName} path must not traverse outside the local checkout.`;
|
|
1055
|
-
}
|
|
1056
|
-
}
|
|
1057
1039
|
// A path is allowed if it resolves inside EITHER the realpath'd root (root) OR
|
|
1058
1040
|
// the lexical cwd (lexicalCwd). This handles missing files in symlinked workspaces
|
|
1059
1041
|
// where realpath fails and the lexical fallback starts with the alias prefix.
|
|
1060
1042
|
const root = await fs.realpath(cwd).catch(() => path.resolve(cwd));
|
|
1061
1043
|
const lexicalCwd = path.resolve(cwd);
|
|
1062
|
-
const resolved = resolveToolPath(cwd, rawPath);
|
|
1063
|
-
const realPath = await fs.realpath(resolved).catch(() => resolved);
|
|
1064
|
-
if (!isInside(root, realPath) && !isInside(lexicalCwd, realPath))
|
|
1065
|
-
return `${toolName} is limited to the local checkout: ${realPath}`;
|
|
1066
|
-
// Pi-effective path check (monotonic: block if either resolution is outside).
|
|
1067
1044
|
// For the read tool, also check Pi's resolveReadPathAsync fallback variants so
|
|
1068
1045
|
// a missing file whose variant resolves outside is not silently allowed.
|
|
1069
1046
|
try {
|
|
@@ -1929,25 +1906,14 @@ export default function codeReviewerExtension(pi: ExtensionAPI) {
|
|
|
1929
1906
|
break;
|
|
1930
1907
|
case "tool_execution_start":
|
|
1931
1908
|
details.toolCalls.push({
|
|
1932
|
-
id: event.toolCallId,
|
|
1933
1909
|
name: event.toolName,
|
|
1934
1910
|
args: event.args,
|
|
1935
|
-
startedAt: Date.now(),
|
|
1936
1911
|
});
|
|
1937
1912
|
if (details.toolCalls.length > MAX_TOOL_CALLS_TO_KEEP) {
|
|
1938
1913
|
details.toolCalls.splice(0, details.toolCalls.length - MAX_TOOL_CALLS_TO_KEEP);
|
|
1939
1914
|
}
|
|
1940
1915
|
emit();
|
|
1941
1916
|
break;
|
|
1942
|
-
case "tool_execution_end": {
|
|
1943
|
-
const call = details.toolCalls.find((item) => item.id === event.toolCallId);
|
|
1944
|
-
if (call) {
|
|
1945
|
-
call.endedAt = Date.now();
|
|
1946
|
-
call.isError = event.isError;
|
|
1947
|
-
}
|
|
1948
|
-
emit();
|
|
1949
|
-
break;
|
|
1950
|
-
}
|
|
1951
1917
|
case "message_update":
|
|
1952
1918
|
if (event.assistantMessageEvent?.type === "text_delta") {
|
|
1953
1919
|
lastContent += event.assistantMessageEvent.delta ?? "";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# context-cap
|
|
2
2
|
|
|
3
|
-
A pi extension that treats large-context models as having an effective
|
|
3
|
+
A pi extension that treats large-context models as having an effective 350k-token context window, so pi's built-in auto-compaction starts earlier, and avoids the dumb zone.
|
|
4
4
|
|
|
5
5
|
By default, pi auto-compacts when:
|
|
6
6
|
|
|
@@ -11,10 +11,10 @@ contextTokens > model.contextWindow - reserveTokens
|
|
|
11
11
|
This extension changes the active model's in-memory `contextWindow` to:
|
|
12
12
|
|
|
13
13
|
```text
|
|
14
|
-
min(originalContextWindow,
|
|
14
|
+
min(originalContextWindow, 350000)
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
With pi's default `reserveTokens` of 16,384, models larger than
|
|
17
|
+
With pi's default `reserveTokens` of 16,384, models larger than 350k will proactively compact around 333,616 tokens.
|
|
18
18
|
|
|
19
19
|
## Commands
|
|
20
20
|
|
|
@@ -65,21 +65,21 @@ Then reload pi:
|
|
|
65
65
|
|
|
66
66
|
### Settings guard and alternative
|
|
67
67
|
|
|
68
|
-
- On Pi hosts exposing `getSettings()`, the extension skips the cap whenever `compaction.modelOverrides` contains the active model's key (`provider/model-id`), whatever fields that entry sets; it does not read `reserveTokens` from the override. Otherwise it uses `compaction.reserveTokens`, then the default `16,384`, and skips the cap when that value is at least `
|
|
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
|
|
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
|
|
68
|
+
- On Pi hosts exposing `getSettings()`, the extension skips the cap whenever `compaction.modelOverrides` contains the active model's key (`provider/model-id`), whatever fields that entry sets; it does not read `reserveTokens` from the override. Otherwise it uses `compaction.reserveTokens`, then the default `16,384`, and skips the cap when that value is at least `350,000` (which would make the threshold zero or negative). 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 350k 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 350k threshold for a model with original window `W`, let `R` be the configured `reserveTokens` value (default `16,384`) and use:
|
|
71
71
|
|
|
72
72
|
```text
|
|
73
|
-
reserveTokens = W -
|
|
73
|
+
reserveTokens = W - 350000 + R
|
|
74
74
|
```
|
|
75
75
|
|
|
76
|
-
For example, with the default `R`, a 1,000,000-token model uses `
|
|
76
|
+
For example, with the default `R`, a 1,000,000-token model uses `666384`, yielding the same `333616` threshold as `350000 - 16384`:
|
|
77
77
|
|
|
78
78
|
```json
|
|
79
79
|
{
|
|
80
80
|
"compaction": {
|
|
81
81
|
"modelOverrides": {
|
|
82
|
-
"provider/model-id": { "reserveTokens":
|
|
82
|
+
"provider/model-id": { "reserveTokens": 666384 }
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { Model, Api } from "@earendil-works/pi-ai";
|
|
3
3
|
|
|
4
|
-
const DEFAULT_MAX_CONTEXT_WINDOW =
|
|
4
|
+
const DEFAULT_MAX_CONTEXT_WINDOW = 350_000;
|
|
5
5
|
const DEFAULT_RESERVE_TOKENS = 16_384;
|
|
6
6
|
const PROCESS_STATE_KEY = Symbol.for("pi-extensions.context-cap.model-holds.v1");
|
|
7
7
|
type AnyModel = Model<Api> | Model<any>;
|
|
@@ -224,7 +224,7 @@ function unavailableReason(result: ApplyResult | undefined): string | undefined
|
|
|
224
224
|
return "physical model has no compatible context-window limit";
|
|
225
225
|
}
|
|
226
226
|
if (result?.reason === "reserve") {
|
|
227
|
-
return `compaction reserveTokens (${formatTokens(result.reserveTokens ?? DEFAULT_MAX_CONTEXT_WINDOW)}) is at least
|
|
227
|
+
return `compaction reserveTokens (${formatTokens(result.reserveTokens ?? DEFAULT_MAX_CONTEXT_WINDOW)}) is at least ${formatTokens(DEFAULT_MAX_CONTEXT_WINDOW)}; the auto-compaction threshold would be zero or negative`;
|
|
228
228
|
}
|
|
229
229
|
if (result?.reason === "modelOverride") {
|
|
230
230
|
return `compaction.modelOverrides[\"${result.key}\"] is configured; leaving the model window unchanged`;
|
|
@@ -317,7 +317,7 @@ export default function contextCapExtension(pi: ExtensionAPI) {
|
|
|
317
317
|
}
|
|
318
318
|
|
|
319
319
|
pi.registerCommand("context-cap", {
|
|
320
|
-
description:
|
|
320
|
+
description: `Toggle the ${formatTokens(DEFAULT_MAX_CONTEXT_WINDOW)} effective context-window cap for auto-compaction`,
|
|
321
321
|
getArgumentCompletions: (prefix) => {
|
|
322
322
|
const commands = ["on", "off", "toggle", "status"];
|
|
323
323
|
const matches = commands.filter((command) => command.startsWith(prefix.trim()));
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@diegopetrucci/pi-context-cap",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "A pi extension that caps effective model context windows at
|
|
3
|
+
"version": "0.1.16",
|
|
4
|
+
"description": "A pi extension that caps effective model context windows at 350k tokens for earlier auto-compaction.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
7
7
|
"pi",
|