pi-repl-py 0.2.1 → 0.2.3
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 +6 -6
- package/package.json +1 -1
- package/scripts/setup-venv.mjs +37 -26
- package/src/extension/prompt.ts +39 -45
package/README.md
CHANGED
|
@@ -43,15 +43,15 @@ clear notice. How the interpreter is resolved is in [docs/ARCHITECTURE.md](docs/
|
|
|
43
43
|
- **A real `ipython` kernel**, not a hand-rolled `exec` loop.
|
|
44
44
|
- **Shell and file IO as plain Python.** `!cmd` and `%%bash` run shell fire-and-forget,
|
|
45
45
|
`subprocess.run(...)` brings the result back into a variable, and `open()` / `pathlib`
|
|
46
|
-
read and write files
|
|
46
|
+
read and write files. No wrapper API to learn, and nothing extra to describe to the model.
|
|
47
47
|
- **Error survival.** A cell that throws reports the traceback and the kernel keeps going.
|
|
48
48
|
- **An honest evaluator.** If it restarts, it names what state it could revive and what it lost, so you don't trust memory that's gone.
|
|
49
49
|
|
|
50
50
|
## Helpers
|
|
51
51
|
|
|
52
|
-
A **helper** is a `.py` file that gets exec'd into every kernel, so whatever it defines
|
|
53
|
-
functions, classes, constants, imports, or a module that manages a tricky piece of
|
|
54
|
-
complexity
|
|
52
|
+
A **helper** is a `.py` file that gets exec'd into every kernel, so whatever it defines
|
|
53
|
+
(like functions, classes, constants, imports, or a module that manages a tricky piece of
|
|
54
|
+
complexity) is available in the workspace. Drop a file in the one helpers directory and
|
|
55
55
|
restart the session; e.g. `helpers/double.py` defining `def double(x)` becomes callable as
|
|
56
56
|
`double(...)`. It ships **empty** (shell and file IO are already plain Python), so a fresh
|
|
57
57
|
install preloads nothing until you add one. Each helper's `helper_description` is shown to
|
|
@@ -66,7 +66,7 @@ Everything the extension keeps lives under one folder in your home directory:
|
|
|
66
66
|
state/ per-session namespace snapshots
|
|
67
67
|
```
|
|
68
68
|
|
|
69
|
-
The helpers directory is fixed at `~/.pi/agent/pi-repl/helpers
|
|
69
|
+
The helpers directory is fixed at `~/.pi/agent/pi-repl/helpers`. No config file.
|
|
70
70
|
|
|
71
71
|
Changing a helper (adding/removing a file, renaming one with a `_` prefix) needs a
|
|
72
72
|
**session restart / `/reload`**: the prompt list is built when `execute` is registered and
|
|
@@ -91,4 +91,4 @@ The Python interpreter is auto-resolved (the venv, else `$PYTHON`/`python3`).
|
|
|
91
91
|
|
|
92
92
|
## License
|
|
93
93
|
|
|
94
|
-
MIT
|
|
94
|
+
MIT. See [LICENSE](LICENSE).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-repl-py",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
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/scripts/setup-venv.mjs
CHANGED
|
@@ -2,30 +2,18 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* postinstall: build the stable per-user Python venv the evaluator needs.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* the Jupyter protocol; `ipykernel` is the only hard runtime dependency. When
|
|
7
|
-
* this is installed as a pi package there is no repo-local `.venv` (gitignored
|
|
8
|
-
* and excluded from the npm tarball), so we create one at a stable path the
|
|
9
|
-
* engine also knows about:
|
|
5
|
+
* On a package install the venv is built at a stable path the engine knows:
|
|
10
6
|
*
|
|
11
7
|
* ~/.pi/agent/pi-repl/venv/bin/python3
|
|
12
8
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* There is no helper/config folder anywhere in this package (no
|
|
18
|
-
* src/engine/helpers, no templates/). The helpers dir is created if missing,
|
|
19
|
-
* but no default helpers are seeded — the REPL itself already provides shell
|
|
20
|
-
* (via `!cmd`, `%%bash`, `subprocess`) and file IO (via `open`, `pathlib`).
|
|
21
|
-
* Users add their own helper .py files freely; existing files are never clobbered.
|
|
22
|
-
*
|
|
23
|
-
* Failures are non-fatal: if there's no system python3 or no network we print a
|
|
24
|
-
* clear notice and let the engine fall back to '$PYTHON' or 'python3' at runtime.
|
|
9
|
+
* The venv is repaired in place: if it exists but ipykernel is not importable,
|
|
10
|
+
* this reflushes the venv and reinstalls rather than trusting a half-built one.
|
|
11
|
+
* Failures are NOT silent: a bad build exits non-zero so `npm install` / `pi
|
|
12
|
+
* install` visibly fails instead of leaving a broken evaluator.
|
|
25
13
|
*/
|
|
26
14
|
|
|
27
15
|
import { execSync } from "node:child_process";
|
|
28
|
-
import {
|
|
16
|
+
import { mkdirSync } from "node:fs";
|
|
29
17
|
import { homedir } from "node:os";
|
|
30
18
|
import { join } from "node:path";
|
|
31
19
|
|
|
@@ -34,6 +22,17 @@ const PY = join(VENV_DIR, "bin", "python3");
|
|
|
34
22
|
const DEPS = ["ipykernel"];
|
|
35
23
|
const HELPERS_DIR = join(homedir(), ".pi", "agent", "pi-repl", "helpers");
|
|
36
24
|
|
|
25
|
+
// A venv that exists but can't import ipykernel is broken. Never trust the
|
|
26
|
+
// binary alone — a half-built venv otherwise looks "already up" forever.
|
|
27
|
+
function ipykernelOk() {
|
|
28
|
+
try {
|
|
29
|
+
execSync(`${PY} -c "import ipykernel"`, { stdio: "ignore" });
|
|
30
|
+
return true;
|
|
31
|
+
} catch {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
37
36
|
function log(m) {
|
|
38
37
|
process.stdout.write(`[pi-repl] ${m}\n`);
|
|
39
38
|
}
|
|
@@ -65,29 +64,41 @@ function seedHelpersDir() {
|
|
|
65
64
|
|
|
66
65
|
function main() {
|
|
67
66
|
seedHelpersDir();
|
|
68
|
-
if (
|
|
69
|
-
log(`venv
|
|
67
|
+
if (ipykernelOk()) {
|
|
68
|
+
log(`venv ready (ipykernel present) at ${VENV_DIR}`);
|
|
70
69
|
return;
|
|
71
70
|
}
|
|
72
71
|
const systemPython = findSystemPython();
|
|
73
72
|
if (!systemPython) {
|
|
74
|
-
|
|
73
|
+
fail(
|
|
75
74
|
`no python3 found on PATH; could not create the evaluator venv. ` +
|
|
76
|
-
`Install python3 and run '${PY.slice(-60)} -m venv' manually
|
|
75
|
+
`Install python3 and run '${PY.slice(-60)} -m venv' manually.`
|
|
77
76
|
);
|
|
78
77
|
return;
|
|
79
78
|
}
|
|
80
|
-
log(`
|
|
79
|
+
log(`building evaluator venv at ${VENV_DIR} (uses ${systemPython})...`);
|
|
81
80
|
try {
|
|
82
81
|
mkdirSync(join(VENV_DIR, ".."), { recursive: true });
|
|
83
|
-
|
|
82
|
+
// flush a half-built venv so pip starts from a clean, knowable slate
|
|
83
|
+
execSync(`${systemPython} -m venv --clear ${VENV_DIR}`, { stdio: "inherit" });
|
|
84
84
|
execSync(`${PY} -m pip install --upgrade pip`, { stdio: "inherit" });
|
|
85
85
|
execSync(`${PY} -m pip install ${DEPS.join(" ")}`, { stdio: "inherit" });
|
|
86
|
+
if (!ipykernelOk()) {
|
|
87
|
+
fail(`ipykernel still not importable after install; the evaluator won't start.`);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
86
90
|
log("done. The pi-repl evaluator will use this venv.");
|
|
87
91
|
} catch (error) {
|
|
88
|
-
|
|
89
|
-
|
|
92
|
+
// A real failure must not exit 0 with a broken venv. npm/pi will see the
|
|
93
|
+
// nonzero exit and report the install as failed instead of silently
|
|
94
|
+
// handing the user a dead evaluator.
|
|
95
|
+
fail(`could not build the evaluator venv (${error && error.message ? error.message : error}). `);
|
|
90
96
|
}
|
|
91
97
|
}
|
|
92
98
|
|
|
99
|
+
function fail(m) {
|
|
100
|
+
process.stderr.write(`[pi-repl] ERROR: ${m}\n`);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
|
|
93
104
|
main();
|
package/src/extension/prompt.ts
CHANGED
|
@@ -1,76 +1,70 @@
|
|
|
1
1
|
// --- prompt: the execute tool's model-facing contract (pure, no pi/helper dep) ---
|
|
2
|
+
//
|
|
3
|
+
// Verbatim clauses from CodeAct (arXiv 2402.01030) and RLM (arXiv 2512.24601)
|
|
4
|
+
// are trimmed to what pi-repl actually has — no sub-LLMs, no recursion, no
|
|
5
|
+
// context variable — and the rest is stripped for lean context. Less prose,
|
|
6
|
+
// more signal; the machine reads every line every turn.
|
|
2
7
|
|
|
3
8
|
export const executeToolDescription =
|
|
4
9
|
"You have one tool: a persistent Python workspace backed by a real `ipython` kernel. " +
|
|
5
|
-
"Variables, imports,
|
|
6
|
-
"
|
|
7
|
-
"Helpers in `~/.pi/agent/pi-repl/helpers/`
|
|
8
|
-
"`[k for k in globals() if not k.startswith('_')]`. A cell returns its final expression; printed output " +
|
|
9
|
-
"
|
|
10
|
+
"Variables, imports, and definitions survive across cells and turns — it is your working memory and action " +
|
|
11
|
+
"language. " +
|
|
12
|
+
"Helpers in `~/.pi/agent/pi-repl/helpers/` load at boot; list them with " +
|
|
13
|
+
"`[k for k in globals() if not k.startswith('_')]`. A cell returns its final expression; printed output is " +
|
|
14
|
+
"captured separately.";
|
|
10
15
|
|
|
11
16
|
export const executePromptSnippet =
|
|
12
|
-
"
|
|
13
|
-
"
|
|
17
|
+
"Work in the workspace: keep artifacts in variables, compose related actions in Python, print only what " +
|
|
18
|
+
"the next step needs, and revise from what you observe.";
|
|
14
19
|
|
|
15
20
|
// --- the workspace doctrine riding the execute tool ---
|
|
16
21
|
export function buildPromptGuidelines(preloaded: string[]): string[] {
|
|
17
22
|
return [
|
|
18
|
-
"##
|
|
19
|
-
"
|
|
20
|
-
"
|
|
23
|
+
"## Your only workspace",
|
|
24
|
+
"`execute` is the only callable tool. Python replaces a read, shell, search, and edit tool rack. State " +
|
|
25
|
+
"persists across cells and turns.",
|
|
21
26
|
"",
|
|
22
|
-
"##
|
|
23
|
-
"
|
|
27
|
+
"## Work in the workspace, not the transcript",
|
|
28
|
+
"Load files, command results, search hits, and computed artifacts into variables once; filter, compare, " +
|
|
29
|
+
"branch, edit, and verify them in later cells. Do not re-read or paste raw material back. Print only the " +
|
|
30
|
+
"small observation needed for the next decision; keep the full artifact in a variable.",
|
|
24
31
|
"",
|
|
25
|
-
"##
|
|
26
|
-
"
|
|
27
|
-
"
|
|
32
|
+
"## A cell is a small program",
|
|
33
|
+
"Compose filesystem access, shell commands, searches, transforms, checks, and edits in ordinary Python " +
|
|
34
|
+
"when they belong to the same step.",
|
|
28
35
|
"",
|
|
29
|
-
"##
|
|
30
|
-
"
|
|
31
|
-
"code that depends on them.",
|
|
36
|
+
"## Revise on observations",
|
|
37
|
+
"Revise prior actions or emit new actions upon new observations.", // CodeAct core
|
|
32
38
|
"",
|
|
33
|
-
"##
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"silent cell automatically). `open()` / `pathlib` read and write files. For safe edits: read the full " +
|
|
37
|
-
"file, modify in memory, write once, then re-read to verify.",
|
|
39
|
+
"## Probe, then build",
|
|
40
|
+
"Inspect what is present — count, print a few lines, list what is loaded — before committing. Build one " +
|
|
41
|
+
"step, run it, and use its output to choose the next.",
|
|
38
42
|
"",
|
|
39
|
-
"## Batch
|
|
40
|
-
"Batch independent
|
|
41
|
-
"
|
|
42
|
-
"",
|
|
43
|
-
"## Search efficiently",
|
|
44
|
-
"Use `rg`, `fd`, `grep`, `find` via `subprocess.run` for deep searches, not Python loops.",
|
|
43
|
+
"## Batch and print sparingly",
|
|
44
|
+
"Batch as much independent work as reasonably possible into one call. Keep large values in variables; " +
|
|
45
|
+
"print slices, counts, and summaries.",
|
|
45
46
|
"",
|
|
46
47
|
...(preloaded.length
|
|
47
48
|
? [
|
|
48
49
|
"## Helpers",
|
|
49
|
-
"User helpers load from `~/.pi/agent/pi-repl/helpers
|
|
50
|
-
"List what
|
|
50
|
+
"User helpers load from `~/.pi/agent/pi-repl/helpers/` as workspace definitions. Their descriptions " +
|
|
51
|
+
"appear below. List what is loaded with `[k for k in globals() if not k.startswith('_')]`.",
|
|
51
52
|
"",
|
|
52
53
|
...preloaded,
|
|
53
54
|
"",
|
|
54
55
|
]
|
|
55
56
|
: []),
|
|
56
|
-
"##
|
|
57
|
-
"
|
|
58
|
-
|
|
59
|
-
"## Output discipline",
|
|
60
|
-
"Printing is a context cost: everything a cell prints stays in the transcript. Print slices, " +
|
|
61
|
-
"counts, and summaries. Keep large values in variables. End a cell with `;` to suppress the " +
|
|
62
|
-
"last-expression echo.",
|
|
57
|
+
"## Shell and search",
|
|
58
|
+
"`subprocess.run(..., timeout=...)` when you need a result — always set a `timeout`, the evaluator does not " +
|
|
59
|
+
"kill a silent cell. Use `rg`/`grep`/`find` via `subprocess.run` for deep searches, not Python loops.",
|
|
63
60
|
"",
|
|
64
61
|
"## Environment boundary",
|
|
65
62
|
"The evaluator runs in a project-local venv, not the system Python. Do not install a target project's " +
|
|
66
|
-
"dependencies into the evaluator
|
|
67
|
-
"through their own interface and normal commands.",
|
|
63
|
+
"dependencies into the evaluator. Run external projects through their own interface and normal commands.",
|
|
68
64
|
"",
|
|
69
65
|
"## Engine reset guard",
|
|
70
|
-
"If
|
|
71
|
-
"
|
|
72
|
-
"
|
|
73
|
-
"command until you have confirmed it still holds what you expect. Functions, classes, and live " +
|
|
74
|
-
"handles cannot be snapshotted and must be redefined.",
|
|
66
|
+
"If output begins with `<repl_engine_reset>`, the kernel was rebuilt from the last snapshot. Re-verify a " +
|
|
67
|
+
"revived variable before reusing it — especially in a shell command. Functions, classes, and live handles " +
|
|
68
|
+
"are not snapshotted and must be redefined.",
|
|
75
69
|
];
|
|
76
70
|
}
|