@diegopetrucci/pi-extensions 0.1.76 → 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 +8 -8
- package/extensions/agent-workflow-audit/README.md +1 -1
- package/extensions/agent-workflow-audit/index.ts +275 -59
- package/extensions/agent-workflow-audit/package.json +1 -1
- package/extensions/annotate-git-diff/git.ts +0 -8
- 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 +3 -30
- package/extensions/code-reviewer/README.md +5 -5
- package/extensions/code-reviewer/index.ts +252 -82
- package/extensions/code-reviewer/package.json +1 -1
- package/extensions/confirm-destructive/index.ts +2 -8
- package/extensions/confirm-destructive/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/README.md +4 -3
- package/extensions/contrarian/index.ts +48 -67
- package/extensions/contrarian/package.json +1 -1
- package/extensions/dirty-repo-guard/index.ts +0 -4
- package/extensions/dirty-repo-guard/package.json +1 -1
- package/extensions/dynamic-context-pruning/README.md +20 -11
- package/extensions/dynamic-context-pruning/index.ts +118 -341
- package/extensions/dynamic-context-pruning/package.json +1 -1
- package/extensions/fast/README.md +18 -11
- 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/inline-bash/index.ts +0 -16
- package/extensions/inline-bash/package.json +1 -1
- package/extensions/librarian/README.md +1 -1
- package/extensions/librarian/index.ts +246 -70
- package/extensions/librarian/package.json +1 -1
- package/extensions/minimal-footer/README.md +5 -5
- package/extensions/minimal-footer/index.ts +5 -26
- 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/README.md +4 -3
- package/extensions/oracle/index.ts +46 -65
- package/extensions/oracle/package.json +1 -1
- package/extensions/permission-gate/README.md +3 -0
- package/extensions/permission-gate/index.ts +127 -36
- package/extensions/permission-gate/package.json +1 -1
- package/extensions/project-mcp-json/README.md +2 -2
- package/extensions/project-mcp-json/package.json +1 -1
- package/extensions/quiet-tools/.pi-fleet-tested-version +1 -1
- package/extensions/quiet-tools/README.md +30 -19
- package/extensions/quiet-tools/index.ts +233 -166
- package/extensions/quiet-tools/package.json +2 -2
- package/extensions/review/README.md +2 -0
- package/extensions/review/package.json +1 -1
- package/extensions/todo/index.ts +0 -19
- package/extensions/todo/package.json +1 -1
- package/extensions/triage-comments/README.md +4 -2
- package/extensions/triage-comments/index.ts +344 -39
- package/extensions/triage-comments/package.json +1 -1
- package/package.json +4 -4
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
|
+
import { realpathSync } from "node:fs";
|
|
3
|
+
import * as os from "node:os";
|
|
2
4
|
import * as path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
3
6
|
|
|
4
7
|
import type { AgentSession, ExtensionAPI, ExtensionFactory } from "@earendil-works/pi-coding-agent";
|
|
5
8
|
import {
|
|
@@ -9,6 +12,7 @@ import {
|
|
|
9
12
|
createAgentSession,
|
|
10
13
|
getAgentDir,
|
|
11
14
|
} from "@earendil-works/pi-coding-agent";
|
|
15
|
+
import * as piCodingAgent from "@earendil-works/pi-coding-agent";
|
|
12
16
|
import { Type } from "typebox";
|
|
13
17
|
|
|
14
18
|
import type { Usage } from "@earendil-works/pi-ai";
|
|
@@ -72,6 +76,33 @@ const CODE_REVIEWER_MODEL_PREFERENCES = [
|
|
|
72
76
|
"grok-4.5",
|
|
73
77
|
"gemini-3.5-flash",
|
|
74
78
|
];
|
|
79
|
+
const AZURE_MODEL_PREFERENCES = [
|
|
80
|
+
"gpt-6-astra ",
|
|
81
|
+
"gpt-6-astra",
|
|
82
|
+
"gpt-6.1-sol ",
|
|
83
|
+
"gpt-6-sol ",
|
|
84
|
+
"gpt-6-sol",
|
|
85
|
+
"gpt-5.6-sol",
|
|
86
|
+
"gpt-5.6-terra",
|
|
87
|
+
"gpt-6-luna ",
|
|
88
|
+
"gpt-6-luna",
|
|
89
|
+
"gpt-5.6-luna",
|
|
90
|
+
"gpt-5.5-pro",
|
|
91
|
+
"gpt-5.5",
|
|
92
|
+
"gpt-5.4-pro",
|
|
93
|
+
"gpt-5.4",
|
|
94
|
+
"gpt-5.3-codex",
|
|
95
|
+
"gpt-5-pro",
|
|
96
|
+
"gpt-5-chat-latest",
|
|
97
|
+
"gpt-5.2-pro",
|
|
98
|
+
"gpt-5.2",
|
|
99
|
+
"gpt-5.1",
|
|
100
|
+
"o3-pro",
|
|
101
|
+
"o1-pro",
|
|
102
|
+
"gpt-5.4-mini",
|
|
103
|
+
"gpt-5-mini",
|
|
104
|
+
];
|
|
105
|
+
|
|
75
106
|
const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
|
|
76
107
|
"amazon-bedrock": [
|
|
77
108
|
"global.anthropic.claude-opus-5-5 ",
|
|
@@ -102,39 +133,12 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
|
|
|
102
133
|
"claude-sonnet-4-6",
|
|
103
134
|
"claude-sonnet-4-5",
|
|
104
135
|
],
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
"gpt-6.1-sol ",
|
|
109
|
-
"gpt-6-sol ",
|
|
110
|
-
"gpt-6-sol",
|
|
111
|
-
"gpt-5.6-sol",
|
|
112
|
-
"gpt-5.6-terra",
|
|
113
|
-
"gpt-6-luna ",
|
|
114
|
-
"gpt-6-luna",
|
|
115
|
-
"gpt-5.6-luna",
|
|
116
|
-
"gpt-5.5-pro",
|
|
117
|
-
"gpt-5.5",
|
|
118
|
-
"gpt-5.4-pro",
|
|
119
|
-
"gpt-5.4",
|
|
120
|
-
"gpt-5.3-codex",
|
|
121
|
-
"gpt-5-pro",
|
|
122
|
-
"gpt-5-chat-latest",
|
|
123
|
-
"gpt-5.2-pro",
|
|
124
|
-
"gpt-5.2",
|
|
125
|
-
"gpt-5.1",
|
|
126
|
-
"o3-pro",
|
|
127
|
-
"o1-pro",
|
|
128
|
-
"gpt-5.4-mini",
|
|
129
|
-
"gpt-5-mini",
|
|
130
|
-
],
|
|
136
|
+
azure: AZURE_MODEL_PREFERENCES,
|
|
137
|
+
// Legacy alias for Pi 1.0.0–1.0.2 hosts (renamed to `azure` in Pi 1.0.3).
|
|
138
|
+
"azure-openai-responses": AZURE_MODEL_PREFERENCES,
|
|
131
139
|
"cloudflare-ai-gateway": [
|
|
132
140
|
"claude-fable-5",
|
|
133
141
|
"claude-opus-5",
|
|
134
|
-
"claude-opus-4.8",
|
|
135
|
-
"claude-opus-4.7",
|
|
136
|
-
"claude-opus-4.6",
|
|
137
|
-
"claude-opus-4.5",
|
|
138
142
|
"gpt-6-astra ",
|
|
139
143
|
"gpt-6-astra",
|
|
140
144
|
"gpt-5.5",
|
|
@@ -286,6 +290,92 @@ const PROVIDER_MODEL_PREFERENCES: Record<string, string[]> = {
|
|
|
286
290
|
],
|
|
287
291
|
};
|
|
288
292
|
|
|
293
|
+
const SUBAGENT_MCP_TOOLS = [
|
|
294
|
+
"mcp__*",
|
|
295
|
+
"tool_search",
|
|
296
|
+
"list_mcp_resources",
|
|
297
|
+
"list_mcp_resource_templates",
|
|
298
|
+
"read_mcp_resource",
|
|
299
|
+
];
|
|
300
|
+
|
|
301
|
+
// Derive project-trust for the isolated subagent session from the host tool context.
|
|
302
|
+
// Fails closed: returns false if the host has not trusted the project, or if the subagent
|
|
303
|
+
// runs in a different directory (e.g. a temp workspace), preventing an untrusted project's
|
|
304
|
+
// .pi/mcp.json from being loaded by the MCP extension.
|
|
305
|
+
function resolveSubagentProjectTrusted(
|
|
306
|
+
ctx: { cwd: string; isProjectTrusted(): boolean },
|
|
307
|
+
subagentCwd: string,
|
|
308
|
+
): boolean {
|
|
309
|
+
// Feature-detected for older Pi hosts that may lack isProjectTrusted.
|
|
310
|
+
const hostTrusted =
|
|
311
|
+
typeof (ctx as { isProjectTrusted?: unknown }).isProjectTrusted === "function"
|
|
312
|
+
? ctx.isProjectTrusted()
|
|
313
|
+
: false;
|
|
314
|
+
if (!hostTrusted) return false;
|
|
315
|
+
try {
|
|
316
|
+
return realpathSync(subagentCwd) === realpathSync(ctx.cwd);
|
|
317
|
+
} catch {
|
|
318
|
+
return false; // fail closed if realpath resolution fails
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function isSubagentMcpTool(toolName: string): boolean {
|
|
323
|
+
return (
|
|
324
|
+
toolName.startsWith("mcp__") ||
|
|
325
|
+
toolName === "tool_search" ||
|
|
326
|
+
toolName === "list_mcp_resources" ||
|
|
327
|
+
toolName === "list_mcp_resource_templates" ||
|
|
328
|
+
toolName === "read_mcp_resource"
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/** Returns true when v is a semver string >=1.0.4 (ignores prerelease; missing/garbage -> false). */
|
|
333
|
+
function isMcpCompatibleVersion(v: string | undefined): boolean {
|
|
334
|
+
if (!v) return false;
|
|
335
|
+
const m = /^(\d+)\.(\d+)\.(\d+)/.exec(v);
|
|
336
|
+
if (!m) return false;
|
|
337
|
+
const major = Number(m[1]),
|
|
338
|
+
minor = Number(m[2]),
|
|
339
|
+
patch = Number(m[3]);
|
|
340
|
+
if (major !== 1) return major > 1;
|
|
341
|
+
if (minor !== 0) return minor > 0;
|
|
342
|
+
return patch >= 4;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Gated on VERSION >=1.0.4: on older hosts mcp__* allowlist entries are exact-name
|
|
346
|
+
// matches that resolve nothing, so spawning MCP servers provides no benefit.
|
|
347
|
+
function createMcpSubagentFactories(): ExtensionFactory[] {
|
|
348
|
+
const ns = piCodingAgent as Record<string, unknown>;
|
|
349
|
+
if (!isMcpCompatibleVersion(ns["VERSION"] as string | undefined)) return [];
|
|
350
|
+
const factories: ExtensionFactory[] = [];
|
|
351
|
+
const mcp = ns["createMcpExtension"];
|
|
352
|
+
const toolSearch = ns["createToolSearchExtension"];
|
|
353
|
+
if (typeof mcp === "function") factories.push((mcp as () => ExtensionFactory)());
|
|
354
|
+
if (typeof toolSearch === "function") factories.push((toolSearch as () => ExtensionFactory)());
|
|
355
|
+
return factories;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Emit session_shutdown before dispose so MCP closes server connections (no leaked child processes).
|
|
359
|
+
async function disposeSubagentSession(session: AgentSession | undefined): Promise<void> {
|
|
360
|
+
if (!session) return;
|
|
361
|
+
try {
|
|
362
|
+
const runner = (
|
|
363
|
+
session as unknown as {
|
|
364
|
+
extensionRunner?: {
|
|
365
|
+
hasHandlers(n: string): boolean;
|
|
366
|
+
emit(e: { type: string; reason: string }): Promise<unknown>;
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
).extensionRunner;
|
|
370
|
+
if (runner?.hasHandlers("session_shutdown")) {
|
|
371
|
+
await runner.emit({ type: "session_shutdown", reason: "quit" });
|
|
372
|
+
}
|
|
373
|
+
} catch {
|
|
374
|
+
// Shutdown errors must not prevent dispose.
|
|
375
|
+
}
|
|
376
|
+
session.dispose();
|
|
377
|
+
}
|
|
378
|
+
|
|
289
379
|
const READ_ONLY_TOOL_NAMES = new Set(["read", "grep", "find", "ls", "bash"]);
|
|
290
380
|
const READ_ONLY_BASH_COMMANDS = new Set(["gh", "git", "pwd"]);
|
|
291
381
|
const READ_ONLY_GIT_SUBCOMMANDS = new Set([
|
|
@@ -321,7 +411,12 @@ const SAFE_GIT_BRANCH_FLAGS = new Set([
|
|
|
321
411
|
"--no-merged",
|
|
322
412
|
]);
|
|
323
413
|
const SAFE_GIT_GLOBAL_FLAGS = new Set(["--no-pager", "--no-optional-locks"]);
|
|
324
|
-
const SAFE_GIT_CONFIG_OVERRIDES = [
|
|
414
|
+
const SAFE_GIT_CONFIG_OVERRIDES = [
|
|
415
|
+
"core.pager=cat",
|
|
416
|
+
"core.fsmonitor=false",
|
|
417
|
+
"diff.external=",
|
|
418
|
+
"log.showSignature=false",
|
|
419
|
+
];
|
|
325
420
|
const GIT_SUBCOMMAND_SAFE_FLAGS: Partial<Record<string, string[]>> = {
|
|
326
421
|
blame: ["--no-textconv"],
|
|
327
422
|
diff: ["--no-ext-diff", "--no-textconv"],
|
|
@@ -330,12 +425,6 @@ const GIT_SUBCOMMAND_SAFE_FLAGS: Partial<Record<string, string[]>> = {
|
|
|
330
425
|
whatchanged: ["--no-ext-diff", "--no-textconv"],
|
|
331
426
|
};
|
|
332
427
|
const GIT_OPTIONS_REQUIRING_LOCAL_FILE = [
|
|
333
|
-
{
|
|
334
|
-
flag: "--contents",
|
|
335
|
-
blockedPrefixes: ["--con"],
|
|
336
|
-
reason:
|
|
337
|
-
"Code Reviewer bash blocks git --contents because it can read local files outside built-in path guards.",
|
|
338
|
-
},
|
|
339
428
|
{
|
|
340
429
|
flag: "--pathspec-from-file",
|
|
341
430
|
blockedPrefixes: ["--pathspec"],
|
|
@@ -353,6 +442,15 @@ const GIT_SUBCOMMAND_OPTIONS_REQUIRING_LOCAL_FILE: Partial<
|
|
|
353
442
|
Record<string, Array<{ reason: string; matches: (token: string) => boolean }>>
|
|
354
443
|
> = {
|
|
355
444
|
blame: [
|
|
445
|
+
{
|
|
446
|
+
reason:
|
|
447
|
+
"Code Reviewer bash blocks git blame --contents because it can read local files outside built-in path guards.",
|
|
448
|
+
matches: (token) => {
|
|
449
|
+
if (!token.startsWith("--")) return false;
|
|
450
|
+
const option = token.split("=", 1)[0]?.toLowerCase() ?? "";
|
|
451
|
+
return option === "--contents" || option.startsWith("--con");
|
|
452
|
+
},
|
|
453
|
+
},
|
|
356
454
|
{
|
|
357
455
|
reason:
|
|
358
456
|
"Code Reviewer bash blocks git blame -S because it can read local revs files outside built-in path guards.",
|
|
@@ -405,12 +503,8 @@ const CodeReviewerParams = Type.Object({
|
|
|
405
503
|
});
|
|
406
504
|
|
|
407
505
|
type ToolCall = {
|
|
408
|
-
id: string;
|
|
409
506
|
name: string;
|
|
410
507
|
args: unknown;
|
|
411
|
-
startedAt: number;
|
|
412
|
-
endedAt?: number;
|
|
413
|
-
isError?: boolean;
|
|
414
508
|
};
|
|
415
509
|
|
|
416
510
|
type ReviewStatus = "running" | "done" | "error" | "aborted";
|
|
@@ -859,10 +953,80 @@ function isInside(parent: string, child: string): boolean {
|
|
|
859
953
|
);
|
|
860
954
|
}
|
|
861
955
|
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
956
|
+
const UNICODE_SPACES_PATTERN = /[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g;
|
|
957
|
+
|
|
958
|
+
/**
|
|
959
|
+
* Mirrors normalizeWindowsShellPath from dist/utils/paths.js. No-op on
|
|
960
|
+
* non-win32 paths.
|
|
961
|
+
*/
|
|
962
|
+
function normalizeWindowsShellPathLocal(filePath: string): string {
|
|
963
|
+
if (!filePath.startsWith("/") || filePath.startsWith("//") || filePath.includes("\\"))
|
|
964
|
+
return filePath;
|
|
965
|
+
const match = filePath.match(/^\/(?:mnt\/|cygdrive\/)?([a-z])(?:\/(.*))?$/i);
|
|
966
|
+
if (!match) return filePath;
|
|
967
|
+
const suffix = match[2]?.replaceAll("/", "\\");
|
|
968
|
+
return `${match[1].toUpperCase()}:\\${suffix ?? ""}`;
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
/**
|
|
972
|
+
* Mirrors Pi's normalizePath (dist/utils/paths.js). On win32, Git Bash / WSL
|
|
973
|
+
* drive-path normalisation always runs; only tilde expansion is gated by
|
|
974
|
+
* expandTilde.
|
|
975
|
+
*/
|
|
976
|
+
function normalizePath(
|
|
977
|
+
input: string,
|
|
978
|
+
opts: { normalizeUnicodeSpaces?: boolean; stripAtPrefix?: boolean; expandTilde?: boolean } = {},
|
|
979
|
+
): string {
|
|
980
|
+
const { normalizeUnicodeSpaces = false, stripAtPrefix = false, expandTilde = true } = opts;
|
|
981
|
+
let p = input;
|
|
982
|
+
if (normalizeUnicodeSpaces) p = p.replace(UNICODE_SPACES_PATTERN, " ");
|
|
983
|
+
if (stripAtPrefix && p.startsWith("@")) p = p.slice(1);
|
|
984
|
+
if (process.platform === "win32") p = normalizeWindowsShellPathLocal(p);
|
|
985
|
+
if (expandTilde) {
|
|
986
|
+
const home = os.homedir();
|
|
987
|
+
if (p === "~") p = home;
|
|
988
|
+
else if (p.startsWith("~/") || (process.platform === "win32" && p.startsWith("~\\")))
|
|
989
|
+
p = path.join(home, p.slice(2));
|
|
990
|
+
}
|
|
991
|
+
if (/^file:\/\//.test(p)) p = fileURLToPath(p);
|
|
992
|
+
return p;
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
/**
|
|
996
|
+
* Mirrors Pi's resolveToCwd (dist/core/tools/path-utils.js). Exposed via
|
|
997
|
+
* __test__ for parity tests; not imported by other extensions.
|
|
998
|
+
*/
|
|
999
|
+
function resolveToCwd(rawPath: string, cwd: string): string {
|
|
1000
|
+
const normalized = normalizePath(rawPath, { normalizeUnicodeSpaces: true, stripAtPrefix: true });
|
|
1001
|
+
const base = normalizePath(cwd);
|
|
1002
|
+
return path.isAbsolute(normalized) ? path.resolve(normalized) : path.resolve(base, normalized);
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
/** Mirrors tryMacOSScreenshotPath from dist/core/tools/path-utils.js */
|
|
1006
|
+
function tryMacOSScreenshotPathVariant(filePath: string): string {
|
|
1007
|
+
return filePath.replace(/ (AM|PM)\./gi, "\u202F$1.");
|
|
1008
|
+
}
|
|
1009
|
+
/** Mirrors tryNFDVariant from dist/core/tools/path-utils.js */
|
|
1010
|
+
function tryNFDPathVariant(filePath: string): string {
|
|
1011
|
+
return filePath.normalize("NFD");
|
|
1012
|
+
}
|
|
1013
|
+
/** Mirrors tryCurlyQuoteVariant from dist/core/tools/path-utils.js */
|
|
1014
|
+
function tryCurlyQuotePathVariant(filePath: string): string {
|
|
1015
|
+
return filePath.replace(/'/g, "\u2019");
|
|
1016
|
+
}
|
|
1017
|
+
/**
|
|
1018
|
+
* Returns all read path candidates including Pi's resolveReadPathAsync fallback
|
|
1019
|
+
* variants. Used only for the read tool guard; grep/find/ls use resolveToCwd only.
|
|
1020
|
+
*/
|
|
1021
|
+
function getReadPathCandidates(resolved: string): string[] {
|
|
1022
|
+
const nfd = tryNFDPathVariant(resolved);
|
|
1023
|
+
return [
|
|
1024
|
+
resolved,
|
|
1025
|
+
tryMacOSScreenshotPathVariant(resolved),
|
|
1026
|
+
nfd,
|
|
1027
|
+
tryCurlyQuotePathVariant(resolved),
|
|
1028
|
+
tryCurlyQuotePathVariant(nfd),
|
|
1029
|
+
];
|
|
866
1030
|
}
|
|
867
1031
|
|
|
868
1032
|
async function assertToolPathInsideCwd(
|
|
@@ -872,18 +1036,28 @@ async function assertToolPathInsideCwd(
|
|
|
872
1036
|
): Promise<string | undefined> {
|
|
873
1037
|
if (rawPath !== undefined && typeof rawPath !== "string")
|
|
874
1038
|
return `${toolName} path must be a string.`;
|
|
875
|
-
if
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
1039
|
+
// A path is allowed if it resolves inside EITHER the realpath'd root (root) OR
|
|
1040
|
+
// the lexical cwd (lexicalCwd). This handles missing files in symlinked workspaces
|
|
1041
|
+
// where realpath fails and the lexical fallback starts with the alias prefix.
|
|
1042
|
+
const root = await fs.realpath(cwd).catch(() => path.resolve(cwd));
|
|
1043
|
+
const lexicalCwd = path.resolve(cwd);
|
|
1044
|
+
// For the read tool, also check Pi's resolveReadPathAsync fallback variants so
|
|
1045
|
+
// a missing file whose variant resolves outside is not silently allowed.
|
|
1046
|
+
try {
|
|
1047
|
+
const piResolved = resolveToCwd(typeof rawPath === "string" ? rawPath : ".", cwd);
|
|
1048
|
+
const candidates = toolName === "read" ? getReadPathCandidates(piResolved) : [piResolved];
|
|
1049
|
+
// Deliberate policy: all candidates are checked regardless of existence (not
|
|
1050
|
+
// Pi's existence-ordered selection) so the guard cannot be raced by creating
|
|
1051
|
+
// a variant between the hook and the read. Trade-off: a read is blocked when
|
|
1052
|
+
// an escaping variant symlink sits next to a safe primary file.
|
|
1053
|
+
for (const candidate of candidates) {
|
|
1054
|
+
const candidateRealPath = await fs.realpath(candidate).catch(() => candidate);
|
|
1055
|
+
if (!isInside(root, candidateRealPath) && !isInside(lexicalCwd, candidateRealPath))
|
|
1056
|
+
return `${toolName} is limited to the local checkout: ${candidateRealPath}`;
|
|
881
1057
|
}
|
|
1058
|
+
} catch {
|
|
1059
|
+
return `${toolName} path could not be resolved safely.`;
|
|
882
1060
|
}
|
|
883
|
-
const root = await fs.realpath(cwd).catch(() => path.resolve(cwd));
|
|
884
|
-
const resolved = resolveToolPath(cwd, rawPath);
|
|
885
|
-
const realPath = await fs.realpath(resolved).catch(() => resolved);
|
|
886
|
-
if (!isInside(root, realPath)) return `${toolName} is limited to the local checkout: ${realPath}`;
|
|
887
1061
|
return undefined;
|
|
888
1062
|
}
|
|
889
1063
|
|
|
@@ -1428,10 +1602,10 @@ function createCodeReviewerRuntimeGuardExtension(options: {
|
|
|
1428
1602
|
});
|
|
1429
1603
|
|
|
1430
1604
|
pi.on("tool_call", async (event) => {
|
|
1431
|
-
if (!READ_ONLY_TOOL_NAMES.has(event.toolName)) {
|
|
1605
|
+
if (!READ_ONLY_TOOL_NAMES.has(event.toolName) && !isSubagentMcpTool(event.toolName)) {
|
|
1432
1606
|
return {
|
|
1433
1607
|
block: true,
|
|
1434
|
-
reason: `code_reviewer exposes read-only tools
|
|
1608
|
+
reason: `code_reviewer exposes read-only built-in tools and MCP tools; ${event.toolName} is not allowed.`,
|
|
1435
1609
|
};
|
|
1436
1610
|
}
|
|
1437
1611
|
|
|
@@ -1505,7 +1679,7 @@ function buildSystemPrompt(options: {
|
|
|
1505
1679
|
maxTurns: number;
|
|
1506
1680
|
maxRunSeconds: number;
|
|
1507
1681
|
}): string {
|
|
1508
|
-
return `You are Code Reviewer, an isolated read-only review agent running inside the code-reviewer package. Review the proposed change against the stated task and the local checkout, then return a concise evidence-based review report.\n\nWorking directory: ${options.cwd}\nTurn budget: ${options.maxTurns} turns total, including your final answer.\nWall-clock budget: ${options.maxRunSeconds} seconds.\n\nAvailable tools are intended to be read-only: read, grep, find, ls, and bash. Use the built-in read, grep, find, and ls tools for local file inspection. Use bash only for a single read-only git, gh, or pwd invocation, such as git status/diff/show/log/blame, gh pr view/diff/list/status/checks, gh api GET calls, or pwd. A runtime guard blocks write/edit tools, shell pipelines/control operators, redirection, mutating git/gh commands, npm/publish commands, path traversal outside the checkout, and other filesystem mutation.\n\nReview priorities, in order:\n1. Ticket fit: does the change actually satisfy the requested task and stay in scope?\n2. Diff accuracy: do the files and edits match what the task claims changed?\n3. Correctness: could the change fail, regress behavior, mishandle errors, or break edge cases?\n4. Security and safety: look for unsafe command use, path handling issues, secret exposure, injection risks, destructive behavior, and policy violations.\n5. Simplicity and maintainability: call out unnecessary complexity, unclear behavior, or avoidable duplication.\n6. Tests and validation: check whether meaningful verification exists and whether gaps matter for this task.\n\nNon-negotiable constraints:\n- Never implement changes. Never edit, write, move, delete, format, install, commit, checkout, reset, clean, push, publish, or mutate GitHub.\n- Treat the supplied task and diff/context as claims to verify, not as facts. Cite file paths, line ranges, git diff/status output, or supplied diff excerpts as evidence.\n- Prefer the built-in file tools over bash when local file inspection is enough.\n- If evidence is missing, stale, or contradictory, say so plainly.\n- Keep the final review concise and actionable. Report the most important findings first; do not pad with non-findings.\n\nOutput format, exact order:\n## Verdict\nOne short paragraph with the overall review outcome.\n\n## Findings\nUse bullets ordered by severity. For each finding include: severity ('blocker', 'major', or 'minor'), a short title, evidence, and why it matters. If there are no findings, write '- none'.\n\n## Validation\nList the read-only checks you performed, including key files inspected, notable git/gh commands, and any uncertainty that limited the review, or '- none'.\n\n## Scope check\nState whether the change appears to match the requested task and note any missing or extra scope.`;
|
|
1682
|
+
return `You are Code Reviewer, an isolated read-only review agent running inside the code-reviewer package. Review the proposed change against the stated task and the local checkout, then return a concise evidence-based review report.\n\nWorking directory: ${options.cwd}\nTurn budget: ${options.maxTurns} turns total, including your final answer.\nWall-clock budget: ${options.maxRunSeconds} seconds.\n\nAvailable tools are intended to be read-only: read, grep, find, ls, and bash. Use the built-in read, grep, find, and ls tools for local file inspection. Use bash only for a single read-only git, gh, or pwd invocation, such as git status/diff/show/log/blame, gh pr view/diff/list/status/checks, gh api GET calls, or pwd. A runtime guard blocks write/edit tools, shell pipelines/control operators, redirection, mutating git/gh commands, npm/publish commands, path traversal outside the checkout, and other filesystem mutation. Configured MCP tools are also available and run without confirmation; use them only to gather evidence or context, not for state-changing actions unless the task explicitly requires it. MCP access requires host Pi >=1.0.4; on older hosts MCP tools are absent with no error.\n\nReview priorities, in order:\n1. Ticket fit: does the change actually satisfy the requested task and stay in scope?\n2. Diff accuracy: do the files and edits match what the task claims changed?\n3. Correctness: could the change fail, regress behavior, mishandle errors, or break edge cases?\n4. Security and safety: look for unsafe command use, path handling issues, secret exposure, injection risks, destructive behavior, and policy violations.\n5. Simplicity and maintainability: call out unnecessary complexity, unclear behavior, or avoidable duplication.\n6. Tests and validation: check whether meaningful verification exists and whether gaps matter for this task.\n\nNon-negotiable constraints:\n- Never implement changes. Never edit, write, move, delete, format, install, commit, checkout, reset, clean, push, publish, or mutate GitHub.\n- Treat the supplied task and diff/context as claims to verify, not as facts. Cite file paths, line ranges, git diff/status output, or supplied diff excerpts as evidence.\n- Prefer the built-in file tools over bash when local file inspection is enough.\n- If evidence is missing, stale, or contradictory, say so plainly.\n- Keep the final review concise and actionable. Report the most important findings first; do not pad with non-findings.\n\nOutput format, exact order:\n## Verdict\nOne short paragraph with the overall review outcome.\n\n## Findings\nUse bullets ordered by severity. For each finding include: severity ('blocker', 'major', or 'minor'), a short title, evidence, and why it matters. If there are no findings, write '- none'.\n\n## Validation\nList the read-only checks you performed, including key files inspected, notable git/gh commands, and any uncertainty that limited the review, or '- none'.\n\n## Scope check\nState whether the change appears to match the requested task and note any missing or extra scope.`;
|
|
1509
1683
|
}
|
|
1510
1684
|
|
|
1511
1685
|
function buildUserPrompt(
|
|
@@ -1564,17 +1738,20 @@ function appendRunDetails(report: string, details: ReviewDetails): string {
|
|
|
1564
1738
|
: "thinking unknown";
|
|
1565
1739
|
const suffix = `\n\n---\nRun details: ${modelSummary}; ${thinkingSummary}; ${details.turns} turn(s); ${details.toolCalls.length} tool call(s); duration ${duration}; cwd ${details.cwd}; tools ${toolSummary}.`;
|
|
1566
1740
|
if (!trimmed)
|
|
1567
|
-
return `## Verdict\nNo review output was produced.\n\n## Findings\n- major — Missing review output. Evidence: the isolated code_reviewer session returned no assistant text. Why it matters: the review could not be completed.\n\n## Validation\n- none\n\n## Scope check\nReview could not be completed
|
|
1741
|
+
return `## Verdict\nNo review output was produced.\n\n## Findings\n- major — Missing review output. Evidence: the isolated code_reviewer session returned no assistant text. Why it matters: the review could not be completed.\n\n## Validation\n- none\n\n## Scope check\nReview could not be completed.${suffix}`;
|
|
1568
1742
|
return `${trimmed}${suffix}`;
|
|
1569
1743
|
}
|
|
1570
1744
|
|
|
1571
1745
|
export const __test__ = {
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1746
|
+
SUBAGENT_MCP_TOOLS,
|
|
1747
|
+
isMcpCompatibleVersion,
|
|
1748
|
+
resolveSubagentProjectTrusted,
|
|
1749
|
+
createMcpSubagentFactories,
|
|
1750
|
+
disposeSubagentSession,
|
|
1575
1751
|
createCodeReviewerRuntimeGuardExtension,
|
|
1752
|
+
assertToolPathInsideCwd,
|
|
1753
|
+
resolveToCwd,
|
|
1576
1754
|
buildSafeGitCommand,
|
|
1577
|
-
formatToolCall,
|
|
1578
1755
|
getBlockedBashReason,
|
|
1579
1756
|
isModelAvailabilityError,
|
|
1580
1757
|
normalizeThinkingLevel,
|
|
@@ -1599,11 +1776,11 @@ export default function codeReviewerExtension(pi: ExtensionAPI) {
|
|
|
1599
1776
|
name: "code_reviewer",
|
|
1600
1777
|
label: "Code reviewer",
|
|
1601
1778
|
description:
|
|
1602
|
-
"
|
|
1779
|
+
"Isolated reviewer with read-only built-in tools and configured MCP tools that checks a proposed change for ticket fit, diff accuracy, correctness, security, simplicity, and validation gaps without implementing anything.",
|
|
1603
1780
|
promptSnippet:
|
|
1604
|
-
"Run an isolated
|
|
1781
|
+
"Run an isolated code review against the local checkout and optional diff using read-only built-in tools plus any configured MCP tools; returns concise findings with evidence and run details.",
|
|
1605
1782
|
promptGuidelines: [
|
|
1606
|
-
"Use code_reviewer when you want an independent
|
|
1783
|
+
"Use code_reviewer when you want an independent review of a proposed change or task implementation; it uses read-only built-in tools and any configured MCP tools.",
|
|
1607
1784
|
"Provide the requested task and any relevant diff/context. Do not use code_reviewer to implement or edit files.",
|
|
1608
1785
|
],
|
|
1609
1786
|
parameters: CodeReviewerParams,
|
|
@@ -1661,7 +1838,8 @@ export default function codeReviewerExtension(pi: ExtensionAPI) {
|
|
|
1661
1838
|
try {
|
|
1662
1839
|
emit();
|
|
1663
1840
|
|
|
1664
|
-
const
|
|
1841
|
+
const projectTrusted = resolveSubagentProjectTrusted(ctx, cwd);
|
|
1842
|
+
const isolatedSettingsManager = SettingsManager.inMemory({}, { projectTrusted });
|
|
1665
1843
|
const resourceLoader = new DefaultResourceLoader({
|
|
1666
1844
|
cwd,
|
|
1667
1845
|
agentDir: getAgentDir(),
|
|
@@ -1673,6 +1851,7 @@ export default function codeReviewerExtension(pi: ExtensionAPI) {
|
|
|
1673
1851
|
noContextFiles: true,
|
|
1674
1852
|
extensionFactories: [
|
|
1675
1853
|
createCodeReviewerRuntimeGuardExtension({ cwd, maxTurns: MAX_TURNS }),
|
|
1854
|
+
...createMcpSubagentFactories(),
|
|
1676
1855
|
],
|
|
1677
1856
|
systemPromptOverride: () =>
|
|
1678
1857
|
buildSystemPrompt({
|
|
@@ -1712,10 +1891,12 @@ export default function codeReviewerExtension(pi: ExtensionAPI) {
|
|
|
1712
1891
|
sessionManager: SessionManager.inMemory(cwd),
|
|
1713
1892
|
model: candidate as CreateAgentSessionModel,
|
|
1714
1893
|
thinkingLevel: thinking.effective,
|
|
1715
|
-
tools: ["read", "grep", "find", "ls", "bash"],
|
|
1894
|
+
tools: ["read", "grep", "find", "ls", "bash", ...SUBAGENT_MCP_TOOLS],
|
|
1716
1895
|
});
|
|
1717
1896
|
attemptSession = created.session;
|
|
1718
1897
|
session = attemptSession;
|
|
1898
|
+
// Connect extensions (MCP servers) before prompting. Feature-detected: Pi >=0.99.0.
|
|
1899
|
+
await (created.session as any).bindExtensions?.({ mode: "json" });
|
|
1719
1900
|
attemptUnsubscribe = attemptSession.subscribe((event) => {
|
|
1720
1901
|
addSessionEventUsage(usage, event);
|
|
1721
1902
|
switch (event.type) {
|
|
@@ -1725,25 +1906,14 @@ export default function codeReviewerExtension(pi: ExtensionAPI) {
|
|
|
1725
1906
|
break;
|
|
1726
1907
|
case "tool_execution_start":
|
|
1727
1908
|
details.toolCalls.push({
|
|
1728
|
-
id: event.toolCallId,
|
|
1729
1909
|
name: event.toolName,
|
|
1730
1910
|
args: event.args,
|
|
1731
|
-
startedAt: Date.now(),
|
|
1732
1911
|
});
|
|
1733
1912
|
if (details.toolCalls.length > MAX_TOOL_CALLS_TO_KEEP) {
|
|
1734
1913
|
details.toolCalls.splice(0, details.toolCalls.length - MAX_TOOL_CALLS_TO_KEEP);
|
|
1735
1914
|
}
|
|
1736
1915
|
emit();
|
|
1737
1916
|
break;
|
|
1738
|
-
case "tool_execution_end": {
|
|
1739
|
-
const call = details.toolCalls.find((item) => item.id === event.toolCallId);
|
|
1740
|
-
if (call) {
|
|
1741
|
-
call.endedAt = Date.now();
|
|
1742
|
-
call.isError = event.isError;
|
|
1743
|
-
}
|
|
1744
|
-
emit();
|
|
1745
|
-
break;
|
|
1746
|
-
}
|
|
1747
1917
|
case "message_update":
|
|
1748
1918
|
if (event.assistantMessageEvent?.type === "text_delta") {
|
|
1749
1919
|
lastContent += event.assistantMessageEvent.delta ?? "";
|
|
@@ -1797,7 +1967,7 @@ export default function codeReviewerExtension(pi: ExtensionAPI) {
|
|
|
1797
1967
|
if (attemptSession && !succeeded) {
|
|
1798
1968
|
attemptUnsubscribe?.();
|
|
1799
1969
|
if (unsubscribe === attemptUnsubscribe) unsubscribe = undefined;
|
|
1800
|
-
attemptSession
|
|
1970
|
+
await disposeSubagentSession(attemptSession);
|
|
1801
1971
|
if (session === attemptSession) session = undefined;
|
|
1802
1972
|
}
|
|
1803
1973
|
}
|
|
@@ -1828,7 +1998,7 @@ export default function codeReviewerExtension(pi: ExtensionAPI) {
|
|
|
1828
1998
|
details.error = failure.error;
|
|
1829
1999
|
details.endedAt = Date.now();
|
|
1830
2000
|
lastContent = appendRunDetails(
|
|
1831
|
-
`## Verdict\nReview failed.\n\n## Findings\n- major — Review execution failed. Evidence: ${message}. Why it matters: the requested read-only review did not complete.\n\n## Validation\n- none\n\n## Scope check\nReview could not be completed
|
|
2001
|
+
`## Verdict\nReview failed.\n\n## Findings\n- major — Review execution failed. Evidence: ${message}. Why it matters: the requested read-only review did not complete.\n\n## Validation\n- none\n\n## Scope check\nReview could not be completed.`,
|
|
1832
2002
|
details,
|
|
1833
2003
|
);
|
|
1834
2004
|
emit();
|
|
@@ -1837,7 +2007,7 @@ export default function codeReviewerExtension(pi: ExtensionAPI) {
|
|
|
1837
2007
|
if (runTimeout) clearTimeout(runTimeout);
|
|
1838
2008
|
if (signal && abortListenerAdded) signal.removeEventListener("abort", abortFromCaller);
|
|
1839
2009
|
unsubscribe?.();
|
|
1840
|
-
session
|
|
2010
|
+
await disposeSubagentSession(session);
|
|
1841
2011
|
}
|
|
1842
2012
|
},
|
|
1843
2013
|
});
|
|
@@ -12,20 +12,14 @@ import type {
|
|
|
12
12
|
} from "@earendil-works/pi-coding-agent";
|
|
13
13
|
|
|
14
14
|
function hasUnsavedUserWork(entries: SessionEntry[]): boolean {
|
|
15
|
-
let sawAssistant = false;
|
|
16
|
-
|
|
17
15
|
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
18
16
|
const entry = entries[index];
|
|
19
17
|
if (entry.type !== "message") continue;
|
|
20
|
-
if (entry.message.role === "assistant")
|
|
21
|
-
sawAssistant = true;
|
|
22
|
-
break;
|
|
23
|
-
}
|
|
18
|
+
if (entry.message.role === "assistant") return false;
|
|
24
19
|
if (entry.message.role === "user") return true;
|
|
25
20
|
}
|
|
26
21
|
|
|
27
|
-
|
|
28
|
-
return entries.some((entry) => entry.type === "message" && entry.message.role === "user");
|
|
22
|
+
return false;
|
|
29
23
|
}
|
|
30
24
|
|
|
31
25
|
export default function (pi: ExtensionAPI) {
|
|
@@ -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
|
|
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",
|