@themoltnet/pi-extension 0.28.0 → 0.28.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/README.md +5 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +313 -20
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,7 +6,8 @@ agent's MoltNet identity fully available inside the sandbox.
|
|
|
6
6
|
## How it works
|
|
7
7
|
|
|
8
8
|
Every pi session boots a lightweight Alpine Linux VM from a cached snapshot.
|
|
9
|
-
All file system and shell tools (read/write/edit/bash)
|
|
9
|
+
All file system, search, and shell tools (read/write/edit/bash/ls/find/grep)
|
|
10
|
+
execute inside the VM.
|
|
10
11
|
MoltNet API tools (diary entries, pack ops, reflection) run on the host via
|
|
11
12
|
the SDK and communicate outbound over HTTP.
|
|
12
13
|
|
|
@@ -24,7 +25,8 @@ pi + extension $MOLTNET_GUEST_WORKSPACE
|
|
|
24
25
|
│ (via gitconfig ssh/allowed_signers
|
|
25
26
|
│ in VM) /home/agent/.pi/agent/auth.json (pi OAuth)
|
|
26
27
|
│
|
|
27
|
-
└─ read/write/edit/bash
|
|
28
|
+
└─ read/write/edit/bash/ls/find/grep
|
|
29
|
+
─▶ vm.exec() / vm.fs.*
|
|
28
30
|
(redirected to VM)
|
|
29
31
|
```
|
|
30
32
|
|
|
@@ -104,6 +106,7 @@ tools can use structured in-process calls rather than shell round-trips.
|
|
|
104
106
|
| Tool | Runs in | Mechanism |
|
|
105
107
|
| ----------------------------------- | ------- | -------------------------------------------------------------------------------- |
|
|
106
108
|
| `read`, `write`, `edit` | VM | Gondolin VFS — agent's FS view is `$MOLTNET_GUEST_WORKSPACE` |
|
|
109
|
+
| `ls`, `find`, `grep` | VM | Gondolin VFS — search/listing observes the sandboxed workspace |
|
|
107
110
|
| `bash` | VM | `vm.exec()` — shell runs in the isolated guest |
|
|
108
111
|
| `user_bash` (human `/bash` command) | VM | Same as agent bash |
|
|
109
112
|
| `moltnet_pack_get` | Host | TypeScript SDK (`@themoltnet/sdk`) authenticated via injected `moltnet.json` |
|
package/dist/index.d.ts
CHANGED
|
@@ -181,7 +181,7 @@ export declare interface CreateSubagentToolArgs {
|
|
|
181
181
|
agentName: string;
|
|
182
182
|
/**
|
|
183
183
|
* Custom tools every subagent inherits (Gondolin-routed
|
|
184
|
-
*
|
|
184
|
+
* built-ins + moltnet_* tools, etc). MUST NOT include
|
|
185
185
|
* the parent's submit-output tool, the parent's `subagent` tool,
|
|
186
186
|
* or any other parent-only artefact — the caller is responsible
|
|
187
187
|
* for filtering. The subagent appends its own submit tool.
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import { createRequire } from "node:module";
|
|
|
2
2
|
import { execFileSync } from "node:child_process";
|
|
3
3
|
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, statSync } from "node:fs";
|
|
4
4
|
import path, { join, relative, sep } from "node:path";
|
|
5
|
-
import { DefaultResourceLoader, SessionManager, createAgentSession, createBashTool, createBashToolDefinition, createEditTool, createEditToolDefinition, createReadTool, createReadToolDefinition, createSyntheticSourceInfo, createWriteTool, createWriteToolDefinition, defineTool, parseFrontmatter } from "@earendil-works/pi-coding-agent";
|
|
5
|
+
import { DEFAULT_MAX_BYTES, DefaultResourceLoader, SessionManager, createAgentSession, createBashTool, createBashToolDefinition, createEditTool, createEditToolDefinition, createFindTool, createFindToolDefinition, createGrepTool, createGrepToolDefinition, createLsTool, createLsToolDefinition, createReadTool, createReadToolDefinition, createSyntheticSourceInfo, createWriteTool, createWriteToolDefinition, defineTool, formatSize, parseFrontmatter, truncateHead, truncateLine } from "@earendil-works/pi-coding-agent";
|
|
6
6
|
import { createHash } from "node:crypto";
|
|
7
7
|
import { Readable } from "node:stream";
|
|
8
8
|
import crypto, { createHash as createHash$1 } from "crypto";
|
|
@@ -18559,11 +18559,13 @@ function rewriteMoltnetJsonPaths(moltnetJson, vmAgentDir, vmSshDir, githubAppPem
|
|
|
18559
18559
|
//#region src/tool-operations.ts
|
|
18560
18560
|
/**
|
|
18561
18561
|
* Gondolin tool operations: redirect pi's built-in tool operations
|
|
18562
|
-
* (read, write, edit, bash) to execute inside the VM.
|
|
18562
|
+
* (read, write, edit, bash, ls, find, grep) to execute inside the VM.
|
|
18563
18563
|
*
|
|
18564
18564
|
* Follows the same pattern as upstream pi-gondolin.ts — pi's tool factories
|
|
18565
18565
|
* accept an `operations` object that provides the underlying I/O.
|
|
18566
18566
|
*/
|
|
18567
|
+
var DEFAULT_GREP_LIMIT = 100;
|
|
18568
|
+
var GREP_MAX_FILE_SIZE = "2M";
|
|
18567
18569
|
function shQuote(s) {
|
|
18568
18570
|
return "'" + s.replace(/'/g, "'\\''") + "'";
|
|
18569
18571
|
}
|
|
@@ -18573,6 +18575,18 @@ function normalizeGuestPath(p) {
|
|
|
18573
18575
|
function isSameOrInsidePosixPath(candidate, root) {
|
|
18574
18576
|
return candidate === root || candidate.startsWith(`${root}/`);
|
|
18575
18577
|
}
|
|
18578
|
+
function resolveLocalPath(localCwd, inputPath) {
|
|
18579
|
+
return path.isAbsolute(inputPath) ? inputPath : path.resolve(localCwd, inputPath);
|
|
18580
|
+
}
|
|
18581
|
+
function toHostToolPath(localCwd, guestWorkspace, guestPath) {
|
|
18582
|
+
const normalizedGuestWorkspace = normalizeGuestPath(guestWorkspace);
|
|
18583
|
+
const normalizedGuestPath = normalizeGuestPath(guestPath);
|
|
18584
|
+
if (isSameOrInsidePosixPath(normalizedGuestPath, normalizedGuestWorkspace)) {
|
|
18585
|
+
const rel = path.posix.relative(normalizedGuestWorkspace, normalizedGuestPath);
|
|
18586
|
+
return rel ? path.join(localCwd, ...rel.split("/")) : localCwd;
|
|
18587
|
+
}
|
|
18588
|
+
return normalizedGuestPath;
|
|
18589
|
+
}
|
|
18576
18590
|
/**
|
|
18577
18591
|
* Map a host-side absolute path to a guest-side workspace path.
|
|
18578
18592
|
* Throws if the path escapes the workspace.
|
|
@@ -18592,17 +18606,10 @@ function toGuestPath(localCwd, localPath, guestWorkspace) {
|
|
|
18592
18606
|
function createGondolinReadOps(vm, localCwd, guestWorkspace) {
|
|
18593
18607
|
return {
|
|
18594
18608
|
readFile: async (p) => {
|
|
18595
|
-
const
|
|
18596
|
-
|
|
18597
|
-
return r.stdoutBuffer;
|
|
18598
|
-
},
|
|
18599
|
-
access: async (p) => {
|
|
18600
|
-
if (!(await vm.exec([
|
|
18601
|
-
"/bin/sh",
|
|
18602
|
-
"-lc",
|
|
18603
|
-
`test -r ${shQuote(toGuestPath(localCwd, p, guestWorkspace))}`
|
|
18604
|
-
])).ok) throw new Error(`not readable: ${p}`);
|
|
18609
|
+
const content = await vm.fs.readFile(toGuestPath(localCwd, p, guestWorkspace));
|
|
18610
|
+
return typeof content === "string" ? Buffer.from(content, "utf8") : Buffer.from(content);
|
|
18605
18611
|
},
|
|
18612
|
+
access: async (p) => vm.fs.access(toGuestPath(localCwd, p, guestWorkspace)),
|
|
18606
18613
|
detectImageMimeType: async (p) => {
|
|
18607
18614
|
try {
|
|
18608
18615
|
const r = await vm.exec([
|
|
@@ -18660,6 +18667,253 @@ function createGondolinEditOps(vm, localCwd, guestWorkspace) {
|
|
|
18660
18667
|
writeFile: w.writeFile
|
|
18661
18668
|
};
|
|
18662
18669
|
}
|
|
18670
|
+
function createGondolinLsOps(vm, localCwd, guestWorkspace) {
|
|
18671
|
+
return {
|
|
18672
|
+
exists: async (p) => {
|
|
18673
|
+
try {
|
|
18674
|
+
await vm.fs.access(toGuestPath(localCwd, p, guestWorkspace));
|
|
18675
|
+
return true;
|
|
18676
|
+
} catch {
|
|
18677
|
+
return false;
|
|
18678
|
+
}
|
|
18679
|
+
},
|
|
18680
|
+
stat: async (p) => vm.fs.stat(toGuestPath(localCwd, p, guestWorkspace)),
|
|
18681
|
+
readdir: async (p) => vm.fs.listDir(toGuestPath(localCwd, p, guestWorkspace))
|
|
18682
|
+
};
|
|
18683
|
+
}
|
|
18684
|
+
async function walkGuestFiles(vm, root, visit, signal) {
|
|
18685
|
+
if (signal?.aborted) throw new Error("Operation aborted");
|
|
18686
|
+
if (!(await vm.fs.stat(root, { signal })).isDirectory()) return visit(root, path.posix.basename(root));
|
|
18687
|
+
const walkDirectory = async (dir, relativeDir) => {
|
|
18688
|
+
if (signal?.aborted) throw new Error("Operation aborted");
|
|
18689
|
+
const entries = await vm.fs.listDir(dir, { signal });
|
|
18690
|
+
for (const entry of entries) {
|
|
18691
|
+
if (entry === ".git" || entry === "node_modules") continue;
|
|
18692
|
+
const guestPath = path.posix.join(dir, entry);
|
|
18693
|
+
const relativePath = relativeDir ? path.posix.join(relativeDir, entry) : entry;
|
|
18694
|
+
let entryStat;
|
|
18695
|
+
try {
|
|
18696
|
+
entryStat = await vm.fs.stat(guestPath, { signal });
|
|
18697
|
+
} catch {
|
|
18698
|
+
continue;
|
|
18699
|
+
}
|
|
18700
|
+
if (entryStat.isDirectory()) {
|
|
18701
|
+
if (!await walkDirectory(guestPath, relativePath)) return false;
|
|
18702
|
+
} else if (!await visit(guestPath, relativePath)) return false;
|
|
18703
|
+
}
|
|
18704
|
+
return true;
|
|
18705
|
+
};
|
|
18706
|
+
return walkDirectory(root, "");
|
|
18707
|
+
}
|
|
18708
|
+
function matchesGlob(relativePath, pattern) {
|
|
18709
|
+
return path.posix.matchesGlob(relativePath, pattern);
|
|
18710
|
+
}
|
|
18711
|
+
function matchesToolGlob(relativePath, pattern) {
|
|
18712
|
+
const normalizedPattern = normalizeGuestPath(pattern);
|
|
18713
|
+
if (normalizedPattern.includes("/")) return matchesGlob(relativePath, normalizedPattern) || matchesGlob(relativePath, `**/${normalizedPattern}`);
|
|
18714
|
+
return matchesGlob(path.posix.basename(relativePath), normalizedPattern);
|
|
18715
|
+
}
|
|
18716
|
+
function createGondolinFindOps(vm, localCwd, guestWorkspace) {
|
|
18717
|
+
return {
|
|
18718
|
+
exists: async (p) => {
|
|
18719
|
+
try {
|
|
18720
|
+
await vm.fs.access(toGuestPath(localCwd, p, guestWorkspace));
|
|
18721
|
+
return true;
|
|
18722
|
+
} catch {
|
|
18723
|
+
return false;
|
|
18724
|
+
}
|
|
18725
|
+
},
|
|
18726
|
+
glob: async (pattern, cwd, options) => {
|
|
18727
|
+
const root = toGuestPath(localCwd, cwd, guestWorkspace);
|
|
18728
|
+
const results = [];
|
|
18729
|
+
await walkGuestFiles(vm, root, (guestPath, relativePath) => {
|
|
18730
|
+
if (results.length >= options.limit) return Promise.resolve(false);
|
|
18731
|
+
if (options.ignore.some((ignore) => matchesToolGlob(relativePath, ignore))) return Promise.resolve(true);
|
|
18732
|
+
if (matchesToolGlob(relativePath, pattern)) results.push(toHostToolPath(localCwd, guestWorkspace, guestPath));
|
|
18733
|
+
return Promise.resolve(results.length < options.limit);
|
|
18734
|
+
});
|
|
18735
|
+
return results;
|
|
18736
|
+
}
|
|
18737
|
+
};
|
|
18738
|
+
}
|
|
18739
|
+
function appendGrepBlock(params) {
|
|
18740
|
+
let linesTruncated = false;
|
|
18741
|
+
const start = params.contextLines > 0 ? Math.max(0, params.lineIndex - params.contextLines) : params.lineIndex;
|
|
18742
|
+
const end = params.contextLines > 0 ? Math.min(params.lines.length - 1, params.lineIndex + params.contextLines) : params.lineIndex;
|
|
18743
|
+
for (let index = start; index <= end; index++) {
|
|
18744
|
+
const { text, wasTruncated } = truncateLine((params.lines[index] ?? "").replace(/\r/g, ""));
|
|
18745
|
+
if (wasTruncated) linesTruncated = true;
|
|
18746
|
+
const separator = index === params.lineIndex ? ":" : "-";
|
|
18747
|
+
params.outputLines.push(`${params.relativePath}${separator}${index + 1}${separator} ${text}`);
|
|
18748
|
+
}
|
|
18749
|
+
return linesTruncated;
|
|
18750
|
+
}
|
|
18751
|
+
function parseRgJsonLines(params) {
|
|
18752
|
+
const parts = `${params.carry}${params.chunk}`.split("\n");
|
|
18753
|
+
const carry = parts.pop() ?? "";
|
|
18754
|
+
let limitReached = false;
|
|
18755
|
+
for (const line of parts) {
|
|
18756
|
+
if (!line.trim() || params.matches.length >= params.effectiveLimit) continue;
|
|
18757
|
+
let event;
|
|
18758
|
+
try {
|
|
18759
|
+
event = JSON.parse(line);
|
|
18760
|
+
} catch {
|
|
18761
|
+
continue;
|
|
18762
|
+
}
|
|
18763
|
+
const candidate = event;
|
|
18764
|
+
if (candidate.type !== "match") continue;
|
|
18765
|
+
const guestPath = candidate.data?.path?.text;
|
|
18766
|
+
const lineNumber = candidate.data?.line_number;
|
|
18767
|
+
if (!guestPath || typeof lineNumber !== "number") continue;
|
|
18768
|
+
params.matches.push({
|
|
18769
|
+
guestPath,
|
|
18770
|
+
lineNumber,
|
|
18771
|
+
lineText: candidate.data?.lines?.text
|
|
18772
|
+
});
|
|
18773
|
+
if (params.matches.length >= params.effectiveLimit) {
|
|
18774
|
+
limitReached = true;
|
|
18775
|
+
break;
|
|
18776
|
+
}
|
|
18777
|
+
}
|
|
18778
|
+
return {
|
|
18779
|
+
carry,
|
|
18780
|
+
limitReached
|
|
18781
|
+
};
|
|
18782
|
+
}
|
|
18783
|
+
async function readGuestLines(vm, guestPath, signal) {
|
|
18784
|
+
try {
|
|
18785
|
+
return (await vm.fs.readFile(guestPath, {
|
|
18786
|
+
encoding: "utf8",
|
|
18787
|
+
signal
|
|
18788
|
+
})).replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
|
|
18789
|
+
} catch {
|
|
18790
|
+
return [];
|
|
18791
|
+
}
|
|
18792
|
+
}
|
|
18793
|
+
async function executeGondolinGrep(vm, localCwd, guestWorkspace, params, signal) {
|
|
18794
|
+
const root = toGuestPath(localCwd, resolveLocalPath(localCwd, params.path ?? "."), guestWorkspace);
|
|
18795
|
+
let rootStat;
|
|
18796
|
+
try {
|
|
18797
|
+
rootStat = await vm.fs.stat(root, { signal });
|
|
18798
|
+
} catch {
|
|
18799
|
+
throw new Error(`Path not found: ${resolveLocalPath(localCwd, params.path ?? ".")}`);
|
|
18800
|
+
}
|
|
18801
|
+
const rootIsDirectory = rootStat.isDirectory();
|
|
18802
|
+
const contextLines = params.context && params.context > 0 ? params.context : 0;
|
|
18803
|
+
const effectiveLimit = Math.max(1, params.limit ?? DEFAULT_GREP_LIMIT);
|
|
18804
|
+
const args = [
|
|
18805
|
+
"--json",
|
|
18806
|
+
"--line-number",
|
|
18807
|
+
"--color=never",
|
|
18808
|
+
"--hidden",
|
|
18809
|
+
"--max-filesize",
|
|
18810
|
+
GREP_MAX_FILE_SIZE
|
|
18811
|
+
];
|
|
18812
|
+
if (params.ignoreCase) args.push("--ignore-case");
|
|
18813
|
+
if (params.literal) args.push("--fixed-strings");
|
|
18814
|
+
if (params.glob) args.push("--glob", params.glob);
|
|
18815
|
+
args.push("--", params.pattern, root);
|
|
18816
|
+
const outputLines = [];
|
|
18817
|
+
const details = {};
|
|
18818
|
+
const matches = [];
|
|
18819
|
+
let matchLimitReached = false;
|
|
18820
|
+
let linesTruncated = false;
|
|
18821
|
+
let stderr = "";
|
|
18822
|
+
let carry = "";
|
|
18823
|
+
const ac = new AbortController();
|
|
18824
|
+
const onAbort = () => ac.abort();
|
|
18825
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
18826
|
+
try {
|
|
18827
|
+
const proc = vm.exec(["/bin/rg", ...args], {
|
|
18828
|
+
signal: ac.signal,
|
|
18829
|
+
stdout: "pipe",
|
|
18830
|
+
stderr: "pipe"
|
|
18831
|
+
});
|
|
18832
|
+
for await (const chunk of proc.output()) {
|
|
18833
|
+
const text = typeof chunk.data === "string" ? chunk.data : Buffer.from(chunk.data).toString("utf8");
|
|
18834
|
+
if (chunk.stream === "stderr") {
|
|
18835
|
+
stderr += text;
|
|
18836
|
+
continue;
|
|
18837
|
+
}
|
|
18838
|
+
const parsed = parseRgJsonLines({
|
|
18839
|
+
chunk: text,
|
|
18840
|
+
carry,
|
|
18841
|
+
matches,
|
|
18842
|
+
effectiveLimit
|
|
18843
|
+
});
|
|
18844
|
+
carry = parsed.carry;
|
|
18845
|
+
if (parsed.limitReached) {
|
|
18846
|
+
matchLimitReached = true;
|
|
18847
|
+
ac.abort();
|
|
18848
|
+
break;
|
|
18849
|
+
}
|
|
18850
|
+
}
|
|
18851
|
+
const r = await proc;
|
|
18852
|
+
if (!signal?.aborted && !matchLimitReached && r.exitCode !== 0 && r.exitCode !== 1) throw new Error(stderr.trim() || `ripgrep exited with code ${r.exitCode}`);
|
|
18853
|
+
} catch (err) {
|
|
18854
|
+
if (signal?.aborted) throw new Error("Operation aborted");
|
|
18855
|
+
if (matchLimitReached) {} else throw err;
|
|
18856
|
+
} finally {
|
|
18857
|
+
signal?.removeEventListener("abort", onAbort);
|
|
18858
|
+
}
|
|
18859
|
+
if (matches.length === 0) return {
|
|
18860
|
+
content: [{
|
|
18861
|
+
type: "text",
|
|
18862
|
+
text: "No matches found"
|
|
18863
|
+
}],
|
|
18864
|
+
details: void 0
|
|
18865
|
+
};
|
|
18866
|
+
const fileCache = /* @__PURE__ */ new Map();
|
|
18867
|
+
for (const match of matches) {
|
|
18868
|
+
if (signal?.aborted) throw new Error("Operation aborted");
|
|
18869
|
+
const displayPath = rootIsDirectory ? path.posix.relative(root, match.guestPath) : path.posix.basename(match.guestPath);
|
|
18870
|
+
if (contextLines === 0 && match.lineText !== void 0) {
|
|
18871
|
+
const { text, wasTruncated } = truncateLine(match.lineText.replace(/\r\n/g, "\n").replace(/\r/g, "").replace(/\n$/, ""));
|
|
18872
|
+
if (wasTruncated) linesTruncated = true;
|
|
18873
|
+
outputLines.push(`${displayPath}:${match.lineNumber}: ${text}`);
|
|
18874
|
+
continue;
|
|
18875
|
+
}
|
|
18876
|
+
let lines = fileCache.get(match.guestPath);
|
|
18877
|
+
if (!lines) {
|
|
18878
|
+
lines = await readGuestLines(vm, match.guestPath, signal);
|
|
18879
|
+
fileCache.set(match.guestPath, lines);
|
|
18880
|
+
}
|
|
18881
|
+
if (lines.length === 0) {
|
|
18882
|
+
outputLines.push(`${displayPath}:${match.lineNumber}: (unable to read file)`);
|
|
18883
|
+
continue;
|
|
18884
|
+
}
|
|
18885
|
+
if (appendGrepBlock({
|
|
18886
|
+
outputLines,
|
|
18887
|
+
lines,
|
|
18888
|
+
relativePath: displayPath,
|
|
18889
|
+
lineIndex: match.lineNumber - 1,
|
|
18890
|
+
contextLines
|
|
18891
|
+
})) linesTruncated = true;
|
|
18892
|
+
}
|
|
18893
|
+
const truncation = truncateHead(outputLines.join("\n"), { maxLines: Number.MAX_SAFE_INTEGER });
|
|
18894
|
+
const notices = [];
|
|
18895
|
+
let output = truncation.content;
|
|
18896
|
+
if (matchLimitReached) {
|
|
18897
|
+
details.matchLimitReached = effectiveLimit;
|
|
18898
|
+
notices.push(`${effectiveLimit} matches limit reached`);
|
|
18899
|
+
}
|
|
18900
|
+
if (linesTruncated) {
|
|
18901
|
+
details.linesTruncated = true;
|
|
18902
|
+
notices.push("long lines truncated");
|
|
18903
|
+
}
|
|
18904
|
+
if (truncation.truncated) {
|
|
18905
|
+
details.truncation = truncation;
|
|
18906
|
+
notices.push(`${formatSize(DEFAULT_MAX_BYTES)} limit reached`);
|
|
18907
|
+
}
|
|
18908
|
+
if (notices.length > 0) output += `\n\n[${notices.join(". ")}]`;
|
|
18909
|
+
return {
|
|
18910
|
+
content: [{
|
|
18911
|
+
type: "text",
|
|
18912
|
+
text: output
|
|
18913
|
+
}],
|
|
18914
|
+
details: Object.keys(details).length > 0 ? details : void 0
|
|
18915
|
+
};
|
|
18916
|
+
}
|
|
18663
18917
|
function createGondolinBashOps(vm, localCwd, guestWorkspace) {
|
|
18664
18918
|
return { exec: async (command, cwd, { onData, signal, timeout, env }) => {
|
|
18665
18919
|
const guestCwd = toGuestPath(localCwd, cwd, guestWorkspace);
|
|
@@ -24292,7 +24546,7 @@ function subagentToolDescription() {
|
|
|
24292
24546
|
"",
|
|
24293
24547
|
"The subagent starts with no conversation history and only the `task` ",
|
|
24294
24548
|
"string you provide as its instructions. It runs in the same VM with ",
|
|
24295
|
-
"the same tools you have (Gondolin-routed
|
|
24549
|
+
"the same tools you have (Gondolin-routed built-ins, ",
|
|
24296
24550
|
"moltnet_* tools), and is expected to call ",
|
|
24297
24551
|
`\`${SUBAGENT_SUBMIT_TOOL_NAME}\` with a payload matching the named `,
|
|
24298
24552
|
"contract before its session ends.",
|
|
@@ -24784,7 +25038,7 @@ function shouldSkipSeedEntry(sourceEntry, entryName, resolvedTargetDir) {
|
|
|
24784
25038
|
*
|
|
24785
25039
|
* This is the pi-specific task executor. It owns:
|
|
24786
25040
|
* - VM lifecycle (ensureSnapshot + resumeVm + close)
|
|
24787
|
-
* - Gondolin-redirected tool wiring (read/write/edit/bash → VM)
|
|
25041
|
+
* - Gondolin-redirected tool wiring (read/write/edit/bash/ls/find/grep → VM)
|
|
24788
25042
|
* - MoltNet custom tools (diary entries, pack render/judge, etc.)
|
|
24789
25043
|
* - pi createAgentSession + event → TaskReporter bridge
|
|
24790
25044
|
*
|
|
@@ -24795,6 +25049,25 @@ function shouldSkipSeedEntry(sourceEntry, entryName, resolvedTargetDir) {
|
|
|
24795
25049
|
* `AgentRuntime`.
|
|
24796
25050
|
*/
|
|
24797
25051
|
var noopTurnEventHandler = () => {};
|
|
25052
|
+
function createGondolinToolDefinitions(config) {
|
|
25053
|
+
const { vm, mountPath, guestWorkspace } = config;
|
|
25054
|
+
const grepTool = createGrepToolDefinition(mountPath);
|
|
25055
|
+
return [
|
|
25056
|
+
createReadToolDefinition(mountPath, { operations: createGondolinReadOps(vm, mountPath, guestWorkspace) }),
|
|
25057
|
+
createWriteToolDefinition(mountPath, { operations: createGondolinWriteOps(vm, mountPath, guestWorkspace) }),
|
|
25058
|
+
createEditToolDefinition(mountPath, { operations: createGondolinEditOps(vm, mountPath, guestWorkspace) }),
|
|
25059
|
+
createBashToolDefinition(mountPath, { operations: createGondolinBashOps(vm, mountPath, guestWorkspace) }),
|
|
25060
|
+
createLsToolDefinition(mountPath, { operations: createGondolinLsOps(vm, mountPath, guestWorkspace) }),
|
|
25061
|
+
createFindToolDefinition(mountPath, { operations: createGondolinFindOps(vm, mountPath, guestWorkspace) }),
|
|
25062
|
+
{
|
|
25063
|
+
...grepTool,
|
|
25064
|
+
async execute(...args) {
|
|
25065
|
+
const [_id, params, signal] = args;
|
|
25066
|
+
return executeGondolinGrep(vm, mountPath, guestWorkspace, params, signal);
|
|
25067
|
+
}
|
|
25068
|
+
}
|
|
25069
|
+
];
|
|
25070
|
+
}
|
|
24798
25071
|
/**
|
|
24799
25072
|
* Factory that builds a pi-specific `executeTask` function suitable for
|
|
24800
25073
|
* injection into `AgentRuntime`. The returned function caches the resolved
|
|
@@ -25058,12 +25331,11 @@ async function executePiTask(claimedTask, reporter, opts) {
|
|
|
25058
25331
|
slugs: injectedContext.injected.map((r) => r.slug)
|
|
25059
25332
|
});
|
|
25060
25333
|
if (injectedContext.userInlineSuffix) taskPrompt = `${taskPrompt}\n\n---\n\n${injectedContext.userInlineSuffix}`;
|
|
25061
|
-
const gondolinCustomTools =
|
|
25062
|
-
|
|
25063
|
-
|
|
25064
|
-
|
|
25065
|
-
|
|
25066
|
-
];
|
|
25334
|
+
const gondolinCustomTools = createGondolinToolDefinitions({
|
|
25335
|
+
vm: managed.vm,
|
|
25336
|
+
mountPath,
|
|
25337
|
+
guestWorkspace: managed.guestWorkspace
|
|
25338
|
+
});
|
|
25067
25339
|
const { handle: submitToolHandle, tools: submitToolDefs } = resolveSubmitTools(task.taskType, {
|
|
25068
25340
|
model: opts.model,
|
|
25069
25341
|
input: task.input
|
|
@@ -25591,6 +25863,9 @@ function moltnetExtension(pi) {
|
|
|
25591
25863
|
const localWrite = createWriteTool(localCwd);
|
|
25592
25864
|
const localEdit = createEditTool(localCwd);
|
|
25593
25865
|
const localBash = createBashTool(localCwd);
|
|
25866
|
+
const localLs = createLsTool(localCwd);
|
|
25867
|
+
const localFind = createFindTool(localCwd);
|
|
25868
|
+
const localGrep = createGrepTool(localCwd);
|
|
25594
25869
|
const initialGuestWorkspace = path.resolve(localCwd);
|
|
25595
25870
|
let vm = null;
|
|
25596
25871
|
let guestWorkspace = initialGuestWorkspace;
|
|
@@ -25701,6 +25976,24 @@ function moltnetExtension(pi) {
|
|
|
25701
25976
|
return createBashTool(localCwd, { operations: createGondolinBashOps(await ensureVm(ctx), localCwd, guestWorkspace) }).execute(id, params, signal, onUpdate);
|
|
25702
25977
|
}
|
|
25703
25978
|
});
|
|
25979
|
+
pi.registerTool({
|
|
25980
|
+
...localLs,
|
|
25981
|
+
async execute(id, params, signal, onUpdate, ctx) {
|
|
25982
|
+
return createLsTool(localCwd, { operations: createGondolinLsOps(await ensureVm(ctx), localCwd, guestWorkspace) }).execute(id, params, signal, onUpdate);
|
|
25983
|
+
}
|
|
25984
|
+
});
|
|
25985
|
+
pi.registerTool({
|
|
25986
|
+
...localFind,
|
|
25987
|
+
async execute(id, params, signal, onUpdate, ctx) {
|
|
25988
|
+
return createFindTool(localCwd, { operations: createGondolinFindOps(await ensureVm(ctx), localCwd, guestWorkspace) }).execute(id, params, signal, onUpdate);
|
|
25989
|
+
}
|
|
25990
|
+
});
|
|
25991
|
+
pi.registerTool({
|
|
25992
|
+
...localGrep,
|
|
25993
|
+
async execute(_id, params, signal, _onUpdate, ctx) {
|
|
25994
|
+
return executeGondolinGrep(await ensureVm(ctx), localCwd, guestWorkspace, params, signal);
|
|
25995
|
+
}
|
|
25996
|
+
});
|
|
25704
25997
|
pi.on("user_bash", (_event, _ctx) => {
|
|
25705
25998
|
if (!vm) return;
|
|
25706
25999
|
return { operations: createGondolinBashOps(vm, localCwd, guestWorkspace) };
|
package/package.json
CHANGED