pi-repl-py 0.2.5 → 0.2.7
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 +10 -2
- package/docs/ARCHITECTURE.md +6 -4
- package/package.json +1 -1
- package/src/engine/index.ts +27 -4
- package/src/extension/prompt.ts +19 -3
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# pi-repl
|
|
1
|
+
# pi-repl-py
|
|
2
2
|
|
|
3
3
|
A [pi](https://pi.dev) extension that gives the agent a single `execute` tool backed by a
|
|
4
4
|
**persistent Python evaluator**: a real `ipython` kernel that keeps variables, functions, imports,
|
|
@@ -32,7 +32,15 @@ A plain `pi` session is untouched; the extension is dormant until `--repl` is pa
|
|
|
32
32
|
|
|
33
33
|
## Installing as a pi package
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
Install the package from npm or directly from GitHub:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pi install npm:pi-repl-py
|
|
39
|
+
# or
|
|
40
|
+
pi install github:k3-2o/pi-repl-py
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The install runs a `postinstall` that creates the Python venv the evaluator needs, at a stable
|
|
36
44
|
per-user path (`~/.pi/agent/pi-repl/venv`). If `python3` or the network is missing, it prints a
|
|
37
45
|
clear notice. How the interpreter is resolved is in [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md).
|
|
38
46
|
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -87,10 +87,12 @@ finished draining on iopub. A cell settles only when **both** the `execute_reply
|
|
|
87
87
|
matching iopub `status idle` (published after every byte of output) have arrived. Settling
|
|
88
88
|
on the reply alone would drop output that was still in flight.
|
|
89
89
|
|
|
90
|
-
**Output is capped per channel.** Each channel accumulates output against a character
|
|
91
|
-
(`maxOutputChars`)
|
|
92
|
-
|
|
93
|
-
|
|
90
|
+
**Output is capped per channel and per line.** Each channel accumulates output against a character
|
|
91
|
+
budget (`maxOutputChars`), checked within each message, so overflow trips the moment a message
|
|
92
|
+
exceeds the budget rather than when it churns on. Each individual line is also capped at a generous
|
|
93
|
+
length (4096 chars), so a single genuinely oversized line cannot own the whole budget — while
|
|
94
|
+
legitimately long REPL output (JSON, reprs, errors) still passes through whole. Both truncations
|
|
95
|
+
are announced with explicit markers so the model knows output was cut.
|
|
94
96
|
|
|
95
97
|
**Cancellation is real.** An abort sends an `interrupt_request` on the control channel,
|
|
96
98
|
which raises a genuine `KeyboardInterrupt` in the running cell; the namespace survives. As a
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-repl-py",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A pi extension with a single tool: execute, running a TypeScript host with a persistent Python (ipykernel) evaluator and a user-configurable toolbox of functions.",
|
|
6
6
|
"keywords": [
|
package/src/engine/index.ts
CHANGED
|
@@ -26,6 +26,10 @@ function resolvePythonPath(cwd: string | undefined): string {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
const DEFAULT_MAX_OUTPUT_CHARS = 65536;
|
|
29
|
+
/** Per-line cap: one genuinely oversized line must not own the channel budget, while legitimately long
|
|
30
|
+
* REPL output (JSON, reprs, errors) still fits under the cap in one piece. Generous enough that only
|
|
31
|
+
* pathological giant lines are trimmed, unlike pi's grep where the line cap keeps matches terse. */
|
|
32
|
+
export const MAX_OUTPUT_LINE_CHARS = 4096;
|
|
29
33
|
const ABORT_GRACE_MS = 500;
|
|
30
34
|
const DEFAULT_SNAPSHOT_DEBOUNCE_MS = 1500;
|
|
31
35
|
|
|
@@ -98,6 +102,18 @@ function truncateWithMarker(text: string, maxChars: number, wasTruncated: boolea
|
|
|
98
102
|
return `${text.slice(0, maxChars)}\n[... output truncated at ${maxChars} chars ...]`;
|
|
99
103
|
}
|
|
100
104
|
|
|
105
|
+
/** Cap each individual line, so one giant line cannot own the whole channel budget (like grep's line cap). */
|
|
106
|
+
export function capLinesForContext(text: string): { text: string; trimmed: boolean } {
|
|
107
|
+
const lines = text.split("\n");
|
|
108
|
+
let trimmed = false;
|
|
109
|
+
const mapped = lines.map((line) => {
|
|
110
|
+
if (line.length <= MAX_OUTPUT_LINE_CHARS) return line;
|
|
111
|
+
trimmed = true;
|
|
112
|
+
return line.slice(0, MAX_OUTPUT_LINE_CHARS);
|
|
113
|
+
});
|
|
114
|
+
return { text: mapped.join("\n"), trimmed };
|
|
115
|
+
}
|
|
116
|
+
|
|
101
117
|
export class EngineManager {
|
|
102
118
|
private readonly options: EngineOptions;
|
|
103
119
|
private kernel?: KernelClient;
|
|
@@ -233,11 +249,18 @@ export class EngineManager {
|
|
|
233
249
|
});
|
|
234
250
|
if (r.status === "ok") this.scheduleSnapshot();
|
|
235
251
|
const status: ExecuteResult["status"] = opts.signal?.aborted ? "aborted" : r.status;
|
|
236
|
-
|
|
252
|
+
// Channel cap (truncateWithMarker), then per-line cap; both append a marker so truncation is explicit.
|
|
253
|
+
const finalize = (text: string, channelTruncated: boolean): string => {
|
|
254
|
+
let out = truncateWithMarker(text, maxChars, channelTruncated);
|
|
255
|
+
const line = capLinesForContext(out);
|
|
256
|
+
if (line.trimmed) out = `${line.text}\n[... some lines exceeded ${MAX_OUTPUT_LINE_CHARS} chars ...]`;
|
|
257
|
+
else out = line.text;
|
|
258
|
+
return out;
|
|
259
|
+
};
|
|
237
260
|
return {
|
|
238
|
-
stdout:
|
|
239
|
-
stderr:
|
|
240
|
-
result: r.result !== undefined ?
|
|
261
|
+
stdout: finalize(r.stdout, r.truncated?.stdout ?? false),
|
|
262
|
+
stderr: finalize(r.stderr, r.truncated?.stderr ?? false),
|
|
263
|
+
result: r.result !== undefined ? finalize(String(r.result), String(r.result).length > maxChars) : undefined,
|
|
241
264
|
error: r.error,
|
|
242
265
|
status,
|
|
243
266
|
durationMs: Date.now() - started,
|
package/src/extension/prompt.ts
CHANGED
|
@@ -9,8 +9,7 @@ export const executeToolDescription =
|
|
|
9
9
|
"You have one tool: a persistent Python workspace backed by a real `ipython` kernel. " +
|
|
10
10
|
"Variables, imports, and definitions survive across cells and turns — it is your working memory and action " +
|
|
11
11
|
"language. " +
|
|
12
|
-
"Helpers in `~/.pi/agent/pi-repl/helpers/` load at boot;
|
|
13
|
-
"`[k for k in globals() if not k.startswith('_')]`. A cell returns its final expression; printed output is " +
|
|
12
|
+
"Helpers in `~/.pi/agent/pi-repl/helpers/` load at boot. A cell returns its final expression; printed output is " +
|
|
14
13
|
"captured separately.";
|
|
15
14
|
|
|
16
15
|
export const executePromptSnippet =
|
|
@@ -40,7 +39,24 @@ export function buildPromptGuidelines(preloaded: string[]): string[] {
|
|
|
40
39
|
"Inspect what is present — count, print a few lines, list what is loaded — before committing. Build one " +
|
|
41
40
|
"step, run it, and use its output to choose the next.",
|
|
42
41
|
"",
|
|
42
|
+
"## Precise file and search work",
|
|
43
|
+
"Search narrowly and inspect only the lines you need. Do not dump whole files or repeat unchanged context. " +
|
|
44
|
+
"When walking directories, prune generated and hidden dirs — node_modules, .git, .venv, dist, __pycache__ — " +
|
|
45
|
+
"in the walk filter; never print a raw tree. " +
|
|
46
|
+
"For existing files, prefer a surgical old-text/new-text replacement over rewriting the file. Read the " +
|
|
47
|
+
"target region first, make the smallest unique replacement, then verify the changed region and file validity. " +
|
|
48
|
+
"Use complete writes only for new files or intentional full rewrites. Never leave a bare final expression: " +
|
|
49
|
+
"IPython displays it automatically; assign results and explicitly print only what you need.",
|
|
50
|
+
"",
|
|
51
|
+
"## Repository discipline",
|
|
52
|
+
"Inspect before changing. Preserve project conventions and unrelated content. Make the smallest valid change, " +
|
|
53
|
+
"verify it afterward, and never invent files, APIs, conventions, or test results.",
|
|
54
|
+
"",
|
|
43
55
|
"## Batch and print sparingly",
|
|
56
|
+
"Every printed value enters the conversation and consumes context. Treat output as expensive: do not print " +
|
|
57
|
+
"raw, recursive, or unbounded results. Explore and filter in variables first, then print only the small, " +
|
|
58
|
+
"bounded observation needed to make the next decision. Never dump an artifact and rely on truncation to " +
|
|
59
|
+
"control it. Quality of output is paramount.",
|
|
44
60
|
"Batch as much independent work as reasonably possible into one call. Keep large values in variables; " +
|
|
45
61
|
"print slices, counts, and summaries.",
|
|
46
62
|
"",
|
|
@@ -48,7 +64,7 @@ export function buildPromptGuidelines(preloaded: string[]): string[] {
|
|
|
48
64
|
? [
|
|
49
65
|
"## Helpers",
|
|
50
66
|
"User helpers load from `~/.pi/agent/pi-repl/helpers/` as workspace definitions. Their descriptions " +
|
|
51
|
-
"appear below.
|
|
67
|
+
"appear below.",
|
|
52
68
|
"",
|
|
53
69
|
...preloaded,
|
|
54
70
|
"",
|