pi-lean-ctx 3.8.6 → 3.8.8
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/extensions/config.ts +26 -0
- package/extensions/index.ts +29 -21
- package/package.json +1 -1
package/extensions/config.ts
CHANGED
|
@@ -132,6 +132,32 @@ export function resolveRouteShell(mode: PiMode, fileRouteShell: unknown): boolea
|
|
|
132
132
|
return fileRouteShell === true;
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
+
/**
|
|
136
|
+
* The five Pi builtins lean-ctx ships compressed `ctx_*` replacements for.
|
|
137
|
+
* Suppressing one removes it from the agent's tool set, so the agent must reach
|
|
138
|
+
* for the metered ctx_* equivalent instead of the uncompressed native.
|
|
139
|
+
*/
|
|
140
|
+
export const REPLACEABLE_BUILTIN_TOOLS = ["read", "bash", "ls", "find", "grep"] as const;
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* The Pi builtins to suppress for a resolved config. Single source of truth for
|
|
144
|
+
* the R1 "102 native bash / 0 ctx_shell" fix (#361): whenever the returned set
|
|
145
|
+
* contains `bash`, the native shell is gone and the agent must route through
|
|
146
|
+
* `ctx_shell` (compressed + metered).
|
|
147
|
+
*
|
|
148
|
+
* replace → all five natives suppressed (only ctx_* exposed)
|
|
149
|
+
* additive+routeShell → only `bash` suppressed (read/ls/find/grep stay)
|
|
150
|
+
* additive → nothing suppressed (fully non-regressive default)
|
|
151
|
+
*
|
|
152
|
+
* Invariant: every suppressed name has a ctx_* replacement (a subset of
|
|
153
|
+
* REPLACEABLE_BUILTIN_TOOLS), so a builtin is never removed without a substitute.
|
|
154
|
+
*/
|
|
155
|
+
export function resolveSuppressedBuiltins(mode: PiMode, routeShell: boolean): Set<string> {
|
|
156
|
+
if (mode === "replace") return new Set(REPLACEABLE_BUILTIN_TOOLS);
|
|
157
|
+
if (routeShell) return new Set(["bash"]);
|
|
158
|
+
return new Set<string>();
|
|
159
|
+
}
|
|
160
|
+
|
|
135
161
|
/** Split a comma/whitespace-separated tool list into trimmed, non-empty names. */
|
|
136
162
|
function parseToolList(raw: string | undefined): string[] {
|
|
137
163
|
if (!raw) return [];
|
package/extensions/index.ts
CHANGED
|
@@ -22,7 +22,7 @@ import { readFile, stat } from "node:fs/promises";
|
|
|
22
22
|
import { extname, resolve } from "node:path";
|
|
23
23
|
import { homedir, platform } from "node:os";
|
|
24
24
|
import { McpBridge } from "./mcp-bridge.js";
|
|
25
|
-
import { loadPiConfig } from "./config.js";
|
|
25
|
+
import { loadPiConfig, resolveSuppressedBuiltins } from "./config.js";
|
|
26
26
|
import type { CompressionStats } from "./types.js";
|
|
27
27
|
|
|
28
28
|
const CODE_EXTENSIONS = new Set([
|
|
@@ -44,12 +44,12 @@ const IMAGE_EXTENSIONS = new Set([".png", ".jpg", ".jpeg", ".gif", ".webp"]);
|
|
|
44
44
|
const CODE_FULL_READ_MAX_BYTES = 8 * 1024;
|
|
45
45
|
const CODE_SIGNATURES_MIN_BYTES = 96 * 1024;
|
|
46
46
|
|
|
47
|
-
// Pi builtins
|
|
48
|
-
// Settings resolve from (most explicit first):
|
|
49
|
-
// ~/.pi/agent/extensions/pi-lean-ctx/config.json,
|
|
47
|
+
// Which Pi builtins to suppress is resolved by resolveSuppressedBuiltins (in
|
|
48
|
+
// config.ts, unit-tested). Settings resolve from (most explicit first):
|
|
49
|
+
// LEAN_CTX_PI_* env vars, then ~/.pi/agent/extensions/pi-lean-ctx/config.json,
|
|
50
|
+
// then defaults (issue #344).
|
|
50
51
|
// mode "additive" (default) — keep Pi builtins, add ctx_* alongside
|
|
51
52
|
// mode "replace" — disable Pi builtins, only expose ctx_*
|
|
52
|
-
const DISABLED_BUILTIN_TOOLS = new Set(["read", "bash", "ls", "find", "grep"]);
|
|
53
53
|
const PI_CONFIG = loadPiConfig();
|
|
54
54
|
const PI_MODE = PI_CONFIG.mode;
|
|
55
55
|
// Max bytes constant for truncation warnings (same as Pi's DEFAULT_MAX_BYTES)
|
|
@@ -61,11 +61,17 @@ const readModeSchema = Type.Union([
|
|
|
61
61
|
Type.Literal("signatures"),
|
|
62
62
|
], { description: "Override auto-selection: full (complete content), map (deps+API signatures), signatures (AST only)" });
|
|
63
63
|
|
|
64
|
+
// Kept field-compatible with the canonical MCP `ctx_read` schema (registry in
|
|
65
|
+
// rust/src/tools/registered/ctx_read.rs) so the tool looks identical across
|
|
66
|
+
// harnesses: Codex sees `start_line`/`fresh`, Pi must too (GH #432). `offset` is
|
|
67
|
+
// the historical Pi alias for `start_line`; both are accepted.
|
|
64
68
|
const readSchema = Type.Object({
|
|
65
69
|
path: Type.String({ description: "Path to the file to read (relative or absolute)" }),
|
|
66
|
-
offset: Type.Optional(Type.Number({ description: "Line number to start reading from (1-indexed)" })),
|
|
67
|
-
limit: Type.Optional(Type.Number({ description: "Maximum number of lines to read" })),
|
|
68
70
|
mode: Type.Optional(readModeSchema),
|
|
71
|
+
start_line: Type.Optional(Type.Number({ description: "1-based line to start reading from (alias: offset)" })),
|
|
72
|
+
offset: Type.Optional(Type.Number({ description: "Alias for start_line — 1-based line to start reading from" })),
|
|
73
|
+
limit: Type.Optional(Type.Number({ description: "Maximum number of lines to read" })),
|
|
74
|
+
fresh: Type.Optional(Type.Boolean({ description: "Force a fresh disk re-read, bypassing the session cache" })),
|
|
69
75
|
});
|
|
70
76
|
|
|
71
77
|
// `path` is REQUIRED on ls/find/grep (#395): with an optional path these tools
|
|
@@ -343,11 +349,7 @@ export default async function (pi: ExtensionAPI) {
|
|
|
343
349
|
// finding: 102 bash / 0 ctx_shell), so the heaviest addressable surface in
|
|
344
350
|
// a fix task — make/reproducer/test logs — never reaches the compressor.
|
|
345
351
|
// - "additive" without routeShell → suppress nothing (keep Pi builtins).
|
|
346
|
-
const suppressedBuiltins = PI_MODE
|
|
347
|
-
? DISABLED_BUILTIN_TOOLS
|
|
348
|
-
: PI_CONFIG.routeShell
|
|
349
|
-
? new Set(["bash"])
|
|
350
|
-
: new Set<string>();
|
|
352
|
+
const suppressedBuiltins = resolveSuppressedBuiltins(PI_MODE, PI_CONFIG.routeShell);
|
|
351
353
|
|
|
352
354
|
if (suppressedBuiltins.size > 0) {
|
|
353
355
|
pi.on("session_start", () => {
|
|
@@ -555,17 +557,22 @@ export default async function (pi: ExtensionAPI) {
|
|
|
555
557
|
const requestedPath = normalizePathArg(params.path);
|
|
556
558
|
const absolutePath = resolve(ctx.cwd, requestedPath);
|
|
557
559
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
+
// `start_line` is the canonical name; `offset` is the historical Pi alias
|
|
561
|
+
// (GH #432). `fresh` forces a disk re-read past the session cache.
|
|
562
|
+
const startOffset = params.offset ?? params.start_line;
|
|
563
|
+
const forceFresh = params.fresh === true;
|
|
564
|
+
|
|
565
|
+
if (startOffset !== undefined || params.limit !== undefined) {
|
|
566
|
+
const startLine = startOffset ?? 1;
|
|
560
567
|
const endLine = params.limit ? startLine + params.limit - 1 : 999999;
|
|
561
568
|
const mode = `lines:${startLine}-${endLine}`;
|
|
562
569
|
// Route line-range reads through the bridge too, so re-reading the same
|
|
563
570
|
// slice hits the session cache instead of re-spawning a CLI per call (#361).
|
|
564
571
|
if (mcpBridge?.isConnected()) {
|
|
565
572
|
try {
|
|
566
|
-
const bridged = await mcpBridge.callTool("ctx_read", { path: absolutePath, mode }, signal);
|
|
573
|
+
const bridged = await mcpBridge.callTool("ctx_read", { path: absolutePath, mode, ...(forceFresh ? { fresh: true } : {}) }, signal);
|
|
567
574
|
const bridgedText = bridged.content.map((block) => block.text).join("");
|
|
568
|
-
const originalSlice = await readSlice(absolutePath,
|
|
575
|
+
const originalSlice = await readSlice(absolutePath, startOffset, params.limit);
|
|
569
576
|
const decorated = withFooter(bridgedText, { originalText: originalSlice.text, always: true, preferEstimate: true, suppressIfNoSaving: true });
|
|
570
577
|
return {
|
|
571
578
|
content: [{ type: "text", text: decorated.text }],
|
|
@@ -575,17 +582,17 @@ export default async function (pi: ExtensionAPI) {
|
|
|
575
582
|
console.error(`[pi-lean-ctx] ctx_read(${mode}) bridge call failed, falling back to CLI: ${err}`);
|
|
576
583
|
}
|
|
577
584
|
}
|
|
578
|
-
const args = ["read", absolutePath, "-m", mode];
|
|
585
|
+
const args = ["read", absolutePath, "-m", mode, ...(forceFresh ? ["--fresh"] : [])];
|
|
579
586
|
try {
|
|
580
587
|
const output = await execLeanCtx(pi, args);
|
|
581
|
-
const originalSlice = await readSlice(absolutePath,
|
|
588
|
+
const originalSlice = await readSlice(absolutePath, startOffset, params.limit);
|
|
582
589
|
const decorated = withFooter(output, { originalText: originalSlice.text, always: true, preferEstimate: true, suppressIfNoSaving: true });
|
|
583
590
|
return {
|
|
584
591
|
content: [{ type: "text", text: decorated.text }],
|
|
585
592
|
details: { path: absolutePath, lines: originalSlice.lines, source: "lean-ctx", mode, compression: decorated.stats },
|
|
586
593
|
};
|
|
587
594
|
} catch {
|
|
588
|
-
const sliced = await readSlice(absolutePath,
|
|
595
|
+
const sliced = await readSlice(absolutePath, startOffset, params.limit);
|
|
589
596
|
return {
|
|
590
597
|
content: [{ type: "text", text: sliced.text }],
|
|
591
598
|
details: { path: absolutePath, lines: sliced.lines, source: "local-slice-fallback", truncated: sliced.truncated },
|
|
@@ -598,6 +605,7 @@ export default async function (pi: ExtensionAPI) {
|
|
|
598
605
|
}
|
|
599
606
|
|
|
600
607
|
const isExplicitFull = params.mode === "full";
|
|
608
|
+
const wantsFresh = forceFresh || isExplicitFull;
|
|
601
609
|
const mode = params.mode ?? await chooseReadMode(absolutePath);
|
|
602
610
|
|
|
603
611
|
// When the embedded MCP bridge is connected, route the read through it so
|
|
@@ -611,7 +619,7 @@ export default async function (pi: ExtensionAPI) {
|
|
|
611
619
|
try {
|
|
612
620
|
const bridged = await mcpBridge.callTool(
|
|
613
621
|
"ctx_read",
|
|
614
|
-
{ path: absolutePath, mode, ...(
|
|
622
|
+
{ path: absolutePath, mode, ...(wantsFresh ? { fresh: true } : {}) },
|
|
615
623
|
signal,
|
|
616
624
|
);
|
|
617
625
|
const bridgedText = bridged.content.map((block) => block.text).join("");
|
|
@@ -626,7 +634,7 @@ export default async function (pi: ExtensionAPI) {
|
|
|
626
634
|
}
|
|
627
635
|
}
|
|
628
636
|
|
|
629
|
-
const args = ["read", absolutePath, "-m", mode, ...(
|
|
637
|
+
const args = ["read", absolutePath, "-m", mode, ...(wantsFresh ? ["--fresh"] : [])];
|
|
630
638
|
const output = await execLeanCtx(pi, args);
|
|
631
639
|
const originalText = await readFile(absolutePath, "utf8");
|
|
632
640
|
const decorated = withFooter(output, { originalText, always: true, preferEstimate: true, suppressIfNoSaving: true });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-lean-ctx",
|
|
3
|
-
"version": "3.8.
|
|
3
|
+
"version": "3.8.8",
|
|
4
4
|
"description": "Pi Coding Agent extension — routes bash/read/grep/find/ls through lean-ctx for strong token savings. The embedded MCP bridge (on by default) adds a persistent session cache so unchanged re-reads cost ~13 tokens.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|