pi-repl-py 0.2.8 → 0.3.0
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/docs/ARCHITECTURE.md +9 -7
- package/index.ts +7 -17
- package/package.json +1 -1
- package/src/engine/index.ts +3 -8
- package/src/engine/kernel.ts +9 -2
- package/src/extension/session-engine.ts +1 -1
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -57,15 +57,17 @@ That path is stable across updates because it sits outside the package's own dir
|
|
|
57
57
|
which npm replaces on each update. If `python3` or the network is missing at install time,
|
|
58
58
|
`postinstall` prints a clear notice and the host falls back at runtime.
|
|
59
59
|
|
|
60
|
-
At spawn, `resolvePythonPath`
|
|
60
|
+
At spawn, `resolvePythonPath` uses exactly one interpreter, the install venv:
|
|
61
61
|
|
|
62
|
-
1.
|
|
63
|
-
2.
|
|
64
|
-
3. `~/.pi/agent/pi-repl/venv` (package install)
|
|
65
|
-
4. `$PYTHON`, then `python3` (the fallback)
|
|
62
|
+
1. `~/.pi/agent/pi-repl/venv` (the package install)
|
|
63
|
+
2. `$PYTHON`, then `python3` (only if the install venv is missing)
|
|
66
64
|
|
|
67
|
-
|
|
68
|
-
venv
|
|
65
|
+
It deliberately does NOT auto-pick the repo's or current directory's `.venv`: a per-project
|
|
66
|
+
venv is not guaranteed to have `ipykernel`, so preferring it (as earlier versions did) made
|
|
67
|
+
the kernel die from a `ModuleNotFoundError` whenever cwd happened to contain such a venv. The
|
|
68
|
+
kernel therefore always runs in the stable install environment. The kernel starts in the
|
|
69
|
+
session's cwd, falling back to the host's own cwd if that directory no longer exists (a
|
|
70
|
+
deleted project dir), so a stale cwd can never prevent the kernel from coming up.
|
|
69
71
|
|
|
70
72
|
## The kernel client
|
|
71
73
|
|
package/index.ts
CHANGED
|
@@ -6,7 +6,7 @@ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
|
6
6
|
import { Type } from "typebox";
|
|
7
7
|
import { EngineManager } from "./src/engine/index.js";
|
|
8
8
|
import { ExecuteCellComponent, type ExecuteDetails, type ExecuteRenderState } from "./src/extension/render.js";
|
|
9
|
-
import { EngineLifecycle
|
|
9
|
+
import { EngineLifecycle } from "./src/extension/session-engine.js";
|
|
10
10
|
import { EXECUTE_DESCRIPTION, buildExecutePromptGuidelines, EXECUTE_PROMPT_SNIPPET } from "./src/extension/tool-meta.js";
|
|
11
11
|
|
|
12
12
|
const executeSchema = Type.Object({
|
|
@@ -87,23 +87,13 @@ export default function (pi: ExtensionAPI) {
|
|
|
87
87
|
}
|
|
88
88
|
// --- active: the whole surface collapses to the one tool ---
|
|
89
89
|
pi.setActiveTools(["execute"]);
|
|
90
|
-
// ---
|
|
90
|
+
// --- warm the engine (and its revive) in the background; no popup. ---
|
|
91
|
+
// --- acquire() dedupes, so the first execute awaits this same in-flight boot ---
|
|
91
92
|
location = { cwd: ctx.cwd, sessionFile: ctx.sessionManager.getSessionFile() ?? undefined };
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
content: `Revived ${restore.restored.length} variable(s) from the previous run: ${summarizeNames(restore.restored, 8)}${
|
|
97
|
-
restore.failed.length > 0
|
|
98
|
-
? `. Failed: ${summarizeNames(
|
|
99
|
-
restore.failed.map((f) => f.name),
|
|
100
|
-
8,
|
|
101
|
-
)}`
|
|
102
|
-
: ""
|
|
103
|
-
}`,
|
|
104
|
-
display: true,
|
|
105
|
-
});
|
|
106
|
-
}
|
|
93
|
+
void lifecycle.acquire("startup").catch(() => {
|
|
94
|
+
// --- boot/revive handled on the execute path; swallow so a background warm can never
|
|
95
|
+
// --- surface an unhandled rejection and the model never needs the restore notice ---
|
|
96
|
+
});
|
|
107
97
|
});
|
|
108
98
|
|
|
109
99
|
pi.on("session_shutdown", async () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-repl-py",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
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
|
@@ -5,21 +5,16 @@
|
|
|
5
5
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
6
6
|
import { homedir } from "node:os";
|
|
7
7
|
import { dirname, join } from "node:path";
|
|
8
|
-
import { fileURLToPath } from "node:url";
|
|
9
8
|
import { KernelClient } from "./kernel.js";
|
|
10
9
|
|
|
11
|
-
const GUEST_REL = fileURLToPath(new URL("./kernel.js", import.meta.url));
|
|
12
|
-
|
|
13
10
|
function installVenvPython(): string {
|
|
14
11
|
return join(homedir(), ".pi", "agent", "pi-repl", "venv", "bin", "python3");
|
|
15
12
|
}
|
|
16
13
|
|
|
17
14
|
/** Prefer a venv with ipykernel; else $PYTHON or python3. */
|
|
18
|
-
function resolvePythonPath(
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const cwdVenv = cwd ? join(cwd, ".venv", "bin", "python3") : "";
|
|
22
|
-
if (cwdVenv && existsSync(cwdVenv)) return cwdVenv;
|
|
15
|
+
function resolvePythonPath(_cwd: string | undefined): string {
|
|
16
|
+
// Only ever use the install venv: a project or repo `.venv` may lack ipykernel and
|
|
17
|
+
// shadow the good environment, killing the kernel. No auto-picking.
|
|
23
18
|
const installVenv = installVenvPython();
|
|
24
19
|
if (existsSync(installVenv)) return installVenv;
|
|
25
20
|
return process.env.PYTHON ?? "python3";
|
package/src/engine/kernel.ts
CHANGED
|
@@ -54,7 +54,14 @@ export interface SnapshotReply {
|
|
|
54
54
|
// --- boot preload: exec each helper; ls()/help() are gone, discovery is globals() ---
|
|
55
55
|
|
|
56
56
|
/** Read the helpers dir (same skip rules as the extension's prompt loader). */
|
|
57
|
-
|
|
57
|
+
/** A directory for the kernel to start in; if the requested cwd is gone, fall back to the
|
|
58
|
+
* evaluator's own cwd rather than letting spawn() die with ENOENT. A deleted project dir is
|
|
59
|
+
* a real resume case (pi guards it too) — the kernel must still come up. */
|
|
60
|
+
function resolveCwd(requested?: string): string {
|
|
61
|
+
if (requested && existsSync(requested)) return requested;
|
|
62
|
+
return process.cwd();
|
|
63
|
+
}
|
|
64
|
+
function readHelperSources(dir?: string): { name: string; source: string }[] {
|
|
58
65
|
// --- one fixed dir, resolved like the prompt side (helpers.ts) so both always agree ---
|
|
59
66
|
const d = dir ?? join(homedir(), ".pi", "agent", "pi-repl", "helpers");
|
|
60
67
|
if (!existsSync(d)) return [];
|
|
@@ -179,7 +186,7 @@ export class KernelClient {
|
|
|
179
186
|
static async start(pythonPath: string, opts: KernelOptions = {}): Promise<KernelClient> {
|
|
180
187
|
const connPath = join(tmpdir(), `pi-repl-kernel-${randomUUID()}.json`);
|
|
181
188
|
const child = spawn(pythonPath, ["-m", "ipykernel", "-f", connPath, "--no-stdout"], {
|
|
182
|
-
cwd: opts.cwd,
|
|
189
|
+
cwd: resolveCwd(opts.cwd),
|
|
183
190
|
env: { ...process.env, ...(opts.env ?? {}) },
|
|
184
191
|
stdio: ["ignore", "pipe", "pipe"],
|
|
185
192
|
});
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import type { RestoreResult } from "../engine/index.js";
|
|
4
4
|
|
|
5
5
|
/** Show enough names to orient, then count the rest (a revive can carry hundreds). */
|
|
6
|
-
|
|
6
|
+
function summarizeNames(names: readonly string[], limit: number): string {
|
|
7
7
|
if (names.length <= limit) return names.join(", ");
|
|
8
8
|
return `${names.slice(0, limit).join(", ")} … and ${names.length - limit} more`;
|
|
9
9
|
}
|