@themoltnet/pi-extension 0.37.1 → 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 +97 -18
  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"]
@@ -10617,7 +10661,11 @@ var ProblemDetailsSchema = _Object_({
10617
10661
  }),
10618
10662
  code: ProblemCodeSchema,
10619
10663
  detail: Optional(String$1()),
10620
- instance: Optional(String$1())
10664
+ instance: Optional(String$1()),
10665
+ retryAfter: Optional(Integer({
10666
+ minimum: 0,
10667
+ description: "Non-negative delay in seconds before retrying, matching the Retry-After response header when present."
10668
+ }))
10621
10669
  }, {
10622
10670
  $id: "ProblemDetails",
10623
10671
  additionalProperties: true
@@ -11463,7 +11511,7 @@ var VerificationResult = _Object_({
11463
11511
  var VerificationRecord = _Object_({
11464
11512
  inputCid: String$1({ minLength: 1 }),
11465
11513
  results: _Array_(VerificationResult),
11466
- 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\"." })
11467
11515
  }, {
11468
11516
  $id: "VerificationRecord",
11469
11517
  additionalProperties: false
@@ -15157,6 +15205,16 @@ function checkVerificationInputCid(value, runtime) {
15157
15205
  }];
15158
15206
  return [];
15159
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
+ }
15160
15218
  function validateTaskResult(taskType, value, input, runtime, submission = false) {
15161
15219
  const entry = getTaskTypeEntry(taskType);
15162
15220
  if (!entry) return [{
@@ -15172,7 +15230,7 @@ function validateTaskResult(taskType, value, input, runtime, submission = false)
15172
15230
  message: validationError
15173
15231
  }];
15174
15232
  }
15175
- return checkVerificationInputCid(value, runtime);
15233
+ return [...checkVerificationInputCid(value, runtime), ...checkVerificationPassedConsistency(value)];
15176
15234
  }
15177
15235
  function validateTaskOutput(taskType, output, input, runtime) {
15178
15236
  return validateTaskResult(taskType, output, input, runtime);
@@ -15520,6 +15578,10 @@ var TaskAttempt = _Object_({
15520
15578
  taskId: Uuid,
15521
15579
  attemptN: Number$1({ minimum: 1 }),
15522
15580
  claimedByAgentId: Uuid,
15581
+ leaseId: Union([Uuid, Null()]),
15582
+ runtimeProfileId: Union([Uuid, Null()]),
15583
+ runtimeProfileRevision: Union([Integer({ minimum: 1 }), Null()]),
15584
+ policySnapshotHash: Union([String$1({ pattern: "^sha256:[0-9a-f]{64}$" }), Null()]),
15523
15585
  runtimeId: Union([Uuid, Null()]),
15524
15586
  claimedAt: IsoTimestamp,
15525
15587
  startedAt: Union([IsoTimestamp, Null()]),
@@ -17564,7 +17626,7 @@ var require_transport = /* @__PURE__ */ __commonJSMin(((exports, module) => {
17564
17626
  var { createRequire: createRequire$1 } = __require("module");
17565
17627
  var { existsSync: existsSync$1 } = __require("node:fs");
17566
17628
  var getCallers = require_caller();
17567
- var { join: join$1, isAbsolute, sep } = __require("node:path");
17629
+ var { join: join$1, isAbsolute: isAbsolute$1, sep } = __require("node:path");
17568
17630
  var { fileURLToPath } = __require("node:url");
17569
17631
  var sleep = require_atomic_sleep();
17570
17632
  var onExit = require_on_exit_leak_free();
@@ -17625,7 +17687,7 @@ var require_transport = /* @__PURE__ */ __commonJSMin(((exports, module) => {
17625
17687
  } catch {
17626
17688
  return false;
17627
17689
  }
17628
- return isAbsolute(path) && !existsSync$1(path);
17690
+ return isAbsolute$1(path) && !existsSync$1(path);
17629
17691
  }
17630
17692
  function stripQuotes(value) {
17631
17693
  const first = value[0];
@@ -17728,7 +17790,7 @@ var require_transport = /* @__PURE__ */ __commonJSMin(((exports, module) => {
17728
17790
  return buildStream(fixTarget(target), options, worker, sync, name);
17729
17791
  function fixTarget(origin) {
17730
17792
  origin = bundlerOverrides[origin] || origin;
17731
- if (isAbsolute(origin) || origin.indexOf("file://") === 0) return origin;
17793
+ if (isAbsolute$1(origin) || origin.indexOf("file://") === 0) return origin;
17732
17794
  if (origin === "pino/file") return join$1(__dirname, "..", "file.js");
17733
17795
  let fixTarget;
17734
17796
  for (const filePath of callers) try {
@@ -28537,6 +28599,12 @@ var WRAPPERS = new Map([
28537
28599
  ["builtin", spec(0)]
28538
28600
  ]);
28539
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
28540
28608
  //#region ../pi-runtime/src/snapshot.ts
28541
28609
  /**
28542
28610
  * Snapshot builder with auto-build and caching.
@@ -28826,6 +28894,17 @@ async function delay(ms, signal, label) {
28826
28894
  * investigation and the alternatives we rejected.
28827
28895
  */
28828
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
+ }
28829
28908
  function shouldRunResumeCommand(entry, ctx) {
28830
28909
  if (typeof entry === "string") return true;
28831
28910
  const workspaceModes = entry.when?.workspaceMode;
@@ -28898,7 +28977,7 @@ function resolveVmAgentDir(config) {
28898
28977
  function loadCredentials(agentDir) {
28899
28978
  const moltnetJson = readFileSync(path.join(agentDir, "moltnet.json"), "utf8");
28900
28979
  const agentEnvRaw = readFileSync(path.join(agentDir, "env"), "utf8");
28901
- const piAgentDir = process.env.PI_CODING_AGENT_DIR ?? path.join(process.env.HOME ?? "", ".pi", "agent");
28980
+ const piAgentDir = resolvePiCodingAgentDir();
28902
28981
  const piAuthPath = path.join(piAgentDir, "auth.json");
28903
28982
  const piAuthJson = existsSync(piAuthPath) ? readFileSync(piAuthPath, "utf8") : null;
28904
28983
  const gitconfigPath = path.join(agentDir, "gitconfig");
@@ -29053,7 +29132,7 @@ async function resumeVm(config) {
29053
29132
  else vmAgentEnv[k] = v;
29054
29133
  }
29055
29134
  vmAgentEnv.MOLTNET_CREDENTIALS_PATH = `${vmAgentDir}/moltnet.json`;
29056
- const vfsConfig = config.sandboxConfig?.vfs;
29135
+ const vfsConfig = resolveVfsShadowConfig(config.sandboxConfig);
29057
29136
  let workspaceProvider = new RealFSProvider(config.mountPath);
29058
29137
  workspaceProvider = new ShadowProvider(workspaceProvider, {
29059
29138
  shouldShadow: ({ path: shadowPath }) => shouldShadowNodeModulesPath(shadowPath),
@@ -29061,11 +29140,11 @@ async function resumeVm(config) {
29061
29140
  tmpfs: new AutoParentMemoryProvider(),
29062
29141
  writeMode: "tmpfs"
29063
29142
  });
29064
- if (vfsConfig?.shadow?.length) {
29065
- const predicate = createShadowPathPredicate(vfsConfig.shadow);
29143
+ if (vfsConfig.mode !== "none") {
29144
+ const predicate = createShadowPathPredicate(vfsConfig.patterns);
29066
29145
  workspaceProvider = new ShadowProvider(workspaceProvider, {
29067
29146
  shouldShadow: predicate,
29068
- writeMode: vfsConfig.shadowMode ?? "tmpfs"
29147
+ writeMode: vfsConfig.mode
29069
29148
  });
29070
29149
  }
29071
29150
  const forwardedEnv = {};
@@ -29525,7 +29604,7 @@ async function executeGondolinGrep(vm, localCwd, guestWorkspace, params, signal)
29525
29604
  const onAbort = () => ac.abort();
29526
29605
  signal?.addEventListener("abort", onAbort, { once: true });
29527
29606
  try {
29528
- const proc = vm.exec(["/bin/rg", ...args], {
29607
+ const proc = vm.exec(["rg", ...args], {
29529
29608
  signal: ac.signal,
29530
29609
  stdout: "pipe",
29531
29610
  stderr: "pipe"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@themoltnet/pi-extension",
3
- "version": "0.37.1",
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.3.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",