@themoltnet/pi-extension 0.27.3 → 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 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) execute inside the VM.
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 ─▶ vm.exec() / vm.fs.*
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
@@ -40,6 +40,16 @@ declare interface BuildAgentSessionArgs {
40
40
  piAuthDir: string;
41
41
  /** Resolved pi model handle (provider + model id). */
42
42
  modelHandle: Model<Api>;
43
+ /** Optional runtime-profile thinking/reasoning level applied at session start. */
44
+ thinkingLevel?: PiThinkingLevel | null;
45
+ /** Optional runtime-profile sampling temperature applied to provider requests. */
46
+ temperature?: number | null;
47
+ /** Optional runtime-profile nucleus sampling probability mass. */
48
+ topP?: number | null;
49
+ /** Optional runtime-profile top-k sampling cutoff. */
50
+ topK?: number | null;
51
+ /** Optional runtime-profile generated output token cap. */
52
+ maxOutputTokens?: number | null;
43
53
  /** Pre-built customTools array. Caller composes Gondolin + MoltNet + submit tools. */
44
54
  customTools: ToolDefinition[];
45
55
  /** System-prompt fragments appended after pi's defaults. Parent passes the
@@ -157,11 +167,21 @@ export declare interface CreateSubagentToolArgs {
157
167
  piAuthDir: string;
158
168
  /** Resolved pi model handle — subagents share it. */
159
169
  modelHandle: Model<Api>;
170
+ /** Runtime-profile thinking/reasoning level — subagents inherit it. */
171
+ thinkingLevel?: PiThinkingLevel | null;
172
+ /** Runtime-profile sampling temperature — subagents inherit it. */
173
+ temperature?: number | null;
174
+ /** Runtime-profile nucleus sampling probability mass — subagents inherit it. */
175
+ topP?: number | null;
176
+ /** Runtime-profile top-k sampling cutoff — subagents inherit it. */
177
+ topK?: number | null;
178
+ /** Runtime-profile generated output token cap — subagents inherit it. */
179
+ maxOutputTokens?: number | null;
160
180
  /** Agent name for telemetry. */
161
181
  agentName: string;
162
182
  /**
163
183
  * Custom tools every subagent inherits (Gondolin-routed
164
- * Read/Write/Edit/Bash + moltnet_* tools, etc). MUST NOT include
184
+ * built-ins + moltnet_* tools, etc). MUST NOT include
165
185
  * the parent's submit-output tool, the parent's `subagent` tool,
166
186
  * or any other parent-only artefact — the caller is responsible
167
187
  * for filtering. The subagent appends its own submit tool.
@@ -256,6 +276,20 @@ export declare interface ExecutePiTaskOptions {
256
276
  /** LLM selection. */
257
277
  provider: string;
258
278
  model: string;
279
+ /**
280
+ * Runtime-profile reasoning/thinking level. Null/undefined means use Pi's
281
+ * configured default; explicit `off` disables provider thinking where
282
+ * supported.
283
+ */
284
+ thinkingLevel?: PiThinkingLevel | null;
285
+ /** Optional sampling temperature. Null/undefined means provider default. */
286
+ temperature?: number | null;
287
+ /** Optional nucleus-sampling probability mass. Null/undefined means provider default. */
288
+ topP?: number | null;
289
+ /** Optional top-k sampling cutoff. Null/undefined means provider default. */
290
+ topK?: number | null;
291
+ /** Optional cap on generated output tokens. Null/undefined means provider/model default. */
292
+ maxOutputTokens?: number | null;
259
293
  /** Extra hosts to allow in the sandbox egress policy. */
260
294
  extraAllowedHosts?: string[];
261
295
  /** Sandbox overrides (env, VFS shadows, resources). */
@@ -544,6 +578,8 @@ export declare interface PiTaskExecutionPlan {
544
578
 
545
579
  export declare type PiTaskExecutionPlanFactory = (claimedTask: ClaimedTask) => Promise<PiTaskExecutionPlan | null> | PiTaskExecutionPlan | null;
546
580
 
581
+ declare type PiThinkingLevel = 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh';
582
+
547
583
  declare interface PiWorkspaceAttachmentPlan {
548
584
  mountPath: string;
549
585
  cwdPath: string;
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";
@@ -1868,6 +1868,28 @@ var uploadRuntimeSession = (options) => (options.client ?? client).put({
1868
1868
  }
1869
1869
  });
1870
1870
  /**
1871
+ * List recent team-scoped runtime slots for repair/sync.
1872
+ */
1873
+ var listRuntimeSlots = (options) => (options.client ?? client).get({
1874
+ security: [
1875
+ {
1876
+ scheme: "bearer",
1877
+ type: "http"
1878
+ },
1879
+ {
1880
+ name: "X-Moltnet-Session-Token",
1881
+ type: "apiKey"
1882
+ },
1883
+ {
1884
+ in: "cookie",
1885
+ name: "ory_kratos_session",
1886
+ type: "apiKey"
1887
+ }
1888
+ ],
1889
+ url: "/runtime-slots",
1890
+ ...options
1891
+ });
1892
+ /**
1871
1893
  * Upsert a team-scoped runtime slot for audit and continuation affinity lookup.
1872
1894
  */
1873
1895
  var beginRuntimeSlot = (options) => (options.client ?? client).post({
@@ -5171,6 +5193,15 @@ function createRuntimeSlotsNamespace(context) {
5171
5193
  if (err instanceof MoltNetError && err.statusCode === 404) return null;
5172
5194
  throw err;
5173
5195
  }
5196
+ },
5197
+ async list(query, options) {
5198
+ const filteredQuery = Object.fromEntries(Object.entries(query).filter(([, value]) => value !== void 0));
5199
+ return unwrapResult(await listRuntimeSlots({
5200
+ auth,
5201
+ client,
5202
+ headers: requiredTeamHeaders(options),
5203
+ query: filteredQuery
5204
+ })).items;
5174
5205
  }
5175
5206
  };
5176
5207
  }
@@ -9520,6 +9551,32 @@ var RuntimeProfileAllowedWorkspaceModes = _Array_(RuntimeProfileWorkspaceMode, {
9520
9551
  maxItems: 3,
9521
9552
  uniqueItems: true
9522
9553
  });
9554
+ var RuntimeProfileThinkingLevelOptions = [
9555
+ Literal("off"),
9556
+ Literal("minimal"),
9557
+ Literal("low"),
9558
+ Literal("medium"),
9559
+ Literal("high"),
9560
+ Literal("xhigh")
9561
+ ];
9562
+ Union([...RuntimeProfileThinkingLevelOptions]);
9563
+ var RuntimeProfileNullableThinkingLevel = Union([...RuntimeProfileThinkingLevelOptions, Null()]);
9564
+ var RuntimeProfileNullableTemperature = Union([Null(), Number$1({
9565
+ minimum: 0,
9566
+ maximum: 2
9567
+ })]);
9568
+ var RuntimeProfileNullableTopP = Union([Null(), Number$1({
9569
+ minimum: 0,
9570
+ maximum: 1
9571
+ })]);
9572
+ var RuntimeProfileNullableTopK = Union([Integer({
9573
+ minimum: 1,
9574
+ maximum: 1e4
9575
+ }), Null()]);
9576
+ var RuntimeProfileNullableMaxOutputTokens = Union([Integer({
9577
+ minimum: 1,
9578
+ maximum: 1e6
9579
+ }), Null()]);
9523
9580
  var SandboxResumeCommandWhenSchema = _Object_({ workspaceMode: Optional(_Array_(Union([
9524
9581
  Literal("shared_mount"),
9525
9582
  Literal("dedicated_worktree"),
@@ -9644,6 +9701,11 @@ _Object_({
9644
9701
  minLength: 1,
9645
9702
  maxLength: 200
9646
9703
  }),
9704
+ thinkingLevel: RuntimeProfileNullableThinkingLevel,
9705
+ temperature: RuntimeProfileNullableTemperature,
9706
+ topP: RuntimeProfileNullableTopP,
9707
+ topK: RuntimeProfileNullableTopK,
9708
+ maxOutputTokens: RuntimeProfileNullableMaxOutputTokens,
9647
9709
  runtimeKind: Literal("gondolin_pi"),
9648
9710
  sandbox: RuntimeProfileSandbox,
9649
9711
  sessionStorageMode: Literal("local"),
@@ -9755,7 +9817,7 @@ var RuntimeWorkspace = _Object_({
9755
9817
  createdAtMs: Integer({ minimum: 0 }),
9756
9818
  lastUsedAtMs: Integer({ minimum: 0 })
9757
9819
  }, { $id: "RuntimeWorkspace" });
9758
- _Object_({
9820
+ _Object_({ items: _Array_(_Object_({
9759
9821
  slot: _Object_({
9760
9822
  id: String$1({ format: "uuid" }),
9761
9823
  teamId: String$1({ format: "uuid" }),
@@ -9788,7 +9850,7 @@ _Object_({
9788
9850
  expiresAtMs: Integer({ minimum: 0 })
9789
9851
  }, { $id: "RuntimeSlot" }),
9790
9852
  workspace: Union([RuntimeWorkspace, Null()])
9791
- }, { $id: "ResolvedRuntimeSlot" });
9853
+ }, { $id: "ResolvedRuntimeSlot" })) }, { $id: "RuntimeSlotListResponse" });
9792
9854
  _Object_({
9793
9855
  agentName: String$1({
9794
9856
  minLength: 1,
@@ -9849,6 +9911,21 @@ _Object_({
9849
9911
  $id: "FindLatestRuntimeSlotForAttemptQuery",
9850
9912
  additionalProperties: false
9851
9913
  });
9914
+ _Object_({
9915
+ agentName: Optional(String$1({
9916
+ minLength: 1,
9917
+ maxLength: 100
9918
+ })),
9919
+ runtimeProfileId: Optional(String$1({ format: "uuid" })),
9920
+ state: Optional(RuntimeSlotState),
9921
+ limit: Optional(Integer({
9922
+ minimum: 1,
9923
+ maximum: 200
9924
+ }))
9925
+ }, {
9926
+ $id: "ListRuntimeSlotsQuery",
9927
+ additionalProperties: false
9928
+ });
9852
9929
  //#endregion
9853
9930
  //#region ../tasks/src/success-criteria.ts
9854
9931
  /**
@@ -18482,11 +18559,13 @@ function rewriteMoltnetJsonPaths(moltnetJson, vmAgentDir, vmSshDir, githubAppPem
18482
18559
  //#region src/tool-operations.ts
18483
18560
  /**
18484
18561
  * Gondolin tool operations: redirect pi's built-in tool operations
18485
- * (read, write, edit, bash) to execute inside the VM.
18562
+ * (read, write, edit, bash, ls, find, grep) to execute inside the VM.
18486
18563
  *
18487
18564
  * Follows the same pattern as upstream pi-gondolin.ts — pi's tool factories
18488
18565
  * accept an `operations` object that provides the underlying I/O.
18489
18566
  */
18567
+ var DEFAULT_GREP_LIMIT = 100;
18568
+ var GREP_MAX_FILE_SIZE = "2M";
18490
18569
  function shQuote(s) {
18491
18570
  return "'" + s.replace(/'/g, "'\\''") + "'";
18492
18571
  }
@@ -18496,6 +18575,18 @@ function normalizeGuestPath(p) {
18496
18575
  function isSameOrInsidePosixPath(candidate, root) {
18497
18576
  return candidate === root || candidate.startsWith(`${root}/`);
18498
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
+ }
18499
18590
  /**
18500
18591
  * Map a host-side absolute path to a guest-side workspace path.
18501
18592
  * Throws if the path escapes the workspace.
@@ -18515,17 +18606,10 @@ function toGuestPath(localCwd, localPath, guestWorkspace) {
18515
18606
  function createGondolinReadOps(vm, localCwd, guestWorkspace) {
18516
18607
  return {
18517
18608
  readFile: async (p) => {
18518
- const r = await vm.exec(["/bin/cat", toGuestPath(localCwd, p, guestWorkspace)]);
18519
- if (!r.ok) throw new Error(`cat failed (${r.exitCode}): ${r.stderr}`);
18520
- return r.stdoutBuffer;
18521
- },
18522
- access: async (p) => {
18523
- if (!(await vm.exec([
18524
- "/bin/sh",
18525
- "-lc",
18526
- `test -r ${shQuote(toGuestPath(localCwd, p, guestWorkspace))}`
18527
- ])).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);
18528
18611
  },
18612
+ access: async (p) => vm.fs.access(toGuestPath(localCwd, p, guestWorkspace)),
18529
18613
  detectImageMimeType: async (p) => {
18530
18614
  try {
18531
18615
  const r = await vm.exec([
@@ -18583,6 +18667,253 @@ function createGondolinEditOps(vm, localCwd, guestWorkspace) {
18583
18667
  writeFile: w.writeFile
18584
18668
  };
18585
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
+ }
18586
18917
  function createGondolinBashOps(vm, localCwd, guestWorkspace) {
18587
18918
  return { exec: async (command, cwd, { onData, signal, timeout, env }) => {
18588
18919
  const guestCwd = toGuestPath(localCwd, cwd, guestWorkspace);
@@ -18755,6 +19086,89 @@ function extractUsage(message) {
18755
19086
  };
18756
19087
  }
18757
19088
  //#endregion
19089
+ //#region src/runtime/model-options-extension.ts
19090
+ function hasPiModelOptions(options) {
19091
+ return options.temperature !== void 0 && options.temperature !== null || options.topP !== void 0 && options.topP !== null || options.topK !== void 0 && options.topK !== null || options.maxOutputTokens !== void 0 && options.maxOutputTokens !== null;
19092
+ }
19093
+ function createPiModelOptionsExtension(options) {
19094
+ return function piModelOptionsExtension(pi) {
19095
+ pi.on("before_provider_request", (event) => {
19096
+ return applyPiModelOptions(event.payload, options);
19097
+ });
19098
+ };
19099
+ }
19100
+ function applyPiModelOptions(payload, options) {
19101
+ if (!isRecord(payload)) return void 0;
19102
+ if (!hasPiModelOptions(options)) return void 0;
19103
+ if (isGooglePayload(payload)) {
19104
+ const config = isRecord(payload.config) ? payload.config : {};
19105
+ return {
19106
+ ...payload,
19107
+ config: applyConfigOptions(config, options)
19108
+ };
19109
+ }
19110
+ if (isBedrockPayload(payload)) {
19111
+ const inferenceConfig = isRecord(payload.inferenceConfig) ? payload.inferenceConfig : {};
19112
+ return {
19113
+ ...payload,
19114
+ inferenceConfig: applyBedrockOptions(inferenceConfig, options)
19115
+ };
19116
+ }
19117
+ return applyTopLevelOptions(payload, options);
19118
+ }
19119
+ function applyConfigOptions(config, options) {
19120
+ return {
19121
+ ...config,
19122
+ ...options.temperature !== void 0 && options.temperature !== null ? { temperature: options.temperature } : {},
19123
+ ...options.topP !== void 0 && options.topP !== null ? { topP: options.topP } : {},
19124
+ ...options.topK !== void 0 && options.topK !== null ? { topK: options.topK } : {},
19125
+ ...options.maxOutputTokens !== void 0 && options.maxOutputTokens !== null ? { maxOutputTokens: options.maxOutputTokens } : {}
19126
+ };
19127
+ }
19128
+ function applyBedrockOptions(inferenceConfig, options) {
19129
+ return {
19130
+ ...inferenceConfig,
19131
+ ...options.temperature !== void 0 && options.temperature !== null ? { temperature: options.temperature } : {},
19132
+ ...options.topP !== void 0 && options.topP !== null ? { topP: options.topP } : {},
19133
+ ...options.maxOutputTokens !== void 0 && options.maxOutputTokens !== null ? { maxTokens: options.maxOutputTokens } : {}
19134
+ };
19135
+ }
19136
+ function applyTopLevelOptions(payload, options) {
19137
+ const reasoningEnabled = hasActiveThinking(payload.thinking) || "reasoning" in payload || "reasoning_effort" in payload;
19138
+ const next = { ...payload };
19139
+ if (options.temperature !== void 0 && options.temperature !== null && !reasoningEnabled) next.temperature = options.temperature;
19140
+ if (options.topP !== void 0 && options.topP !== null && !reasoningEnabled) next.top_p = options.topP;
19141
+ if (options.topK !== void 0 && options.topK !== null && !reasoningEnabled && isAnthropicPayload(next)) next.top_k = options.topK;
19142
+ if (options.maxOutputTokens !== void 0 && options.maxOutputTokens !== null) {
19143
+ const maxOutputTokens = options.maxOutputTokens;
19144
+ if ("max_output_tokens" in next || isResponsesPayload(next)) next.max_output_tokens = maxOutputTokens;
19145
+ else if ("max_completion_tokens" in next) next.max_completion_tokens = maxOutputTokens;
19146
+ else if ("maxTokens" in next) next.maxTokens = maxOutputTokens;
19147
+ else next.max_tokens = maxOutputTokens;
19148
+ }
19149
+ return next;
19150
+ }
19151
+ function isGooglePayload(payload) {
19152
+ return "contents" in payload && ("config" in payload || "model" in payload);
19153
+ }
19154
+ function isBedrockPayload(payload) {
19155
+ return "inferenceConfig" in payload || "additionalModelRequestFields" in payload;
19156
+ }
19157
+ function isResponsesPayload(payload) {
19158
+ return "input" in payload && !("messages" in payload);
19159
+ }
19160
+ function isAnthropicPayload(payload) {
19161
+ return "anthropic_version" in payload;
19162
+ }
19163
+ function hasActiveThinking(value) {
19164
+ if (!isRecord(value)) return false;
19165
+ const type = value.type;
19166
+ return type !== "disabled" && type !== "off" && type !== false;
19167
+ }
19168
+ function isRecord(value) {
19169
+ return typeof value === "object" && value !== null && !Array.isArray(value);
19170
+ }
19171
+ //#endregion
18758
19172
  //#region src/runtime/agent-session-factory.ts
18759
19173
  var NO_SKILLS = () => ({
18760
19174
  skills: [],
@@ -18772,10 +19186,17 @@ async function buildAgentSession(args) {
18772
19186
  agentName: args.agentName,
18773
19187
  spanAttributes: args.otelSpanAttrs
18774
19188
  });
19189
+ const modelOptions = {
19190
+ temperature: args.temperature,
19191
+ topP: args.topP,
19192
+ topK: args.topK,
19193
+ maxOutputTokens: args.maxOutputTokens
19194
+ };
19195
+ const extensionFactories = hasPiModelOptions(modelOptions) ? [piOtelExtension, createPiModelOptionsExtension(modelOptions)] : [piOtelExtension];
18775
19196
  const resourceLoader = new DefaultResourceLoader({
18776
19197
  cwd: args.cwdPath,
18777
19198
  agentDir: args.piAuthDir,
18778
- extensionFactories: [piOtelExtension],
19199
+ extensionFactories,
18779
19200
  appendSystemPrompt: args.appendSystemPrompt,
18780
19201
  skillsOverride: args.skillsOverride ?? NO_SKILLS
18781
19202
  });
@@ -18789,6 +19210,7 @@ async function buildAgentSession(args) {
18789
19210
  agentDir: args.piAuthDir,
18790
19211
  cwd: args.cwdPath,
18791
19212
  model: args.modelHandle,
19213
+ thinkingLevel: args.thinkingLevel ?? void 0,
18792
19214
  customTools: args.customTools,
18793
19215
  sessionManager,
18794
19216
  resourceLoader
@@ -24052,6 +24474,11 @@ function createSubagentTool(args) {
24052
24474
  cwdPath: args.cwdPath ?? args.mountPath,
24053
24475
  piAuthDir: args.piAuthDir,
24054
24476
  modelHandle: args.modelHandle,
24477
+ thinkingLevel: args.thinkingLevel,
24478
+ temperature: args.temperature,
24479
+ topP: args.topP,
24480
+ topK: args.topK,
24481
+ maxOutputTokens: args.maxOutputTokens,
24055
24482
  agentName: args.agentName,
24056
24483
  customTools: [...args.inheritedCustomTools, submitTool],
24057
24484
  appendSystemPrompt: [args.parentRuntimeInstructor, subagentInstructor],
@@ -24119,7 +24546,7 @@ function subagentToolDescription() {
24119
24546
  "",
24120
24547
  "The subagent starts with no conversation history and only the `task` ",
24121
24548
  "string you provide as its instructions. It runs in the same VM with ",
24122
- "the same tools you have (Gondolin-routed Read/Write/Edit/Bash, ",
24549
+ "the same tools you have (Gondolin-routed built-ins, ",
24123
24550
  "moltnet_* tools), and is expected to call ",
24124
24551
  `\`${SUBAGENT_SUBMIT_TOOL_NAME}\` with a payload matching the named `,
24125
24552
  "contract before its session ends.",
@@ -24611,7 +25038,7 @@ function shouldSkipSeedEntry(sourceEntry, entryName, resolvedTargetDir) {
24611
25038
  *
24612
25039
  * This is the pi-specific task executor. It owns:
24613
25040
  * - VM lifecycle (ensureSnapshot + resumeVm + close)
24614
- * - Gondolin-redirected tool wiring (read/write/edit/bash → VM)
25041
+ * - Gondolin-redirected tool wiring (read/write/edit/bash/ls/find/grep → VM)
24615
25042
  * - MoltNet custom tools (diary entries, pack render/judge, etc.)
24616
25043
  * - pi createAgentSession + event → TaskReporter bridge
24617
25044
  *
@@ -24622,6 +25049,25 @@ function shouldSkipSeedEntry(sourceEntry, entryName, resolvedTargetDir) {
24622
25049
  * `AgentRuntime`.
24623
25050
  */
24624
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
+ }
24625
25071
  /**
24626
25072
  * Factory that builds a pi-specific `executeTask` function suitable for
24627
25073
  * injection into `AgentRuntime`. The returned function caches the resolved
@@ -24885,12 +25331,11 @@ async function executePiTask(claimedTask, reporter, opts) {
24885
25331
  slugs: injectedContext.injected.map((r) => r.slug)
24886
25332
  });
24887
25333
  if (injectedContext.userInlineSuffix) taskPrompt = `${taskPrompt}\n\n---\n\n${injectedContext.userInlineSuffix}`;
24888
- const gondolinCustomTools = [
24889
- createReadToolDefinition(mountPath, { operations: createGondolinReadOps(managed.vm, mountPath, managed.guestWorkspace) }),
24890
- createWriteToolDefinition(mountPath, { operations: createGondolinWriteOps(managed.vm, mountPath, managed.guestWorkspace) }),
24891
- createEditToolDefinition(mountPath, { operations: createGondolinEditOps(managed.vm, mountPath, managed.guestWorkspace) }),
24892
- createBashToolDefinition(mountPath, { operations: createGondolinBashOps(managed.vm, mountPath, managed.guestWorkspace) })
24893
- ];
25334
+ const gondolinCustomTools = createGondolinToolDefinitions({
25335
+ vm: managed.vm,
25336
+ mountPath,
25337
+ guestWorkspace: managed.guestWorkspace
25338
+ });
24894
25339
  const { handle: submitToolHandle, tools: submitToolDefs } = resolveSubmitTools(task.taskType, {
24895
25340
  model: opts.model,
24896
25341
  input: task.input
@@ -24936,6 +25381,11 @@ async function executePiTask(claimedTask, reporter, opts) {
24936
25381
  cwdPath,
24937
25382
  piAuthDir,
24938
25383
  modelHandle,
25384
+ thinkingLevel: opts.thinkingLevel,
25385
+ temperature: opts.temperature,
25386
+ topP: opts.topP,
25387
+ topK: opts.topK,
25388
+ maxOutputTokens: opts.maxOutputTokens,
24939
25389
  agentName: opts.agentName,
24940
25390
  inheritedCustomTools: [...gondolinCustomTools, ...moltnetTools],
24941
25391
  parentRuntimeInstructor: runtimeInstructor,
@@ -24952,6 +25402,11 @@ async function executePiTask(claimedTask, reporter, opts) {
24952
25402
  cwdPath,
24953
25403
  piAuthDir,
24954
25404
  modelHandle,
25405
+ thinkingLevel: opts.thinkingLevel,
25406
+ temperature: opts.temperature,
25407
+ topP: opts.topP,
25408
+ topK: opts.topK,
25409
+ maxOutputTokens: opts.maxOutputTokens,
24955
25410
  agentName: opts.agentName,
24956
25411
  customTools: [
24957
25412
  ...gondolinCustomTools,
@@ -25408,6 +25863,9 @@ function moltnetExtension(pi) {
25408
25863
  const localWrite = createWriteTool(localCwd);
25409
25864
  const localEdit = createEditTool(localCwd);
25410
25865
  const localBash = createBashTool(localCwd);
25866
+ const localLs = createLsTool(localCwd);
25867
+ const localFind = createFindTool(localCwd);
25868
+ const localGrep = createGrepTool(localCwd);
25411
25869
  const initialGuestWorkspace = path.resolve(localCwd);
25412
25870
  let vm = null;
25413
25871
  let guestWorkspace = initialGuestWorkspace;
@@ -25518,6 +25976,24 @@ function moltnetExtension(pi) {
25518
25976
  return createBashTool(localCwd, { operations: createGondolinBashOps(await ensureVm(ctx), localCwd, guestWorkspace) }).execute(id, params, signal, onUpdate);
25519
25977
  }
25520
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
+ });
25521
25997
  pi.on("user_bash", (_event, _ctx) => {
25522
25998
  if (!vm) return;
25523
25999
  return { operations: createGondolinBashOps(vm, localCwd, guestWorkspace) };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@themoltnet/pi-extension",
3
- "version": "0.27.3",
3
+ "version": "0.28.1",
4
4
  "type": "module",
5
5
  "description": "MoltNet pi extension — sandboxed tool execution in Gondolin VMs with MoltNet identity and persistent memory",
6
6
  "keywords": [
@@ -36,8 +36,8 @@
36
36
  "@earendil-works/gondolin": "^0.9.1",
37
37
  "@opentelemetry/api": "^1.9.0",
38
38
  "typebox": "^1.2.8",
39
- "@themoltnet/sdk": "0.113.1",
40
- "@themoltnet/agent-runtime": "0.31.0"
39
+ "@themoltnet/agent-runtime": "0.31.1",
40
+ "@themoltnet/sdk": "0.114.0"
41
41
  },
42
42
  "peerDependencies": {
43
43
  "@earendil-works/pi-coding-agent": ">=0.74.0",
@@ -61,8 +61,7 @@
61
61
  "vite": "^8.0.0",
62
62
  "vite-plugin-dts": "^4.5.4",
63
63
  "vitest": "^3.0.0",
64
- "@moltnet/crypto-service": "0.1.0",
65
- "@moltnet/tasks": "0.1.0"
64
+ "@moltnet/crypto-service": "0.1.0"
66
65
  },
67
66
  "engines": {
68
67
  "node": ">=22"