@themoltnet/pi-extension 0.37.2 → 0.38.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +88 -17
  2. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -1,14 +1,14 @@
1
1
  import { createRequire } from "node:module";
2
2
  import { execFileSync } from "node:child_process";
3
3
  import { createReadStream, createWriteStream, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync } from "node:fs";
4
- import path, { join } from "node:path";
4
+ import path, { isAbsolute, join, relative } from "node:path";
5
5
  import { DEFAULT_MAX_BYTES, createBashTool, createEditTool, createFindTool, createGrepTool, createLsTool, createReadTool, createWriteTool, defineTool, formatSize, truncateHead, truncateLine } from "@earendil-works/pi-coding-agent";
6
- import { readFile, realpath, stat } from "node:fs/promises";
6
+ import { mkdir, readFile, realpath, stat } from "node:fs/promises";
7
7
  import { pipeline } from "node:stream/promises";
8
8
  import { Type } from "@earendil-works/pi-ai";
9
- import { homedir } from "node:os";
10
9
  import crypto, { createHash } from "crypto";
11
10
  import { createHash as createHash$1 } from "node:crypto";
11
+ import { homedir } from "node:os";
12
12
  import { Readable } from "node:stream";
13
13
  import { MemoryProvider, RealFSProvider, ShadowProvider, VM, VmCheckpoint, createHttpHooks, createShadowPathPredicate, ensureImageSelector, isWriteFlag, loadGuestAssets } from "@earendil-works/gondolin";
14
14
  import { parseEnv } from "node:util";
@@ -41,6 +41,20 @@ var __copyProps = (to, from, except, desc) => {
41
41
  var __toCommonJS = (mod) => __hasOwnProp.call(mod, "module.exports") ? mod["module.exports"] : __copyProps(__defProp$1({}, "__esModule", { value: true }), mod);
42
42
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
43
43
  //#endregion
44
+ //#region ../pi-runtime/src/path-containment.ts
45
+ /**
46
+ * Check containment for already-resolved lexical or real paths.
47
+ *
48
+ * Callers that accept untrusted paths must resolve/realpath at their I/O
49
+ * boundary first; keeping the platform-specific relative-path rule here avoids
50
+ * subtly different `..` and absolute-path handling across runtime cleanup,
51
+ * session sync, and artifact staging.
52
+ */
53
+ function isResolvedPathInsideRoot(path, root) {
54
+ const rel = relative(root, path);
55
+ return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
56
+ }
57
+ //#endregion
44
58
  //#region ../pi-runtime/src/moltnet/render-phase6.ts
45
59
  function slugToTitle(value) {
46
60
  return value.split(/[:/_-]+/).filter(Boolean).map((part) => part[0]?.toUpperCase() + part.slice(1)).join(" ");
@@ -232,11 +246,32 @@ async function openWorkspaceArtifactInput(config, cwd, filePath) {
232
246
  }
233
247
  async function resolveWorkspaceOutputPath(cwd, filePath) {
234
248
  const resolved = path.isAbsolute(filePath) ? path.resolve(filePath) : path.resolve(cwd, filePath);
235
- const [realCwd, realParent] = await Promise.all([realpath(cwd), realpath(path.dirname(resolved))]);
236
- const rel = path.relative(realCwd, realParent);
237
- if (rel.startsWith("..") || path.isAbsolute(rel)) throw new Error(`task artifact output path escapes workspace: ${filePath}`);
249
+ const workspaceRoot = path.resolve(cwd);
250
+ const lexicalRel = path.relative(workspaceRoot, resolved);
251
+ if (lexicalRel === "" || lexicalRel.startsWith("..") || path.isAbsolute(lexicalRel)) throw new Error(`task artifact output path escapes workspace: ${filePath}`);
252
+ const realCwd = await realpath(cwd);
253
+ const parent = path.dirname(resolved);
254
+ assertPathInsideWorkspace(realCwd, await findExistingAncestor(parent), filePath);
255
+ await mkdir(parent, { recursive: true });
256
+ assertPathInsideWorkspace(realCwd, await realpath(parent), filePath);
238
257
  return resolved;
239
258
  }
259
+ async function findExistingAncestor(candidate) {
260
+ let current = candidate;
261
+ for (;;) {
262
+ try {
263
+ return await realpath(current);
264
+ } catch (err) {
265
+ if (!err || typeof err !== "object" || !("code" in err) || err.code !== "ENOENT") throw err;
266
+ }
267
+ const parent = path.dirname(current);
268
+ if (parent === current) throw new Error(`task artifact output has no existing ancestor: ${candidate}`);
269
+ current = parent;
270
+ }
271
+ }
272
+ function assertPathInsideWorkspace(realCwd, realPath, displayPath) {
273
+ if (!isResolvedPathInsideRoot(realPath, realCwd)) throw new Error(`task artifact output path escapes workspace: ${displayPath}`);
274
+ }
240
275
  /**
241
276
  * Expand the `taskFilter` shorthand on the diary list/search tools into
242
277
  * the matching `task:*` provenance tags emitted by `moltnet_create_entry`
@@ -9948,6 +9983,11 @@ var RuntimeModel = _Object_({
9948
9983
  var RUNTIME_PROFILE_CONTEXT_CATALOGUE = {
9949
9984
  version: 1,
9950
9985
  fragments: {
9986
+ "artifact-planner-v1": {
9987
+ binding: "prompt_prefix",
9988
+ content: "# Bounded artifact planner\n\n- The typed task facts, embedded bounded manifest, exact bound artifact references, registered tools, and runtime capability section are the complete contract. Do not search diaries, inspect a mounted repository, enumerate unrelated tasks or artifacts, use shell commands, modify files, commit, branch, push, or contact GitHub.\n- Read only the exact artifact CIDs named by the task, and only when the embedded manifest does not provide enough evidence. Never paginate or discover artifacts speculatively.\n- Perform semantic classification and planning from supplied content and producer/consumer evidence. Do not substitute filename, directory, language, ecosystem, or repository-specific exclusion rules for evidence.\n- Return exactly the requested versioned structured plan through the registered submit-output tool. Do not emit a second prose or JSON representation.",
9989
+ slug: "artifact-planner-v1"
9990
+ },
9951
9991
  "accountable-delivery-v1": {
9952
9992
  binding: "prompt_prefix",
9953
9993
  content: "# Accountable delivery\n\n- Pair every commit made during this task with a signed diary entry created by the `moltnet_create_entry` custom tool. Put the returned id in a `MoltNet-Diary: <id>` commit trailer.\n- Keep commit signing enabled; do not bypass the agent git configuration.\n- Push a branch and open or update a pull request only when the task asks for it. For GitHub mutations, use the credential-bound `GH_TOKEN` command form required by the runtime kernel.\n- Keep changes, commits, and any requested pull request coherent enough to review independently.",
@@ -9980,6 +10020,10 @@ var RUNTIME_PROFILE_CONTEXT_CATALOGUE = {
9980
10020
  }
9981
10021
  },
9982
10022
  recipes: {
10023
+ "artifact-planner@v1": {
10024
+ description: "Minimal artifact-only context for bounded semantic classification and planning.",
10025
+ fragments: ["artifact-planner-v1"]
10026
+ },
9983
10027
  "run-eval-direct@v1": {
9984
10028
  description: "Minimal direct context for a short, isolated evaluation run.",
9985
10029
  fragments: ["run-eval-direct-v1"]
@@ -11467,7 +11511,7 @@ var VerificationResult = _Object_({
11467
11511
  var VerificationRecord = _Object_({
11468
11512
  inputCid: String$1({ minLength: 1 }),
11469
11513
  results: _Array_(VerificationResult),
11470
- passed: Boolean$1()
11514
+ passed: Boolean$1({ description: "True iff every verification result has status \"pass\" or \"skip\"; false when any result has status \"fail\"." })
11471
11515
  }, {
11472
11516
  $id: "VerificationRecord",
11473
11517
  additionalProperties: false
@@ -15161,6 +15205,16 @@ function checkVerificationInputCid(value, runtime) {
15161
15205
  }];
15162
15206
  return [];
15163
15207
  }
15208
+ function checkVerificationPassedConsistency(value) {
15209
+ const verification = value !== null && typeof value === "object" ? value.verification : void 0;
15210
+ if (verification === void 0 || !Array.isArray(verification.results) || typeof verification.passed !== "boolean") return [];
15211
+ const expectedPassed = verification.results.every((result) => result.status !== "fail");
15212
+ if (verification.passed !== expectedPassed) return [{
15213
+ field: "output/verification/passed",
15214
+ message: "must be true iff no verification result has status \"fail\""
15215
+ }];
15216
+ return [];
15217
+ }
15164
15218
  function validateTaskResult(taskType, value, input, runtime, submission = false) {
15165
15219
  const entry = getTaskTypeEntry(taskType);
15166
15220
  if (!entry) return [{
@@ -15176,7 +15230,7 @@ function validateTaskResult(taskType, value, input, runtime, submission = false)
15176
15230
  message: validationError
15177
15231
  }];
15178
15232
  }
15179
- return checkVerificationInputCid(value, runtime);
15233
+ return [...checkVerificationInputCid(value, runtime), ...checkVerificationPassedConsistency(value)];
15180
15234
  }
15181
15235
  function validateTaskOutput(taskType, output, input, runtime) {
15182
15236
  return validateTaskResult(taskType, output, input, runtime);
@@ -17572,7 +17626,7 @@ var require_transport = /* @__PURE__ */ __commonJSMin(((exports, module) => {
17572
17626
  var { createRequire: createRequire$1 } = __require("module");
17573
17627
  var { existsSync: existsSync$1 } = __require("node:fs");
17574
17628
  var getCallers = require_caller();
17575
- var { join: join$1, isAbsolute, sep } = __require("node:path");
17629
+ var { join: join$1, isAbsolute: isAbsolute$1, sep } = __require("node:path");
17576
17630
  var { fileURLToPath } = __require("node:url");
17577
17631
  var sleep = require_atomic_sleep();
17578
17632
  var onExit = require_on_exit_leak_free();
@@ -17633,7 +17687,7 @@ var require_transport = /* @__PURE__ */ __commonJSMin(((exports, module) => {
17633
17687
  } catch {
17634
17688
  return false;
17635
17689
  }
17636
- return isAbsolute(path) && !existsSync$1(path);
17690
+ return isAbsolute$1(path) && !existsSync$1(path);
17637
17691
  }
17638
17692
  function stripQuotes(value) {
17639
17693
  const first = value[0];
@@ -17736,7 +17790,7 @@ var require_transport = /* @__PURE__ */ __commonJSMin(((exports, module) => {
17736
17790
  return buildStream(fixTarget(target), options, worker, sync, name);
17737
17791
  function fixTarget(origin) {
17738
17792
  origin = bundlerOverrides[origin] || origin;
17739
- if (isAbsolute(origin) || origin.indexOf("file://") === 0) return origin;
17793
+ if (isAbsolute$1(origin) || origin.indexOf("file://") === 0) return origin;
17740
17794
  if (origin === "pino/file") return join$1(__dirname, "..", "file.js");
17741
17795
  let fixTarget;
17742
17796
  for (const filePath of callers) try {
@@ -28545,6 +28599,12 @@ var WRAPPERS = new Map([
28545
28599
  ["builtin", spec(0)]
28546
28600
  ]);
28547
28601
  //#endregion
28602
+ //#region ../pi-runtime/src/config.ts
28603
+ /** Resolve Pi's host-side auth/config directory from process configuration. */
28604
+ function resolvePiCodingAgentDir() {
28605
+ return process.env["PI_CODING_AGENT_DIR"] ?? path.join(homedir(), ".pi", "agent");
28606
+ }
28607
+ //#endregion
28548
28608
  //#region ../pi-runtime/src/snapshot.ts
28549
28609
  /**
28550
28610
  * Snapshot builder with auto-build and caching.
@@ -28834,6 +28894,17 @@ async function delay(ms, signal, label) {
28834
28894
  * investigation and the alternatives we rejected.
28835
28895
  */
28836
28896
  var GUEST_TASK_CONTEXT_MOUNT = "/moltnet-task-context";
28897
+ function resolveVfsShadowConfig(config) {
28898
+ const patterns = config?.vfs?.shadow ?? [];
28899
+ if (patterns.length === 0) return {
28900
+ mode: "none",
28901
+ patterns: []
28902
+ };
28903
+ return {
28904
+ mode: config?.vfs?.shadowMode ?? "tmpfs",
28905
+ patterns
28906
+ };
28907
+ }
28837
28908
  function shouldRunResumeCommand(entry, ctx) {
28838
28909
  if (typeof entry === "string") return true;
28839
28910
  const workspaceModes = entry.when?.workspaceMode;
@@ -28906,7 +28977,7 @@ function resolveVmAgentDir(config) {
28906
28977
  function loadCredentials(agentDir) {
28907
28978
  const moltnetJson = readFileSync(path.join(agentDir, "moltnet.json"), "utf8");
28908
28979
  const agentEnvRaw = readFileSync(path.join(agentDir, "env"), "utf8");
28909
- const piAgentDir = process.env.PI_CODING_AGENT_DIR ?? path.join(process.env.HOME ?? "", ".pi", "agent");
28980
+ const piAgentDir = resolvePiCodingAgentDir();
28910
28981
  const piAuthPath = path.join(piAgentDir, "auth.json");
28911
28982
  const piAuthJson = existsSync(piAuthPath) ? readFileSync(piAuthPath, "utf8") : null;
28912
28983
  const gitconfigPath = path.join(agentDir, "gitconfig");
@@ -29061,7 +29132,7 @@ async function resumeVm(config) {
29061
29132
  else vmAgentEnv[k] = v;
29062
29133
  }
29063
29134
  vmAgentEnv.MOLTNET_CREDENTIALS_PATH = `${vmAgentDir}/moltnet.json`;
29064
- const vfsConfig = config.sandboxConfig?.vfs;
29135
+ const vfsConfig = resolveVfsShadowConfig(config.sandboxConfig);
29065
29136
  let workspaceProvider = new RealFSProvider(config.mountPath);
29066
29137
  workspaceProvider = new ShadowProvider(workspaceProvider, {
29067
29138
  shouldShadow: ({ path: shadowPath }) => shouldShadowNodeModulesPath(shadowPath),
@@ -29069,11 +29140,11 @@ async function resumeVm(config) {
29069
29140
  tmpfs: new AutoParentMemoryProvider(),
29070
29141
  writeMode: "tmpfs"
29071
29142
  });
29072
- if (vfsConfig?.shadow?.length) {
29073
- const predicate = createShadowPathPredicate(vfsConfig.shadow);
29143
+ if (vfsConfig.mode !== "none") {
29144
+ const predicate = createShadowPathPredicate(vfsConfig.patterns);
29074
29145
  workspaceProvider = new ShadowProvider(workspaceProvider, {
29075
29146
  shouldShadow: predicate,
29076
- writeMode: vfsConfig.shadowMode ?? "tmpfs"
29147
+ writeMode: vfsConfig.mode
29077
29148
  });
29078
29149
  }
29079
29150
  const forwardedEnv = {};
@@ -29533,7 +29604,7 @@ async function executeGondolinGrep(vm, localCwd, guestWorkspace, params, signal)
29533
29604
  const onAbort = () => ac.abort();
29534
29605
  signal?.addEventListener("abort", onAbort, { once: true });
29535
29606
  try {
29536
- const proc = vm.exec(["/bin/rg", ...args], {
29607
+ const proc = vm.exec(["rg", ...args], {
29537
29608
  signal: ac.signal,
29538
29609
  stdout: "pipe",
29539
29610
  stderr: "pipe"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@themoltnet/pi-extension",
3
- "version": "0.37.2",
3
+ "version": "0.38.0",
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": [
@@ -34,11 +34,11 @@
34
34
  },
35
35
  "dependencies": {
36
36
  "@earendil-works/gondolin": "^0.9.1",
37
- "@themoltnet/pi-runtime": "0.4.0",
37
+ "@themoltnet/pi-runtime": "0.5.0",
38
38
  "@themoltnet/sdk": "0.128.0"
39
39
  },
40
40
  "peerDependencies": {
41
- "@earendil-works/pi-ai": "0.74.0",
41
+ "@earendil-works/pi-ai": "0.79.4",
42
42
  "@earendil-works/pi-coding-agent": "0.79.4"
43
43
  },
44
44
  "peerDependenciesMeta": {
@@ -50,7 +50,7 @@
50
50
  }
51
51
  },
52
52
  "devDependencies": {
53
- "@earendil-works/pi-ai": "0.74.0",
53
+ "@earendil-works/pi-ai": "0.79.4",
54
54
  "@earendil-works/pi-coding-agent": "0.79.4",
55
55
  "@types/node": "^22.19.0",
56
56
  "typescript": "~5.9.2",