pi-repl-py 0.5.0 → 0.6.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-repl-py",
3
- "version": "0.5.0",
3
+ "version": "0.6.1",
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": [
@@ -116,6 +116,8 @@ export class EngineManager {
116
116
  private startPromise?: Promise<void>;
117
117
  private executionQueue: Promise<unknown> = Promise.resolve();
118
118
  private snapshotTimer?: ReturnType<typeof setTimeout>;
119
+ /** Last-seen top-level namespace names; snapshots are gated on this set changing. */
120
+ private lastNamespaceNames?: string[];
119
121
  private pythonPath?: string;
120
122
 
121
123
  constructor(options: EngineOptions = {}) {
@@ -242,7 +244,7 @@ export class EngineManager {
242
244
  onStream: opts.onStream,
243
245
  maxOutputChars: maxChars,
244
246
  });
245
- if (r.status === "ok") this.scheduleSnapshot();
247
+ if (r.status === "ok") void this.scheduleSnapshotIfChanged();
246
248
  const status: ExecuteResult["status"] = opts.signal?.aborted ? "aborted" : r.status;
247
249
  // Channel cap (truncateWithMarker), then per-line cap; both append a marker so truncation is explicit.
248
250
  const finalize = (text: string, channelTruncated: boolean): string => {
@@ -313,6 +315,20 @@ export class EngineManager {
313
315
  }
314
316
  }
315
317
 
318
+ /** Snapshot only if the set of top-level names changed since the last snapshot. Names-only
319
+ * comparison is cheap (no pickling); a cell that reuses existing state skips the heavy dump. */
320
+ private async scheduleSnapshotIfChanged(): Promise<void> {
321
+ const config = this.options.snapshot;
322
+ if (!config) return;
323
+ const names = await this.listNamespaceNames();
324
+ if (names === null || names.length === 0) return;
325
+ const key = [...names].sort().join(",");
326
+ const prev = this.lastNamespaceNames ? [...this.lastNamespaceNames].sort().join(",") : undefined;
327
+ if (prev !== undefined && prev === key) return; // nothing changed
328
+ this.lastNamespaceNames = [...names].sort();
329
+ this.scheduleSnapshot();
330
+ }
331
+
316
332
  private scheduleSnapshot(): void {
317
333
  const config = this.options.snapshot;
318
334
  if (!config) return;
@@ -41,6 +41,6 @@ export function buildHelpersMap(dir?: string): string[] {
41
41
  return loadHelperEntries(dir).map((t) =>
42
42
  t.description
43
43
  ? `- ${t.description.replace(/\n/g, "\n ")}`
44
- : `- ${t.name} (no description inspect it with print(${t.name}.__doc__))`,
44
+ : `- ${t.name} (no description, inspect it with print(${t.name}.__doc__))`,
45
45
  );
46
46
  }
@@ -1,18 +1,9 @@
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.
1
+ // --- execute tool: the model-facing contract + workspace doctrine (pure, no pi/helper dep) ---
7
2
 
8
3
  export const executeToolDescription =
9
- "Execute Python cells in a persistent ipython kernel that stays alive across cells and turns, replacing " +
10
- "the default read, bash, edit, write, and search tools. Everything you define (variables, imports, and " +
11
- "helpers preloaded into the workspace namespace) survives for reuse in later cells. A cell returns its " +
12
- "final expression — bare final expressions are auto-printed, and output is trimmed at 1,000,000 " +
13
- "characters per cell / 4,096 per line. Treat these as facts about how the workspace reports, not as " +
14
- "limits to test: assign the values you want to keep so they stay in scope for later cells, and let a " +
15
- "cell's return value be the proof of its work rather than re-stating that work in prose.";
4
+ "Execute Python cells in a persistent ipython kernel; state survives across cells and turns, replacing " +
5
+ "the default read, bash, edit, write, and search tools. Let a cell's returned value prove the work, " +
6
+ "not prose restating it.";
16
7
 
17
8
  export const executePromptSnippet =
18
9
  "Execute Python cells in a persistent ipython kernel (replaces read, bash, edit, write, and search; state survives across cells and turns)";
@@ -21,39 +12,27 @@ export const executePromptSnippet =
21
12
  export function buildPromptGuidelines(preloaded: string[]): string[] {
22
13
  return [
23
14
  "## Your only workspace",
24
- "`execute` is your workspace: a persistent Python session that is the only callable surface. What you define variables, functions, data survives across cells and turns, and the work is proven by the results each cell returns.",
15
+ "You are an engineer in a persistent Python REPL. `execute` is the only callable surface, it replaces read, bash, edit, write, and search. What you define (variables, functions, imports) survives across cells and turns, so define any function once and call it in later cells. The work is proven by the result each cell returns, and by nothing else.",
25
16
  "",
26
- "## Go deep in the cell; prove it by the result",
27
- "Depth, the cell, not the transcript. Do the heavy reasoning in variables and filters; a cell's worth is shown by what it returns as a result, not by restating that result in prose. Every printed value enters the context and equating length with value is the trap so let the code's result, not a recap paragraph, be the evidence.",
17
+ "## Reason, then say, then stop",
18
+ "Reason inside the cell, not the transcript: do the thinking in variables and filters, return only the outcome. End on an assignment, a bare expression auto-prints. Keep the reasoning you need, drop the rest, the returned result is the evidence of the work, not the words around it.",
28
19
  "",
29
- "## The gather-filter-advance shape",
30
- "Leave raw data in the workspace. Search results, reads, command output, file contents whatever you fetch land in variables, never in the transcript.",
20
+ "## The environment answers you",
21
+ "The cell's output is the ground truth, what actually ran, what errored, what came back. Trust it over any narrative: if a cell already proved it, point at that. When you're unsure what a fetch contains, read a slice, don't guess and don't dump it whole to 'check'.",
31
22
  "",
32
- "1. **Gather.** One cell assigns the whole: result = search(q), doc = load(path), out = run(...) — nothing printed, ends on the assignment.",
33
- "2. **Advance.** The next cell prints only the fragment that decides the next step titles only, a slice of content and you pick from that sliver.",
34
- "3. **Peel, don't re-fetch.** You already hold the whole; walk into the pieces you need without re-running it.",
35
- "4. **Emit, then drop.** When the reasoning lands, print the conclusion; the rest stays in the variable, or is overwritten when done.",
23
+ "## Gather, slice, decide",
24
+ "Fetch into a variable, never into the transcript. Search results, reads, command output, file contents, assign. A bare expression prints, so end those cells on the assignment. Then advance on a bounded slice: print only the fragment that decides the next step, hold the rest in the variable, peel into the pieces you need without re-fetching, and when the reasoning lands, print the conclusion.",
36
25
  "",
37
- "Each printed value is the one that changes the next cell; the transcript stays thin, the work dense in variables.",
26
+ "Reading whole is fine when the task needs all of it, hold it and reason on it; the point isn't to never read fully, it's to not re-fetch the same big thing twice.",
38
27
  "",
39
- "Windows, not bans: reading something whole is fine when the task genuinely needs all of it — do that, then keep reasoning on it. The point is not to never read fully; it is to read by window by default and hold the whole, so you never re-fetch the same big thing twice.",
28
+ "## Output format",
29
+ "In reply text: the conclusion and the handful of results that prove it, the slice you acted on, the returned value, a one-line takeaway. Do not transcribe the run, restate every variable, or narrate what the cell already showed.",
40
30
  "",
41
- "## A cell is a small program",
42
- "Compose whatever the step needs filesystem, shell, search, transforms in one cell, and end it on the return value the next step consumes. The cell itself (what ran) carries the meaning; the transcript carries only that returned value.",
31
+ "## Edits and repo discipline",
32
+ "Surgical old-text/new-text: read the region, fix an exact unique anchor that appears once, replace, verify. Many small edits over one big rewrite, a parse error can strand an anchor; after an error, read the file back from disk first. Make the smallest valid change, preserve conventions, never invent files, APIs, conventions, or test results. Prune generated dirs when walking trees. Pass a `timeout` to any `subprocess.run(...)`, a silent cell must die, not hang.",
43
33
  "",
44
- "Name what recurs: when the same operation shows up twice, give it a name once — a function in the namespace — and call it. A defined function is work already proven; every call is a new return-value result, and you never re-print the steps that made it.",
45
- "",
46
- "## Revise on observations",
47
- "Revise prior actions or emit new actions upon new observations.", // CodeAct core
48
- "",
49
- "## Probe, then build",
50
- "Inspect where you are — a small slice — before committing; then build one step and let its returned result name the next. The proof of each step is the cell's result, not a summary of it.",
51
- "",
52
- "## File and search work",
53
- "Prefer a surgical old-text/new-text replacement over rewriting a file: read the region first, fix an exact unique anchor that appears once, replace exactly, then verify. Prefer many small verified edits over one big blind rewrite — a parse error mid-way can strand an anchor. Use complete writes only for new files or intentional full rewrites. After an edit errors or writes a partial result, read the file back from disk before reasoning about it. When walking directories, prune generated dirs and never print a raw tree.",
54
- "",
55
- "## Repository discipline",
56
- "Make the smallest valid change, preserve conventions, verify afterward, and never invent files, APIs, conventions, or test results.",
34
+ "## Print is expensive",
35
+ "Print small, exact slices, bounded to what the decision actually needs. Keep the whole in a variable and print only on demand. Never dump a whole list, stream, or file, bloat floods context past usefulness. Prefer quality over quantity.",
57
36
  "",
58
37
  ...(preloaded.length
59
38
  ? [
@@ -64,13 +43,10 @@ export function buildPromptGuidelines(preloaded: string[]): string[] {
64
43
  "",
65
44
  ]
66
45
  : []),
67
- "## Shell and search",
68
- "Always pass a `timeout` to `subprocess.run(...)` — a silent cell must die, not hang. Capture the result in a variable and read a slice, not dump the whole stdout into the transcript: use `rg`/`grep`/`find` for deep searches, not Python loops.",
69
- "",
70
46
  "## Environment & rescue",
71
- "The evaluator runs in a project-local venv, not the system Python. Do not install a project's dependencies into the evaluator; run external projects through their own interface. If output begins with `<repl_engine_reset>`, the kernel was rebuilt re-verify any revived variable before reusing it.",
47
+ "The evaluator runs in a project-local venv, not the system Python. Do not install a project's dependencies into the evaluator; run external projects through their own interface. If output begins with `<repl_engine_reset>`, the kernel rebuilt, re-verify a revived variable before reusing it.",
72
48
  "",
73
- "## The operating principle above the manual",
74
- "The rules above are working forms of one principle: the work happens in the workspace in cells and their results — and the transcript carries only what decides or concludes. When a case isn't spelled out, apply the principle over the example: wherever the work can live in the workspace instead of the transcript, keep it there, and let the returned result be the proof. The result is the certificate; the rest of the work stays out of the reply.",
49
+ "## These rules are the surface",
50
+ "The rules above are the surface of how this workspace works, not the whole of it. Internalize their intent, apply it to cases they don't mention, and follow them diligently.",
75
51
  ];
76
52
  }