pi-repl-py 0.6.6 → 0.6.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/README.md +11 -8
- package/docs/ARCHITECTURE.md +7 -5
- package/docs/helpers.md +18 -5
- package/index.ts +1 -1
- package/package.json +1 -1
- package/src/engine/helpers-locate.ts +24 -0
- package/src/engine/index.ts +3 -3
- package/src/engine/kernel.ts +20 -13
- package/src/extension/helpers.ts +34 -20
- package/src/extension/prompt.ts +15 -44
- package/src/extension/tool-meta.ts +4 -4
package/README.md
CHANGED
|
@@ -64,22 +64,25 @@ On **Termux (Android)**, the `postinstall` venv build can fail because `ipykerne
|
|
|
64
64
|
|
|
65
65
|
A **helper** is a `.py` file that gets exec'd into every kernel, so whatever it defines
|
|
66
66
|
(like functions, classes, constants, imports, or a module that manages a tricky piece of
|
|
67
|
-
complexity) is available in the workspace. Drop a file in
|
|
68
|
-
|
|
69
|
-
`double
|
|
70
|
-
|
|
71
|
-
|
|
67
|
+
complexity) is available in the workspace. Drop a file in a `.pi/helpers/` directory in
|
|
68
|
+
your project (or `~/.pi/agent/pi-repl/helpers/` for every project) and restart the session;
|
|
69
|
+
e.g. `helpers/double.py` defining `def double(x)` becomes callable as `double(...)`. Global
|
|
70
|
+
helpers ship **empty** (shell and file IO are already plain Python), so a fresh install
|
|
71
|
+
preloads nothing until you add one. Project helpers shadow same-named global ones. Each
|
|
72
|
+
helper's `helper_description` is shown to the model verbatim; the full contract lives in
|
|
73
|
+
[docs/helpers.md](docs/helpers.md).
|
|
72
74
|
|
|
73
|
-
|
|
75
|
+
The extension keeps its runtime under one folder in your home directory:
|
|
74
76
|
|
|
75
77
|
```
|
|
76
78
|
~/.pi/agent/pi-repl/
|
|
77
79
|
venv/ the Python interpreter + ipykernel
|
|
78
|
-
helpers/
|
|
80
|
+
helpers/ global helpers (created empty on install; every *.py loads)
|
|
79
81
|
state/ per-session namespace snapshots
|
|
80
82
|
```
|
|
81
83
|
|
|
82
|
-
|
|
84
|
+
Project helpers live in `<project>/.pi/helpers/` instead; both tiers are scanned with the
|
|
85
|
+
project one first. No config file.
|
|
83
86
|
|
|
84
87
|
Changing a helper (adding/removing a file, renaming one with a `_` prefix) needs a
|
|
85
88
|
**session restart / `/reload`**: the prompt list is built when `execute` is registered and
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -175,11 +175,13 @@ watchdog timeout.
|
|
|
175
175
|
state/ per-session namespace snapshots
|
|
176
176
|
```
|
|
177
177
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
178
|
+
Helpers merge project and global dirs: `resolveHelperDirs` walks from the working
|
|
179
|
+
directory up to the git root collecting `.pi/helpers/`, then appends
|
|
180
|
+
`~/.pi/agent/pi-repl/helpers`. Both the prompt loader and the kernel's `readHelperSources`
|
|
181
|
+
walk the same ordered list with first-seen-wins, so a project helper shadows the same-named
|
|
182
|
+
global one and both sides are guaranteed to agree. The venv is built automatically, and the
|
|
183
|
+
interpreter follows the order above. No setting is needed. The per-cell silence watchdog is
|
|
184
|
+
off by default (`PI_REPL_TIMEOUT_MS=0`: a silent but working cell may run on).
|
|
183
185
|
|
|
184
186
|
## Reference documentation
|
|
185
187
|
|
package/docs/helpers.md
CHANGED
|
@@ -9,13 +9,26 @@ name or any other public name in the file.
|
|
|
9
9
|
|
|
10
10
|
## Where helpers live
|
|
11
11
|
|
|
12
|
+
Helpers come from two places: a **project** directory and a **global** directory.
|
|
13
|
+
|
|
12
14
|
```text
|
|
13
|
-
|
|
15
|
+
<project>/.pi/helpers/ project helpers (looked up from the working dir)
|
|
16
|
+
~/.pi/agent/pi-repl/helpers/ global helpers (every project)
|
|
14
17
|
```
|
|
15
18
|
|
|
16
|
-
The directory is created empty when pi-repl is installed.
|
|
19
|
+
The global directory is created empty when pi-repl is installed. In a project, any
|
|
20
|
+
`.pi/helpers/` directory is picked up by walking up from the working directory to the
|
|
21
|
+
git repo root, so a helper works no matter how deep in the project you are.
|
|
22
|
+
|
|
23
|
+
Every `.py` file found is loaded when the evaluator starts; files whose names begin with
|
|
24
|
+
`_` are ignored.
|
|
25
|
+
|
|
26
|
+
The two tiers merge: a project helper **shadows** a same-named global helper, and global
|
|
27
|
+
helpers fill in whatever the project does not define. One file name appears once in the
|
|
28
|
+
tool prompt and once in the kernel.
|
|
17
29
|
|
|
18
|
-
After adding, changing, renaming, or disabling a helper, run `/reload` or start a new
|
|
30
|
+
After adding, changing, renaming, or disabling a helper, run `/reload` or start a new
|
|
31
|
+
`pi --repl` session. The running evaluator does not watch the directories for changes.
|
|
19
32
|
|
|
20
33
|
## A small function helper
|
|
21
34
|
|
|
@@ -109,7 +122,7 @@ Use docstrings for argument details, defaults, return values, errors, environmen
|
|
|
109
122
|
|
|
110
123
|
## How loading works
|
|
111
124
|
|
|
112
|
-
At startup, two parts of pi-repl read the same helper
|
|
125
|
+
At startup, two parts of pi-repl read the same merged helper list (project dirs first, global last):
|
|
113
126
|
|
|
114
127
|
1. The kernel executes each eligible `.py` file. Its definitions become names in the Python workspace.
|
|
115
128
|
2. The host reads `helper_description` to build the helper guidance shown to the model.
|
|
@@ -196,7 +209,7 @@ The loader skips it. Rename it back and reload when you want it again.
|
|
|
196
209
|
|
|
197
210
|
## Checklist
|
|
198
211
|
|
|
199
|
-
- [ ] The file is in `~/.pi/agent/pi-repl/helpers
|
|
212
|
+
- [ ] The file is in `~/.pi/agent/pi-repl/helpers/` (global) or in `<project>/.pi/helpers/` (project-scoped).
|
|
200
213
|
- [ ] Its public names and call shapes are clear.
|
|
201
214
|
- [ ] `helper_description` is short enough for every-turn context.
|
|
202
215
|
- [ ] Detailed behavior is in docstrings.
|
package/index.ts
CHANGED
|
@@ -125,7 +125,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
125
125
|
label: "execute",
|
|
126
126
|
description: EXECUTE_DESCRIPTION,
|
|
127
127
|
promptSnippet: EXECUTE_PROMPT_SNIPPET,
|
|
128
|
-
promptGuidelines: buildExecutePromptGuidelines(),
|
|
128
|
+
promptGuidelines: buildExecutePromptGuidelines(process.cwd()),
|
|
129
129
|
parameters: executeSchema,
|
|
130
130
|
renderShell: "self",
|
|
131
131
|
renderCall(args, theme, context) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-repl-py",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.8",
|
|
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": [
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// --- shared helper-dir resolution: prompt and kernel must read the same ordered list ---
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { dirname, join, resolve } from "node:path";
|
|
5
|
+
|
|
6
|
+
const GLOBAL_HELPERS_DIR = join(homedir(), ".pi", "agent", "pi-repl", "helpers");
|
|
7
|
+
|
|
8
|
+
/** Ordered candidate dirs: nearest .pi/helpers up to the git root, then the global dir last. */
|
|
9
|
+
export function resolveHelperDirs(cwd?: string, globalDir?: string): string[] {
|
|
10
|
+
const dirs: string[] = [];
|
|
11
|
+
if (cwd) {
|
|
12
|
+
let cur = resolve(cwd);
|
|
13
|
+
for (;;) {
|
|
14
|
+
const d = join(cur, ".pi", "helpers");
|
|
15
|
+
if (existsSync(d)) dirs.push(d);
|
|
16
|
+
if (existsSync(join(cur, ".git"))) break;
|
|
17
|
+
const parent = dirname(cur);
|
|
18
|
+
if (parent === cur) break;
|
|
19
|
+
cur = parent;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
dirs.push(globalDir ?? GLOBAL_HELPERS_DIR);
|
|
23
|
+
return dirs;
|
|
24
|
+
}
|
package/src/engine/index.ts
CHANGED
|
@@ -20,12 +20,12 @@ function resolvePythonPath(_cwd: string | undefined): string {
|
|
|
20
20
|
return process.env.PYTHON ?? "python3";
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
const DEFAULT_MAX_OUTPUT_CHARS =
|
|
23
|
+
const DEFAULT_MAX_OUTPUT_CHARS = 46080;
|
|
24
24
|
/** Per-line cap: one genuinely oversized line must not own the channel budget, while legitimately long
|
|
25
25
|
* REPL output (JSON, reprs, errors) still fits under the cap in one piece. Generous enough that only
|
|
26
26
|
* pathological giant lines are trimmed, unlike pi's grep where the line cap keeps matches terse. */
|
|
27
27
|
export const MAX_OUTPUT_LINE_CHARS = 4096;
|
|
28
|
-
const ABORT_GRACE_MS =
|
|
28
|
+
const ABORT_GRACE_MS = 20_000;
|
|
29
29
|
const DEFAULT_SNAPSHOT_DEBOUNCE_MS = 1500;
|
|
30
30
|
|
|
31
31
|
interface EngineExecuteError {
|
|
@@ -50,7 +50,7 @@ export interface ExecuteOptions {
|
|
|
50
50
|
/** Aborting cancels the cell via kernel interrupt; the namespace is preserved. */
|
|
51
51
|
signal?: AbortSignal;
|
|
52
52
|
onStream?: (chunk: string, name: "stdout" | "stderr") => void;
|
|
53
|
-
/** Cap stdout / stderr / result at this many characters. Default
|
|
53
|
+
/** Cap stdout / stderr / result at this many characters. Default 45K. */
|
|
54
54
|
maxOutputChars?: number;
|
|
55
55
|
}
|
|
56
56
|
|
package/src/engine/kernel.ts
CHANGED
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
import { type ChildProcess, spawn } from "node:child_process";
|
|
4
4
|
import { randomUUID } from "node:crypto";
|
|
5
5
|
import { existsSync, readdirSync, readFileSync, rmSync } from "node:fs";
|
|
6
|
-
import {
|
|
6
|
+
import { tmpdir } from "node:os";
|
|
7
7
|
import { join } from "node:path";
|
|
8
|
+
import { resolveHelperDirs } from "./helpers-locate.js";
|
|
8
9
|
import {
|
|
9
10
|
type ConnectionFile,
|
|
10
11
|
executeRequest,
|
|
@@ -62,18 +63,22 @@ function resolveCwd(requested?: string): string {
|
|
|
62
63
|
if (requested && existsSync(requested)) return requested;
|
|
63
64
|
return process.cwd();
|
|
64
65
|
}
|
|
65
|
-
function readHelperSources(
|
|
66
|
-
// ---
|
|
67
|
-
const
|
|
68
|
-
if (!existsSync(d)) return [];
|
|
66
|
+
function readHelperSources(dirs: string[]): { name: string; source: string }[] {
|
|
67
|
+
// --- merged dirs come pre-ordered (project first, global last); first-seen name wins ---
|
|
68
|
+
const seen = new Set<string>();
|
|
69
69
|
const out: { name: string; source: string }[] = [];
|
|
70
|
-
for (const
|
|
71
|
-
if (!
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
70
|
+
for (const d of dirs) {
|
|
71
|
+
if (!existsSync(d)) continue;
|
|
72
|
+
for (const file of readdirSync(d).sort()) {
|
|
73
|
+
if (!file.endsWith(".py")) continue;
|
|
74
|
+
const name = file.slice(0, -3);
|
|
75
|
+
if (!/^[A-Za-z_]\w*$/.test(name) || name.startsWith("_")) continue;
|
|
76
|
+
if (seen.has(name)) continue;
|
|
77
|
+
seen.add(name);
|
|
78
|
+
try {
|
|
79
|
+
out.push({ name, source: readFileSync(join(d, file), "utf8") });
|
|
80
|
+
} catch {}
|
|
81
|
+
}
|
|
77
82
|
}
|
|
78
83
|
return out;
|
|
79
84
|
}
|
|
@@ -184,7 +189,9 @@ export class KernelClient {
|
|
|
184
189
|
|
|
185
190
|
private constructor(conn: ConnectionFile, opts: KernelOptions) {
|
|
186
191
|
this.session = new JupyterSession({ key: conn.key });
|
|
187
|
-
this.helperSources =
|
|
192
|
+
this.helperSources = opts.env?.PI_HELPERS_DIR
|
|
193
|
+
? readHelperSources([opts.env.PI_HELPERS_DIR])
|
|
194
|
+
: readHelperSources(resolveHelperDirs(opts.cwd, opts.env?.PI_HELPERS_GLOBAL_DIR));
|
|
188
195
|
this.timeoutMs = opts.timeoutMs ?? 0;
|
|
189
196
|
}
|
|
190
197
|
|
package/src/extension/helpers.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
/** Loads helpers from
|
|
1
|
+
/** Loads helpers from project then global dirs; `helper_description` surfaces verbatim (no signature parsing). */
|
|
2
2
|
|
|
3
3
|
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { join } from "node:path";
|
|
6
|
+
import { resolveHelperDirs } from "../engine/helpers-locate.js";
|
|
6
7
|
|
|
7
8
|
const DEFAULT_HELPERS_DIR = join(homedir(), ".pi", "agent", "pi-repl", "helpers");
|
|
8
9
|
|
|
@@ -11,36 +12,49 @@ interface HelperEntry {
|
|
|
11
12
|
description: string; // full helper_description body, "" if absent
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
/** Extract `helper_description
|
|
15
|
+
/** Extract `helper_description` verbatim; no signature parsing. */
|
|
15
16
|
function parseDescription(source: string): string {
|
|
16
17
|
const m = source.match(/helper_description\s*=\s*("""|''')([\s\S]*?)\1/);
|
|
17
18
|
return m ? m[2].trim() : "";
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
/**
|
|
21
|
-
function loadHelperEntries(
|
|
22
|
-
const
|
|
23
|
-
if (!existsSync(d)) return [];
|
|
21
|
+
/** Merge entries from ordered dirs; first-seen name wins, so a project helper shadows the global one. */
|
|
22
|
+
function loadHelperEntries(dirs: string[]): HelperEntry[] {
|
|
23
|
+
const seen = new Set<string>();
|
|
24
24
|
const entries: HelperEntry[] = [];
|
|
25
|
-
for (const
|
|
26
|
-
if (!
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
25
|
+
for (const d of dirs) {
|
|
26
|
+
if (!existsSync(d)) continue;
|
|
27
|
+
for (const file of readdirSync(d).sort()) {
|
|
28
|
+
if (!file.endsWith(".py")) continue;
|
|
29
|
+
const name = file.slice(0, -3);
|
|
30
|
+
if (!/^[A-Za-z_]\w*$/.test(name)) continue;
|
|
31
|
+
// --- underscore-prefixed files are neither loaded nor advertised ---
|
|
32
|
+
if (name.startsWith("_")) continue;
|
|
33
|
+
if (seen.has(name)) continue;
|
|
34
|
+
seen.add(name);
|
|
35
|
+
try {
|
|
36
|
+
const source = readFileSync(join(d, file), "utf8");
|
|
37
|
+
entries.push({ name, description: parseDescription(source) });
|
|
38
|
+
} catch {}
|
|
39
|
+
}
|
|
35
40
|
}
|
|
36
41
|
return entries;
|
|
37
42
|
}
|
|
38
43
|
|
|
39
|
-
/** The prompt-facing list
|
|
44
|
+
/** The prompt-facing list for ONE dir: verbatim description, or an introspection pointer. */
|
|
40
45
|
export function buildHelpersMap(dir?: string): string[] {
|
|
41
|
-
return loadHelperEntries(dir).map((t) =>
|
|
46
|
+
return loadHelperEntries([dir ?? DEFAULT_HELPERS_DIR]).map((t) =>
|
|
42
47
|
t.description
|
|
43
|
-
?
|
|
44
|
-
:
|
|
48
|
+
? t.description.replace(/\n/g, "\n ")
|
|
49
|
+
: `${t.name} (no description, inspect it with print(${t.name}.__doc__))`,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The prompt-facing list at a cwd: project .pi/helpers first (up to the git root), global fallback, project shadows. */
|
|
54
|
+
export function buildHelpersMapForCwd(cwd: string, globalDir?: string): string[] {
|
|
55
|
+
return loadHelperEntries(resolveHelperDirs(cwd, globalDir)).map((t) =>
|
|
56
|
+
t.description
|
|
57
|
+
? t.description.replace(/\n/g, "\n ")
|
|
58
|
+
: `${t.name} (no description, inspect it with print(${t.name}.__doc__))`,
|
|
45
59
|
);
|
|
46
60
|
}
|
package/src/extension/prompt.ts
CHANGED
|
@@ -1,52 +1,23 @@
|
|
|
1
|
-
// --- execute tool: the model-facing contract
|
|
1
|
+
// --- execute tool: the model-facing contract, shaped exactly like pi's built-in tools ---
|
|
2
|
+
// description = rich short behavior; promptSnippet = one-liner; guidelines = flat bullets.
|
|
2
3
|
|
|
3
4
|
export const executeToolDescription =
|
|
4
|
-
"Execute Python cells in a persistent
|
|
5
|
-
"
|
|
6
|
-
"
|
|
5
|
+
"Execute Python cells in a persistent Python shell that is your entire workspace: it is where you read, " +
|
|
6
|
+
"write, run, and move, all in one instrument. The state you build, files, and subprocesses survive " +
|
|
7
|
+
"from one call to the next. Returns stdout, stderr, and the value of the last expression. Output is " +
|
|
8
|
+
"truncated to 45K with a marker.";
|
|
7
9
|
|
|
8
|
-
export const executePromptSnippet =
|
|
9
|
-
"Execute Python cells in a persistent ipython kernel (replaces read, bash, edit, write, and search; state survives across cells and turns)";
|
|
10
|
+
export const executePromptSnippet = "Execute Python in a persistent shell (read, write, run, search, and more)";
|
|
10
11
|
|
|
11
|
-
// --- the
|
|
12
|
+
// --- the model-facing guidelines, flat bullets like pi's own tool contributions ---
|
|
12
13
|
export function buildPromptGuidelines(preloaded: string[]): string[] {
|
|
13
14
|
return [
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"",
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"",
|
|
20
|
-
"
|
|
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'.",
|
|
22
|
-
"",
|
|
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.",
|
|
25
|
-
"",
|
|
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.",
|
|
27
|
-
"",
|
|
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.",
|
|
30
|
-
"",
|
|
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.",
|
|
33
|
-
"",
|
|
34
|
-
"## Print is expensive",
|
|
35
|
-
"Every token you print is spent from the context you need for the turns to come, and it never comes back. Treat printing as debt, not reward. Print only the exact fragment the next decision consumes and hold the whole in a variable. Every other print is waste, it buys nothing and closes the room you have left to think. Ask before you print: does this decide the next step, or is it just noise? When it is noise, cut it. When in doubt, cut it. A tight transcript is the sign you actually worked; a bloated one is the sign you did not.",
|
|
36
|
-
"",
|
|
37
|
-
...(preloaded.length
|
|
38
|
-
? [
|
|
39
|
-
"## Helpers",
|
|
40
|
-
"These helpers are already defined in the workspace namespace. Use them by name as you would any other loaded function, class, or variable. Their code already executed at kernel boot. Descriptions appear below.",
|
|
41
|
-
"",
|
|
42
|
-
...preloaded,
|
|
43
|
-
"",
|
|
44
|
-
]
|
|
45
|
-
: []),
|
|
46
|
-
"## Environment & rescue",
|
|
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.",
|
|
48
|
-
"",
|
|
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.",
|
|
15
|
+
"State persists across cells, so keep building on it.",
|
|
16
|
+
"Find, filter, fetch, read: narrow the output in Python, then print the exact slice you need.",
|
|
17
|
+
"Keep the result in a variable and reuse it, instead of re-fetching the same thing.",
|
|
18
|
+
"Make surgical, precise changes over rewrites or whole-file dumps: a small unique anchor, replace, verify, read the file back before trusting it.",
|
|
19
|
+
"The evaluator runs in a project-local venv. Do not install a project's dependencies into it; run external projects through their own interface. If output begins with <repl_engine_reset>, the kernel rebuilt; re-verify a revived variable.",
|
|
20
|
+
...(preloaded.length ? ["Preloaded helpers, use them as any loaded function or variable:", ...preloaded] : []),
|
|
21
|
+
"Be concise.",
|
|
51
22
|
];
|
|
52
23
|
}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
// --- tool-meta: thin surface assembling the execute tool's prompt from pure modules ---
|
|
2
2
|
// --- the model contract lives in prompt.ts; only the helpers wiring stays here ---
|
|
3
3
|
|
|
4
|
-
import { buildHelpersMap } from "./helpers.js";
|
|
4
|
+
import { buildHelpersMap, buildHelpersMapForCwd } from "./helpers.js";
|
|
5
5
|
import { buildPromptGuidelines, executePromptSnippet, executeToolDescription } from "./prompt.js";
|
|
6
6
|
|
|
7
7
|
export const EXECUTE_DESCRIPTION = executeToolDescription;
|
|
8
8
|
export const EXECUTE_PROMPT_SNIPPET = executePromptSnippet;
|
|
9
9
|
|
|
10
|
-
// --- build the guidelines from
|
|
11
|
-
export function buildExecutePromptGuidelines(): string[] {
|
|
12
|
-
const map = buildHelpersMap();
|
|
10
|
+
// --- build the guidelines from project + global helper dirs ---
|
|
11
|
+
export function buildExecutePromptGuidelines(cwd?: string): string[] {
|
|
12
|
+
const map = cwd ? buildHelpersMapForCwd(cwd) : buildHelpersMap();
|
|
13
13
|
const preloaded = map.length > 0 ? map : [];
|
|
14
14
|
return buildPromptGuidelines(preloaded);
|
|
15
15
|
}
|