@themoltnet/pi-extension 0.29.0 → 0.30.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.
package/dist/index.d.ts CHANGED
@@ -6,6 +6,7 @@ import { EditOperations } from '@earendil-works/pi-coding-agent';
6
6
  import { ExtensionAPI } from '@earendil-works/pi-coding-agent';
7
7
  import { LoadSkillsResult } from '@earendil-works/pi-coding-agent';
8
8
  import { Model } from '@earendil-works/pi-ai';
9
+ import { Readable } from 'node:stream';
9
10
  import { ReadOperations } from '@earendil-works/pi-coding-agent';
10
11
  import { Skill } from '@earendil-works/pi-coding-agent';
11
12
  import { Static } from 'typebox';
@@ -294,6 +295,8 @@ export declare interface ExecutePiTaskOptions {
294
295
  extraAllowedHosts?: string[];
295
296
  /** Sandbox overrides (env, VFS shadows, resources). */
296
297
  sandboxConfig?: SandboxConfig;
298
+ /** Host environment variable names to forward into the Pi VM. */
299
+ forwardEnv?: string[];
297
300
  /**
298
301
  * Forwarded to `buildTaskUserPrompt` for per-type builders. Static
299
302
  * across tasks. Today no built-in builder needs per-task `extras` —
@@ -481,6 +484,17 @@ declare interface MoltNetToolsConfig {
481
484
  clearSessionErrors(): void;
482
485
  /** Host working directory for host-exec commands (worktree path or cwd). */
483
486
  getHostCwd?(): string;
487
+ /**
488
+ * Optional workspace-file reader. Daemon/Gondolin callers provide this so
489
+ * artifact uploads see guest overlay writes that may not exist on the host
490
+ * mount path yet.
491
+ */
492
+ openWorkspaceFileForRead?(filePath: string): Promise<{
493
+ stream: Readable;
494
+ isFile: boolean;
495
+ sizeBytes?: number;
496
+ displayPath?: string;
497
+ }>;
484
498
  /**
485
499
  * Set of process.env keys that are safe to forward to host-exec child
486
500
  * processes. Configured at sandbox startup so the caller can include
@@ -972,6 +986,14 @@ export declare interface VmConfig {
972
986
  extraAllowedHosts?: string[];
973
987
  /** Full sandbox config (vfs shadows, env overrides). */
974
988
  sandboxConfig?: SandboxConfig;
989
+ /**
990
+ * Host environment variable names to copy into the VM process.
991
+ *
992
+ * Runtime profiles use this for provider API keys: `requiredEnv` proves the
993
+ * daemon host has the secret, and this allowlist forwards only those names
994
+ * into the guest without storing secret values in the profile.
995
+ */
996
+ forwardEnv?: string[];
975
997
  /** Abort resume/setup work, closing any live VM owned by resumeVm. */
976
998
  signal?: AbortSignal;
977
999
  }
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { createRequire } from "node:module";
2
2
  import { execFileSync } from "node:child_process";
3
3
  import { cpSync, createReadStream, createWriteStream, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, statSync } from "node:fs";
4
- import path, { join, relative, sep } from "node:path";
4
+ import path, { isAbsolute, join, relative, resolve, sep } from "node:path";
5
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";
@@ -2874,6 +2874,15 @@ function unwrapResult(result) {
2874
2874
  networkError.stack = error.stack;
2875
2875
  throw networkError;
2876
2876
  }
2877
+ const responseSummary = summarizeResponse(result.response);
2878
+ if (responseSummary) {
2879
+ const detail = stringifyUnknown(error);
2880
+ throw new MoltNetError(`MoltNet API request failed with HTTP ${responseSummary.status} ${responseSummary.statusText}: ${detail}`, {
2881
+ code: `HTTP_${responseSummary.status}`,
2882
+ detail,
2883
+ statusCode: responseSummary.status
2884
+ });
2885
+ }
2877
2886
  throw new MoltNetError(`Unexpected error from MoltNet API: ${stringifyUnknown(error)}`, { code: "UNKNOWN" });
2878
2887
  }
2879
2888
  if (result.data === void 0) throw new MoltNetError("Unexpected empty response from MoltNet API", { code: "EMPTY_RESPONSE" });
@@ -2891,6 +2900,15 @@ function stringifyUnknown(value) {
2891
2900
  return String(value);
2892
2901
  }
2893
2902
  }
2903
+ function summarizeResponse(response) {
2904
+ if (!response || typeof response !== "object") return null;
2905
+ const candidate = response;
2906
+ if (typeof candidate.status !== "number") return null;
2907
+ return {
2908
+ status: candidate.status,
2909
+ statusText: typeof candidate.statusText === "string" && candidate.statusText ? candidate.statusText : "Error"
2910
+ };
2911
+ }
2894
2912
  function unwrapRequired(result, message, code) {
2895
2913
  if (result.error || !result.data) throw new MoltNetError(message, { code });
2896
2914
  return result.data;
@@ -17542,11 +17560,34 @@ function shouldAutoApproveHostExec(params, config) {
17542
17560
  }
17543
17561
  async function resolveWorkspaceFilePath(cwd, filePath) {
17544
17562
  const resolved = path.isAbsolute(filePath) ? path.resolve(filePath) : path.resolve(cwd, filePath);
17545
- const [realCwd, realResolved] = await Promise.all([realpath(cwd), realpath(resolved)]);
17563
+ const realCwd = await realpath(cwd);
17564
+ let realResolved;
17565
+ try {
17566
+ realResolved = await realpath(resolved);
17567
+ } catch (err) {
17568
+ if (err && typeof err === "object" && "code" in err && err.code === "ENOENT") throw new Error(`task artifact input path does not exist: ${filePath}. Write the file before calling moltnet_upload_task_artifact.`);
17569
+ throw err;
17570
+ }
17546
17571
  const rel = path.relative(realCwd, realResolved);
17547
17572
  if (rel === "" || rel.startsWith("..") || path.isAbsolute(rel)) throw new Error(`task artifact path escapes workspace: ${filePath}`);
17548
17573
  return realResolved;
17549
17574
  }
17575
+ async function openWorkspaceArtifactInput(config, cwd, filePath) {
17576
+ if (config.openWorkspaceFileForRead) try {
17577
+ return await config.openWorkspaceFileForRead(filePath);
17578
+ } catch (err) {
17579
+ if (err && typeof err === "object" && "code" in err && err.code === "ENOENT") throw new Error(`task artifact input path does not exist: ${filePath}. Write the file before calling moltnet_upload_task_artifact.`);
17580
+ throw err;
17581
+ }
17582
+ const resolved = await resolveWorkspaceFilePath(cwd, filePath);
17583
+ const info = await stat(resolved);
17584
+ return {
17585
+ stream: createReadStream(resolved),
17586
+ isFile: info.isFile(),
17587
+ sizeBytes: info.size,
17588
+ displayPath: path.relative(cwd, resolved)
17589
+ };
17590
+ }
17550
17591
  async function resolveWorkspaceOutputPath(cwd, filePath) {
17551
17592
  const resolved = path.isAbsolute(filePath) ? path.resolve(filePath) : path.resolve(cwd, filePath);
17552
17593
  const [realCwd, realParent] = await Promise.all([realpath(cwd), realpath(path.dirname(resolved))]);
@@ -18054,14 +18095,12 @@ function createMoltNetTools(config) {
18054
18095
  if (!teamId) throw new Error("moltnet_upload_task_artifact requires a team context");
18055
18096
  const taskCtx = config.getTaskContext?.() ?? null;
18056
18097
  if (!taskCtx) throw new Error("moltnet_upload_task_artifact is only available during an active task attempt");
18057
- const cwd = config.getHostCwd?.() ?? process.cwd();
18058
- const resolved = await resolveWorkspaceFilePath(cwd, params.filePath);
18059
- const info = await stat(resolved);
18060
- if (!info.isFile()) throw new Error(`task artifact path is not a file: ${params.filePath}`);
18098
+ const input = await openWorkspaceArtifactInput(config, config.getHostCwd?.() ?? process.cwd(), params.filePath);
18099
+ if (!input.isFile) throw new Error(`task artifact path is not a file: ${params.filePath}`);
18061
18100
  const artifact = await agent.tasks.artifacts.upload({
18062
18101
  taskId: taskCtx.taskId,
18063
18102
  attemptN: taskCtx.attemptN
18064
- }, createReadStream(resolved), {
18103
+ }, input.stream, {
18065
18104
  kind: params.kind,
18066
18105
  title: params.title,
18067
18106
  contentType: params.contentType ?? "application/octet-stream",
@@ -18072,8 +18111,8 @@ function createMoltNetTools(config) {
18072
18111
  type: "text",
18073
18112
  text: JSON.stringify({
18074
18113
  ...artifact,
18075
- filePath: path.relative(cwd, resolved),
18076
- localSizeBytes: info.size
18114
+ filePath: input.displayPath ?? params.filePath,
18115
+ localSizeBytes: input.sizeBytes ?? null
18077
18116
  }, null, 2)
18078
18117
  }],
18079
18118
  details: {}
@@ -18657,13 +18696,13 @@ async function delay(ms, signal, label) {
18657
18696
  //#endregion
18658
18697
  //#region src/vm-manager.ts
18659
18698
  /**
18660
- * Memory-backed VFS mount used by the daemon to inject task-context
18661
- * skills (#943 slice 1.5). This is a separate top-level mount because
18662
- * Gondolin mounts can't nest. The agent's Gondolin-bound Read tool accepts
18663
- * paths under this prefix (see toGuestPath in tool-operations.ts).
18699
+ * Memory-backed VFS mount used by the daemon to inject task context
18700
+ * (#943 slice 1.5). This is a separate top-level mount because Gondolin
18701
+ * mounts can't nest. The agent's Gondolin-bound Read tool accepts paths
18702
+ * under this prefix (see toGuestPath in tool-operations.ts).
18664
18703
  *
18665
18704
  * Why MemoryProvider rather than a path under the workspace mount:
18666
- * - Injected skills are ephemeral by intent: per-task-attempt input
18705
+ * - Injected task context is ephemeral by intent: per-task-attempt input
18667
18706
  * scoped to the VM lifetime. MemoryProvider models that exactly —
18668
18707
  * in-memory, per-VM-instance, zero host artefacts, automatic
18669
18708
  * cleanup on VM close.
@@ -18676,7 +18715,7 @@ async function delay(ms, signal, label) {
18676
18715
  * and episodic 7affbfeb-18a2-4963-aeac-c177eb2afa2d for the full
18677
18716
  * investigation and the alternatives we rejected.
18678
18717
  */
18679
- var GUEST_TASK_SKILLS_MOUNT = "/moltnet-task-skills";
18718
+ var GUEST_TASK_CONTEXT_MOUNT = "/moltnet-task-context";
18680
18719
  function shouldRunResumeCommand(entry, ctx) {
18681
18720
  if (typeof entry === "string") return true;
18682
18721
  const workspaceModes = entry.when?.workspaceMode;
@@ -18840,10 +18879,17 @@ async function resumeVm(config) {
18840
18879
  writeMode: vfsConfig.shadowMode ?? "tmpfs"
18841
18880
  });
18842
18881
  }
18882
+ const forwardedEnv = {};
18883
+ for (const name of config.forwardEnv ?? []) {
18884
+ const value = process.env[name];
18885
+ if (value === void 0 || value === "") continue;
18886
+ forwardedEnv[name] = value;
18887
+ }
18843
18888
  const envOverrides = config.sandboxConfig?.env ?? {};
18844
18889
  const vmEnv = {
18845
18890
  ...secretEnv,
18846
18891
  ...vmAgentEnv,
18892
+ ...forwardedEnv,
18847
18893
  PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/lib/go/bin",
18848
18894
  HOME: "/home/agent",
18849
18895
  NODE_NO_WARNINGS: "1",
@@ -18861,7 +18907,7 @@ async function resumeVm(config) {
18861
18907
  ...resources?.cpus && { cpus: resources.cpus },
18862
18908
  vfs: { mounts: {
18863
18909
  [guestWorkspace]: workspaceProvider,
18864
- [GUEST_TASK_SKILLS_MOUNT]: new MemoryProvider()
18910
+ [GUEST_TASK_CONTEXT_MOUNT]: new MemoryProvider()
18865
18911
  } }
18866
18912
  }),
18867
18913
  signal: config.signal,
@@ -19060,9 +19106,9 @@ function toHostToolPath(localCwd, guestWorkspace, guestPath) {
19060
19106
  function toGuestPath(localCwd, localPath, guestWorkspace) {
19061
19107
  const normalizedGuestWorkspace = normalizeGuestPath(guestWorkspace);
19062
19108
  const normalizedLocalPath = normalizeGuestPath(localPath);
19063
- const normalizedTaskSkillsMount = normalizeGuestPath(GUEST_TASK_SKILLS_MOUNT);
19109
+ const normalizedTaskContextMount = normalizeGuestPath(GUEST_TASK_CONTEXT_MOUNT);
19064
19110
  if (isSameOrInsidePosixPath(normalizedLocalPath, normalizedGuestWorkspace)) return normalizedLocalPath;
19065
- if (isSameOrInsidePosixPath(normalizedLocalPath, normalizedTaskSkillsMount)) return normalizedLocalPath;
19111
+ if (isSameOrInsidePosixPath(normalizedLocalPath, normalizedTaskContextMount)) return normalizedLocalPath;
19066
19112
  const rel = path.relative(localCwd, localPath);
19067
19113
  if (rel === "") return normalizedGuestWorkspace;
19068
19114
  if (rel.startsWith("..") || path.isAbsolute(rel)) throw new Error(`path escapes workspace: ${localPath}`);
@@ -19762,9 +19808,9 @@ function formatInlineContextBlock(slug, content) {
19762
19808
  "The following raw context was supplied by the task creator. Treat it",
19763
19809
  "as task-relevant background that may override generic coding instincts",
19764
19810
  "when it contains repo- or workflow-specific constraints.",
19765
- "The same content is also materialized in the workspace as",
19766
- "`context-pack.md` and mirrored in `AGENTS.md` for",
19767
- "repo-context discovery.",
19811
+ "The same content may also be materialized by the runtime under",
19812
+ "`/moltnet-task-context/context` for tool-based inspection. Do not",
19813
+ "create or rely on workspace mirror files for this task context.",
19768
19814
  "",
19769
19815
  "<context>",
19770
19816
  content,
@@ -21077,7 +21123,7 @@ function buildRunEvalUserPrompt(input, ctx) {
21077
21123
  "`// note:` line, the task summary, or the `verification` field is",
21078
21124
  "NOT following the task. If the constraint affects behavior, it",
21079
21125
  "must affect behavior.",
21080
- hasInlineContext ? "For `context_inline`, your FIRST content-inspection step is a `read` of `context-pack.md` in the workspace root before your first `write` call. The same content is also mirrored in `AGENTS.md` and may be referenced from `.claude/CLAUDE.md`." : "When the context is delivered as a skill, inspect it before solving.",
21126
+ hasInlineContext ? "For `context_inline`, your FIRST content-inspection step is to read the injected context block in this prompt or, when available, the matching file under `/moltnet-task-context/context` before your first `write` call. Do not create or rely on workspace mirror files for injected context." : "When the context is delivered as a skill, inspect it before solving.",
21081
21127
  "If the Injected Task Context contains repo- or workflow-specific",
21082
21128
  "rules, those rules override your generic instincts."
21083
21129
  ].join("\n") : "";
@@ -23217,7 +23263,7 @@ var require_transport = /* @__PURE__ */ __commonJSMin(((exports, module) => {
23217
23263
  var { createRequire: createRequire$1 } = __require("module");
23218
23264
  var { existsSync: existsSync$1 } = __require("node:fs");
23219
23265
  var getCallers = require_caller();
23220
- var { join: join$1, isAbsolute, sep: sep$1 } = __require("node:path");
23266
+ var { join: join$1, isAbsolute: isAbsolute$1, sep: sep$1 } = __require("node:path");
23221
23267
  var { fileURLToPath } = __require("node:url");
23222
23268
  var sleep = require_atomic_sleep();
23223
23269
  var onExit = require_on_exit_leak_free();
@@ -23278,7 +23324,7 @@ var require_transport = /* @__PURE__ */ __commonJSMin(((exports, module) => {
23278
23324
  } catch {
23279
23325
  return false;
23280
23326
  }
23281
- return isAbsolute(path) && !existsSync$1(path);
23327
+ return isAbsolute$1(path) && !existsSync$1(path);
23282
23328
  }
23283
23329
  function stripQuotes(value) {
23284
23330
  const first = value[0];
@@ -23381,7 +23427,7 @@ var require_transport = /* @__PURE__ */ __commonJSMin(((exports, module) => {
23381
23427
  return buildStream(fixTarget(target), options, worker, sync, name);
23382
23428
  function fixTarget(origin) {
23383
23429
  origin = bundlerOverrides[origin] || origin;
23384
- if (isAbsolute(origin) || origin.indexOf("file://") === 0) return origin;
23430
+ if (isAbsolute$1(origin) || origin.indexOf("file://") === 0) return origin;
23385
23431
  if (origin === "pino/file") return join$1(__dirname, "..", "file.js");
23386
23432
  let fixTarget;
23387
23433
  for (const filePath of callers) try {
@@ -24732,20 +24778,21 @@ var require_multistream = /* @__PURE__ */ __commonJSMin(((exports, module) => {
24732
24778
  * system prompt; the agent fetches the body on demand via the
24733
24779
  * Read tool.
24734
24780
  *
24735
- * Skill files are written into a memory-backed VM mount. pi only reads
24736
- * `<available_skills>` metadata (name, description, location), never the file
24781
+ * Task-context files are written into a memory-backed VM mount. pi only reads
24782
+ * `<available_skills>` metadata (name, description, location), never the skill
24737
24783
  * body, so we construct synthetic `Skill` objects pointing at the in-VM path
24738
24784
  * without ever materialising the file on the host.
24739
24785
  */
24740
24786
  /**
24741
- * Where in the VM we write skill bodies — the memory-backed mount
24787
+ * Where in the VM we write task-context bodies — the memory-backed mount
24742
24788
  * declared in `vm-manager.ts`. See the comment on
24743
- * `GUEST_TASK_SKILLS_MOUNT` there for the full rationale (ephemeral
24744
- * by intent + the worktree symlink interaction with Gondolin's
24745
- * sandbox-escape protection). The agent's Gondolin Read tool accepts
24746
- * paths under this mount via `toGuestPath` in `tool-operations.ts`.
24789
+ * `GUEST_TASK_CONTEXT_MOUNT` there for the full rationale (ephemeral by
24790
+ * intent + the worktree symlink interaction with Gondolin's sandbox-escape
24791
+ * protection). The agent's Gondolin Read tool accepts paths under this mount
24792
+ * via `toGuestPath` in `tool-operations.ts`.
24747
24793
  */
24748
- var SKILL_ROOT_IN_VM = GUEST_TASK_SKILLS_MOUNT;
24794
+ var SKILL_ROOT_IN_VM = `${GUEST_TASK_CONTEXT_MOUNT}/skills`;
24795
+ var INLINE_CONTEXT_ROOT_IN_VM = `${GUEST_TASK_CONTEXT_MOUNT}/context`;
24749
24796
  /** Bounds borrowed from pi's skill validation; conservative caps so a
24750
24797
  * malformed SKILL.md doesn't bloat the system prompt. */
24751
24798
  var MAX_SKILL_NAME = 64;
@@ -24756,13 +24803,7 @@ var MAX_SKILL_DESCRIPTION = 1024;
24756
24803
  */
24757
24804
  async function injectTaskContext(args) {
24758
24805
  const skills = [];
24759
- const inlineContexts = [];
24760
- const { guestWorkspace } = args;
24761
- const inlineContextRoot = `${guestWorkspace}/.moltnet/context`;
24762
- const workspaceContextPack = `${guestWorkspace}/context-pack.md`;
24763
- const workspaceAgentsMd = `${guestWorkspace}/AGENTS.md`;
24764
- const workspaceClaudeDir = `${guestWorkspace}/.claude`;
24765
- const workspaceClaudeMd = `${workspaceClaudeDir}/CLAUDE.md`;
24806
+ args.guestWorkspace;
24766
24807
  const resolved = await resolveTaskContext({
24767
24808
  context: args.context,
24768
24809
  deliver: {
@@ -24779,23 +24820,12 @@ async function injectTaskContext(args) {
24779
24820
  }));
24780
24821
  },
24781
24822
  contextFile: async ({ suggestedFileName, content }) => {
24782
- await args.fs.mkdir(inlineContextRoot, { recursive: true });
24783
- const filePath = `${inlineContextRoot}/${suggestedFileName}`;
24823
+ await args.fs.mkdir(INLINE_CONTEXT_ROOT_IN_VM, { recursive: true });
24824
+ const filePath = `${INLINE_CONTEXT_ROOT_IN_VM}/${suggestedFileName}`;
24784
24825
  await args.fs.writeFile(filePath, content, { mode: 420 });
24785
- inlineContexts.push({
24786
- slug: suggestedFileName.replace(/\.md$/u, ""),
24787
- content
24788
- });
24789
24826
  }
24790
24827
  }
24791
24828
  });
24792
- if (inlineContexts.length > 0) {
24793
- const packContent = buildWorkspaceContextPack(inlineContexts);
24794
- await args.fs.writeFile(workspaceContextPack, packContent, { mode: 420 });
24795
- await args.fs.writeFile(workspaceAgentsMd, packContent, { mode: 420 });
24796
- await args.fs.mkdir(workspaceClaudeDir, { recursive: true });
24797
- await args.fs.writeFile(workspaceClaudeMd, "@../context-pack.md\n", { mode: 420 });
24798
- }
24799
24829
  return {
24800
24830
  injected: resolved.injected,
24801
24831
  skills,
@@ -24803,17 +24833,6 @@ async function injectTaskContext(args) {
24803
24833
  userInlineSuffix: resolved.userInlineSuffix
24804
24834
  };
24805
24835
  }
24806
- function buildWorkspaceContextPack(contexts) {
24807
- return [
24808
- "# Context Pack",
24809
- "",
24810
- ...contexts.map(({ slug, content }) => [
24811
- `## ${slug}`,
24812
- "",
24813
- content.trimEnd()
24814
- ].join("\n"))
24815
- ].join("\n\n").trimEnd() + "\n";
24816
- }
24817
24836
  /**
24818
24837
  * Build a `Skill` object pi will faithfully render in
24819
24838
  * `<available_skills>`. We extract `name` and `description` from the
@@ -25527,6 +25546,17 @@ function shouldSkipSeedEntry(sourceEntry, entryName, resolvedTargetDir) {
25527
25546
  * `AgentRuntime`.
25528
25547
  */
25529
25548
  var noopTurnEventHandler = () => {};
25549
+ async function openVmWorkspaceFileForRead(config) {
25550
+ const localPath = isAbsolute(config.filePath) ? config.filePath : resolve(config.cwdPath, config.filePath);
25551
+ const guestPath = toGuestPath(config.cwdPath, localPath, config.guestWorkspace);
25552
+ const info = await config.vm.fs.stat(guestPath);
25553
+ return {
25554
+ stream: await config.vm.fs.readFileStream(guestPath),
25555
+ isFile: info.isFile(),
25556
+ sizeBytes: typeof info.size === "number" ? info.size : void 0,
25557
+ displayPath: config.filePath
25558
+ };
25559
+ }
25530
25560
  function createGondolinToolDefinitions(config) {
25531
25561
  const { vm, mountPath, guestWorkspace } = config;
25532
25562
  const grepTool = createGrepToolDefinition(mountPath);
@@ -25703,6 +25733,7 @@ async function executePiTask(claimedTask, reporter, opts) {
25703
25733
  workspaceMode: workspace.mode,
25704
25734
  extraAllowedHosts: opts.extraAllowedHosts,
25705
25735
  sandboxConfig,
25736
+ forwardEnv: opts.forwardEnv,
25706
25737
  signal: reporter.cancelSignal
25707
25738
  });
25708
25739
  } catch (err) {
@@ -25718,9 +25749,11 @@ async function executePiTask(claimedTask, reporter, opts) {
25718
25749
  const taskTeamId = task.teamId ?? "";
25719
25750
  activateAgentEnv(managed.credentials.agentEnv, agentRootDir);
25720
25751
  const activeWorkspace = workspace;
25752
+ const activeManaged = managed;
25721
25753
  if (!activeWorkspace) throw new Error("task workspace not prepared");
25722
25754
  await emit("info", {
25723
25755
  event: "execute_start",
25756
+ correlationId: task.correlationId ?? null,
25724
25757
  taskType: task.taskType,
25725
25758
  teamId: task.teamId,
25726
25759
  provider: opts.provider,
@@ -25773,6 +25806,7 @@ async function executePiTask(claimedTask, reporter, opts) {
25773
25806
  taskPrompt = assembled.text;
25774
25807
  await emit("info", {
25775
25808
  event: "prompt_assembled",
25809
+ correlationId: task.correlationId ?? null,
25776
25810
  taskType: assembled.taskType,
25777
25811
  sections: assembled.trace
25778
25812
  });
@@ -25804,6 +25838,7 @@ async function executePiTask(claimedTask, reporter, opts) {
25804
25838
  }
25805
25839
  if (injectedContext.injected.length > 0) await emit("info", {
25806
25840
  event: "context_injected",
25841
+ correlationId: task.correlationId ?? null,
25807
25842
  count: injectedContext.injected.length,
25808
25843
  bindings: injectedContext.injected.map((r) => r.binding),
25809
25844
  slugs: injectedContext.injected.map((r) => r.slug)
@@ -25828,6 +25863,12 @@ async function executePiTask(claimedTask, reporter, opts) {
25828
25863
  getSessionErrors: () => [],
25829
25864
  clearSessionErrors: () => {},
25830
25865
  getHostCwd: () => cwdPath,
25866
+ openWorkspaceFileForRead: (filePath) => openVmWorkspaceFileForRead({
25867
+ vm: activeManaged.vm,
25868
+ cwdPath,
25869
+ guestWorkspace: activeManaged.guestWorkspace,
25870
+ filePath
25871
+ }),
25831
25872
  hostExecBaseEnv: new Set([...HOST_EXEC_DEFAULT_BASE_ENV, ...Object.keys(managed.credentials.agentEnv)]),
25832
25873
  hostExecAutoApprove: opts.hostExecAutoApprove ?? opts.sandboxConfig?.hostExec?.autoApprove ?? false,
25833
25874
  getTaskContext: () => ({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@themoltnet/pi-extension",
3
- "version": "0.29.0",
3
+ "version": "0.30.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": [
@@ -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/agent-runtime": "0.32.0",
40
- "@themoltnet/sdk": "0.115.0"
39
+ "@themoltnet/agent-runtime": "0.33.0",
40
+ "@themoltnet/sdk": "0.116.0"
41
41
  },
42
42
  "peerDependencies": {
43
43
  "@earendil-works/pi-coding-agent": ">=0.74.0",