@themoltnet/pi-extension 0.28.1 → 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
@@ -762,6 +776,13 @@ declare const Task: Type.TObject<{
762
776
  commit_sha: Type.TOptional<Type.TString>;
763
777
  snapshot_cid: Type.TOptional<Type.TString>;
764
778
  }>>;
779
+ artifact: Type.TOptional<Type.TObject<{
780
+ cid: Type.TString;
781
+ attemptN: Type.TInteger;
782
+ kind: Type.TOptional<Type.TString>;
783
+ title: Type.TOptional<Type.TString>;
784
+ contentType: Type.TOptional<Type.TString>;
785
+ }>>;
765
786
  }>>;
766
787
  correlationId: Type.TUnion<[Type.TString, Type.TNull]>;
767
788
  proposedByAgentId: Type.TUnion<[Type.TString, Type.TNull]>;
@@ -965,6 +986,14 @@ export declare interface VmConfig {
965
986
  extraAllowedHosts?: string[];
966
987
  /** Full sandbox config (vfs shadows, env overrides). */
967
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[];
968
997
  /** Abort resume/setup work, closing any live VM owned by resumeVm. */
969
998
  signal?: AbortSignal;
970
999
  }
package/dist/index.js CHANGED
@@ -1,13 +1,14 @@
1
1
  import { createRequire } from "node:module";
2
2
  import { execFileSync } from "node:child_process";
3
- import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, statSync } from "node:fs";
4
- import path, { join, relative, sep } from "node:path";
3
+ import { cpSync, createReadStream, createWriteStream, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, statSync } from "node:fs";
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";
8
8
  import crypto, { createHash as createHash$1 } from "crypto";
9
- import { readFile } from "node:fs/promises";
9
+ import { readFile, realpath, stat } from "node:fs/promises";
10
10
  import { homedir } from "node:os";
11
+ import { pipeline } from "node:stream/promises";
11
12
  import { Type, getModel } from "@earendil-works/pi-ai";
12
13
  import { MemoryProvider, RealFSProvider, ShadowProvider, VM, VmCheckpoint, createHttpHooks, createShadowPathPredicate, ensureImageSelector, loadGuestAssets } from "@earendil-works/gondolin";
13
14
  import { parseEnv } from "node:util";
@@ -2308,6 +2309,55 @@ var claimTask = (options) => (options.client ?? client).post({
2308
2309
  }
2309
2310
  });
2310
2311
  /**
2312
+ * List task artifact metadata for the current team.
2313
+ */
2314
+ var listTaskArtifacts = (options) => (options.client ?? client).get({
2315
+ security: [
2316
+ {
2317
+ scheme: "bearer",
2318
+ type: "http"
2319
+ },
2320
+ {
2321
+ name: "X-Moltnet-Session-Token",
2322
+ type: "apiKey"
2323
+ },
2324
+ {
2325
+ in: "cookie",
2326
+ name: "ory_kratos_session",
2327
+ type: "apiKey"
2328
+ }
2329
+ ],
2330
+ url: "/tasks/{taskId}/artifacts",
2331
+ ...options
2332
+ });
2333
+ /**
2334
+ * Upload immutable content-addressed artifact content for a task attempt.
2335
+ */
2336
+ var uploadTaskArtifact = (options) => (options.client ?? client).put({
2337
+ bodySerializer: null,
2338
+ security: [
2339
+ {
2340
+ scheme: "bearer",
2341
+ type: "http"
2342
+ },
2343
+ {
2344
+ name: "X-Moltnet-Session-Token",
2345
+ type: "apiKey"
2346
+ },
2347
+ {
2348
+ in: "cookie",
2349
+ name: "ory_kratos_session",
2350
+ type: "apiKey"
2351
+ }
2352
+ ],
2353
+ url: "/tasks/{taskId}/attempts/{attemptN}/artifacts",
2354
+ ...options,
2355
+ headers: {
2356
+ "Content-Type": "application/octet-stream",
2357
+ ...options.headers
2358
+ }
2359
+ });
2360
+ /**
2311
2361
  * List teams the caller belongs to.
2312
2362
  */
2313
2363
  var listTeams = (options) => (options?.client ?? client).get({
@@ -2824,6 +2874,15 @@ function unwrapResult(result) {
2824
2874
  networkError.stack = error.stack;
2825
2875
  throw networkError;
2826
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
+ }
2827
2886
  throw new MoltNetError(`Unexpected error from MoltNet API: ${stringifyUnknown(error)}`, { code: "UNKNOWN" });
2828
2887
  }
2829
2888
  if (result.data === void 0) throw new MoltNetError("Unexpected empty response from MoltNet API", { code: "EMPTY_RESPONSE" });
@@ -2841,6 +2900,15 @@ function stringifyUnknown(value) {
2841
2900
  return String(value);
2842
2901
  }
2843
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
+ }
2844
2912
  function unwrapRequired(result, message, code) {
2845
2913
  if (result.error || !result.data) throw new MoltNetError(message, { code });
2846
2914
  return result.data;
@@ -10073,6 +10141,97 @@ var VerificationRecord = _Object_({
10073
10141
  $id: "VerificationRecord",
10074
10142
  additionalProperties: false
10075
10143
  });
10144
+ _Object_({
10145
+ artifacts: _Array_(_Object_({
10146
+ id: String$1({ format: "uuid" }),
10147
+ teamId: String$1({ format: "uuid" }),
10148
+ taskId: String$1({ format: "uuid" }),
10149
+ attemptN: Integer({ minimum: 1 }),
10150
+ kind: String$1({
10151
+ minLength: 1,
10152
+ maxLength: 100
10153
+ }),
10154
+ title: String$1({
10155
+ minLength: 1,
10156
+ maxLength: 255
10157
+ }),
10158
+ contentType: String$1({
10159
+ minLength: 1,
10160
+ maxLength: 200
10161
+ }),
10162
+ contentEncoding: Union([String$1({
10163
+ minLength: 1,
10164
+ maxLength: 100
10165
+ }), Null()]),
10166
+ sizeBytes: Integer({ minimum: 0 }),
10167
+ cid: String$1({
10168
+ minLength: 1,
10169
+ maxLength: 100
10170
+ }),
10171
+ createdByAgentId: String$1({ format: "uuid" }),
10172
+ expiresAt: Union([String$1({ format: "date-time" }), Null()]),
10173
+ createdAt: String$1({ format: "date-time" })
10174
+ }, { $id: "TaskArtifact" })),
10175
+ nextCursor: Union([String$1({ minLength: 1 }), Null()])
10176
+ }, { $id: "TaskArtifactList" });
10177
+ _Object_({
10178
+ limit: Optional(Integer({
10179
+ minimum: 1,
10180
+ maximum: 100
10181
+ })),
10182
+ cursor: Optional(String$1({ minLength: 1 }))
10183
+ }, {
10184
+ $id: "ListTaskArtifactsQuery",
10185
+ additionalProperties: false
10186
+ });
10187
+ _Object_({
10188
+ kind: String$1({
10189
+ minLength: 1,
10190
+ maxLength: 100
10191
+ }),
10192
+ title: String$1({
10193
+ minLength: 1,
10194
+ maxLength: 255
10195
+ }),
10196
+ contentType: Optional(String$1({
10197
+ minLength: 1,
10198
+ maxLength: 200
10199
+ })),
10200
+ contentEncoding: Optional(String$1({
10201
+ minLength: 1,
10202
+ maxLength: 100
10203
+ }))
10204
+ }, {
10205
+ $id: "UploadTaskArtifactQuery",
10206
+ additionalProperties: false
10207
+ });
10208
+ String$1({
10209
+ $id: "TaskArtifactContent",
10210
+ description: "Task artifact content stream.",
10211
+ format: "binary"
10212
+ });
10213
+ _Object_({ taskId: String$1({ format: "uuid" }) }, {
10214
+ $id: "TaskArtifactTaskParams",
10215
+ additionalProperties: false
10216
+ });
10217
+ _Object_({
10218
+ taskId: String$1({ format: "uuid" }),
10219
+ attemptN: Integer({ minimum: 1 })
10220
+ }, {
10221
+ $id: "TaskArtifactAttemptParams",
10222
+ additionalProperties: false
10223
+ });
10224
+ _Object_({
10225
+ taskId: String$1({ format: "uuid" }),
10226
+ attemptN: Integer({ minimum: 1 }),
10227
+ cid: String$1({
10228
+ minLength: 1,
10229
+ maxLength: 100
10230
+ })
10231
+ }, {
10232
+ $id: "TaskArtifactContentParams",
10233
+ additionalProperties: false
10234
+ });
10076
10235
  //#endregion
10077
10236
  //#region ../../node_modules/.pnpm/multiformats@13.4.2/node_modules/multiformats/dist/src/codecs/json.js
10078
10237
  var textEncoder$2 = new TextEncoder();
@@ -10339,6 +10498,10 @@ var FreeformArtifact = _Object_({
10339
10498
  description: Optional(String$1({ minLength: 1 })),
10340
10499
  url: Optional(String$1({ minLength: 1 })),
10341
10500
  path: Optional(String$1({ minLength: 1 })),
10501
+ cid: Optional(String$1({ minLength: 1 })),
10502
+ contentType: Optional(String$1({ minLength: 1 })),
10503
+ contentEncoding: Optional(String$1({ minLength: 1 })),
10504
+ sizeBytes: Optional(Integer({ minimum: 0 })),
10342
10505
  body: Optional(String$1({ maxLength: 65536 }))
10343
10506
  }, {
10344
10507
  $id: "FreeformArtifact",
@@ -13818,7 +13981,23 @@ var TaskRef = _Object_({
13818
13981
  url: Optional(String$1()),
13819
13982
  commit_sha: Optional(String$1()),
13820
13983
  snapshot_cid: Optional(Cid)
13821
- }))
13984
+ })),
13985
+ artifact: Optional(_Object_({
13986
+ cid: Cid,
13987
+ attemptN: Integer({ minimum: 1 }),
13988
+ kind: Optional(String$1({
13989
+ minLength: 1,
13990
+ maxLength: 100
13991
+ })),
13992
+ title: Optional(String$1({
13993
+ minLength: 1,
13994
+ maxLength: 255
13995
+ })),
13996
+ contentType: Optional(String$1({
13997
+ minLength: 1,
13998
+ maxLength: 200
13999
+ }))
14000
+ }, { additionalProperties: false }))
13822
14001
  }, {
13823
14002
  $id: "TaskRef",
13824
14003
  additionalProperties: false
@@ -14163,6 +14342,7 @@ var TaskBuilder = class {
14163
14342
  message: "reference is missing required outputCid"
14164
14343
  }]);
14165
14344
  ref = {
14345
+ ...s,
14166
14346
  taskId: s.taskId ?? null,
14167
14347
  outputCid: s.outputCid,
14168
14348
  role
@@ -14172,6 +14352,60 @@ var TaskBuilder = class {
14172
14352
  return this;
14173
14353
  }
14174
14354
  /**
14355
+ * Add a reference to a persistent task artifact while retaining the accepted
14356
+ * output CID as the provenance anchor.
14357
+ *
14358
+ * @param source - A result reader, raw artifact reference, or `TaskRef`.
14359
+ * @param role - The role the referenced artifact plays.
14360
+ * @returns This builder, for chaining.
14361
+ * @throws {TaskBuildError} when output or artifact CID is missing.
14362
+ */
14363
+ artifactReference(source, role) {
14364
+ let ref;
14365
+ if ("artifactRef" in source && typeof source.artifactRef === "function") ref = source.artifactRef(role);
14366
+ else if ("artifact" in source && source.artifact?.cid) {
14367
+ if (typeof source.artifact.attemptN !== "number" || !Number.isInteger(source.artifact.attemptN) || source.artifact.attemptN < 1) throw new TaskBuildError([{
14368
+ field: "references/artifact/attemptN",
14369
+ message: "artifact reference is missing required attemptN"
14370
+ }]);
14371
+ ref = {
14372
+ ...source,
14373
+ role
14374
+ };
14375
+ } else {
14376
+ const s = source;
14377
+ const errors = [];
14378
+ if (!s.outputCid) errors.push({
14379
+ field: "references/outputCid",
14380
+ message: "reference is missing required outputCid"
14381
+ });
14382
+ if (!s.artifactCid) errors.push({
14383
+ field: "references/artifact/cid",
14384
+ message: "artifact reference is missing required cid"
14385
+ });
14386
+ if (typeof s.attemptN !== "number" || !Number.isInteger(s.attemptN) || s.attemptN < 1) errors.push({
14387
+ field: "references/artifact/attemptN",
14388
+ message: "artifact reference is missing required attemptN"
14389
+ });
14390
+ if (errors.length > 0) throw new TaskBuildError(errors);
14391
+ const attemptN = s.attemptN;
14392
+ ref = {
14393
+ taskId: s.taskId ?? null,
14394
+ outputCid: s.outputCid,
14395
+ role,
14396
+ artifact: {
14397
+ cid: s.artifactCid,
14398
+ attemptN,
14399
+ ...s.kind ? { kind: s.kind } : {},
14400
+ ...s.title ? { title: s.title } : {},
14401
+ ...s.contentType ? { contentType: s.contentType } : {}
14402
+ }
14403
+ };
14404
+ }
14405
+ this.refs.push(ref);
14406
+ return this;
14407
+ }
14408
+ /**
14175
14409
  * Set the owning team (required by the wire schema).
14176
14410
  *
14177
14411
  * @param teamId - Team UUID.
@@ -14549,6 +14783,34 @@ var TaskResultReader = class {
14549
14783
  role
14550
14784
  };
14551
14785
  }
14786
+ /**
14787
+ * Build a `TaskRef` that anchors a downstream task to this accepted output
14788
+ * and points at one persistent task artifact by CID.
14789
+ *
14790
+ * @param filter - Artifact object or a filter resolved against output artifacts.
14791
+ * @param role - The role this artifact plays in the downstream task.
14792
+ * @returns A `TaskRef` with `artifact.cid` populated.
14793
+ * @throws {TaskResultError} if no matching artifact has a CID.
14794
+ */
14795
+ artifactRef(filter, role) {
14796
+ const artifact = typeof filter === "object" && "cid" in filter && "kind" in filter ? filter : this.artifact(filter);
14797
+ if (!artifact?.cid) throw new TaskResultError([{
14798
+ field: "artifacts/cid",
14799
+ message: "no matching artifact with a cid"
14800
+ }]);
14801
+ return {
14802
+ taskId: this.taskId,
14803
+ outputCid: this.outputCid,
14804
+ role,
14805
+ artifact: {
14806
+ cid: artifact.cid,
14807
+ attemptN: this.accepted.attemptN,
14808
+ kind: artifact.kind,
14809
+ title: artifact.title,
14810
+ ...artifact.contentType ? { contentType: artifact.contentType } : {}
14811
+ }
14812
+ };
14813
+ }
14552
14814
  };
14553
14815
  /**
14554
14816
  * Validate and construct a {@link TaskResultReader} from a task and its
@@ -14573,6 +14835,63 @@ function createTasksNamespace(context) {
14573
14835
  auth
14574
14836
  }));
14575
14837
  },
14838
+ artifacts: {
14839
+ async upload(path, body, query, options) {
14840
+ return unwrapResult(await uploadTaskArtifact({
14841
+ auth,
14842
+ body,
14843
+ client,
14844
+ duplex: "half",
14845
+ headers: {
14846
+ ...requiredTeamHeaders(options),
14847
+ "content-type": "application/octet-stream"
14848
+ },
14849
+ path,
14850
+ query
14851
+ }));
14852
+ },
14853
+ async list(taskId, options, query) {
14854
+ return unwrapResult(await listTaskArtifacts({
14855
+ client,
14856
+ auth,
14857
+ headers: requiredTeamHeaders(options),
14858
+ path: { taskId },
14859
+ query
14860
+ })).artifacts;
14861
+ },
14862
+ async listPage(taskId, query, options) {
14863
+ return unwrapResult(await listTaskArtifacts({
14864
+ client,
14865
+ auth,
14866
+ headers: requiredTeamHeaders(options),
14867
+ path: { taskId },
14868
+ query
14869
+ }));
14870
+ },
14871
+ async download(path, options) {
14872
+ const result = await client.request({
14873
+ auth,
14874
+ headers: requiredTeamHeaders(options),
14875
+ method: "GET",
14876
+ parseAs: "stream",
14877
+ path,
14878
+ security: [{
14879
+ scheme: "bearer",
14880
+ type: "http"
14881
+ }],
14882
+ url: "/tasks/{taskId}/attempts/{attemptN}/artifacts/{cid}/content"
14883
+ });
14884
+ const normalizedStream = normalizeDownloadStream(unwrapResult(result));
14885
+ if (normalizedStream) return {
14886
+ artifactId: header(result.response, "x-moltnet-task-artifact-id"),
14887
+ cid: header(result.response, "x-moltnet-task-artifact-cid"),
14888
+ contentEncoding: header(result.response, "x-moltnet-task-artifact-content-encoding"),
14889
+ contentType: header(result.response, "x-moltnet-task-artifact-content-type"),
14890
+ stream: normalizedStream
14891
+ };
14892
+ throw new MoltNetError("Unexpected task artifact download response stream", { code: "INVALID_RESPONSE" });
14893
+ }
14894
+ },
14576
14895
  async list(query, options) {
14577
14896
  return unwrapResult(await listTasks({
14578
14897
  client,
@@ -14741,6 +15060,33 @@ function createTasksNamespace(context) {
14741
15060
  }
14742
15061
  };
14743
15062
  }
15063
+ function header(response, name) {
15064
+ const value = response?.headers.get(name) ?? null;
15065
+ return value === "" ? null : value;
15066
+ }
15067
+ function normalizeDownloadStream(stream) {
15068
+ if (isAsyncIterable(stream)) return stream;
15069
+ if (isReadableStream(stream)) return readableStreamToAsyncIterable(stream);
15070
+ return null;
15071
+ }
15072
+ function isAsyncIterable(value) {
15073
+ return typeof value === "object" && value !== null && Symbol.asyncIterator in value && typeof value[Symbol.asyncIterator] === "function";
15074
+ }
15075
+ function isReadableStream(value) {
15076
+ return typeof value === "object" && value !== null && "getReader" in value && typeof value.getReader === "function";
15077
+ }
15078
+ async function* readableStreamToAsyncIterable(stream) {
15079
+ const reader = stream.getReader();
15080
+ try {
15081
+ while (true) {
15082
+ const result = await reader.read();
15083
+ if (result.done) return;
15084
+ yield result.value;
15085
+ }
15086
+ } finally {
15087
+ reader.releaseLock();
15088
+ }
15089
+ }
14744
15090
  //#endregion
14745
15091
  //#region ../sdk/src/namespaces/teams.ts
14746
15092
  function createTeamsNamespace(context) {
@@ -17212,6 +17558,43 @@ function shouldAutoApproveHostExec(params, config) {
17212
17558
  if (!Array.isArray(policy)) return false;
17213
17559
  return policy.some((rule) => hostExecMatchesAutoApproveRule(params, rule));
17214
17560
  }
17561
+ async function resolveWorkspaceFilePath(cwd, filePath) {
17562
+ const resolved = path.isAbsolute(filePath) ? path.resolve(filePath) : path.resolve(cwd, filePath);
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
+ }
17571
+ const rel = path.relative(realCwd, realResolved);
17572
+ if (rel === "" || rel.startsWith("..") || path.isAbsolute(rel)) throw new Error(`task artifact path escapes workspace: ${filePath}`);
17573
+ return realResolved;
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
+ }
17591
+ async function resolveWorkspaceOutputPath(cwd, filePath) {
17592
+ const resolved = path.isAbsolute(filePath) ? path.resolve(filePath) : path.resolve(cwd, filePath);
17593
+ const [realCwd, realParent] = await Promise.all([realpath(cwd), realpath(path.dirname(resolved))]);
17594
+ const rel = path.relative(realCwd, realParent);
17595
+ if (rel.startsWith("..") || path.isAbsolute(rel)) throw new Error(`task artifact output path escapes workspace: ${filePath}`);
17596
+ return resolved;
17597
+ }
17215
17598
  /**
17216
17599
  * Expand the `taskFilter` shorthand on the diary list/search tools into
17217
17600
  * the matching `task:*` provenance tags emitted by `moltnet_create_entry`
@@ -17696,6 +18079,125 @@ function createMoltNetTools(config) {
17696
18079
  };
17697
18080
  }
17698
18081
  });
18082
+ const uploadTaskArtifact = defineTool({
18083
+ name: "moltnet_upload_task_artifact",
18084
+ label: "Upload MoltNet Task Artifact",
18085
+ description: "Upload a file from the current task workspace as an immutable task artifact. Only available during an active task attempt; the tool attaches the artifact to the active taskId/attemptN and returns metadata including cid, sizeBytes, kind, and title. Use this for large logs, reports, build outputs, screenshots, generated files, or other bytes that should be referenced by CID instead of pasted into structured task output.",
18086
+ parameters: Type.Object({
18087
+ filePath: Type.String({ description: "Path to a file under the current task workspace. Relative paths are resolved from the workspace root." }),
18088
+ kind: Type.String({ description: "Artifact category, e.g. log, report, patch, screenshot, bundle, dataset, trace." }),
18089
+ title: Type.String({ description: "Human-readable artifact title, usually the file name." }),
18090
+ contentType: Type.Optional(Type.String({ description: "MIME type. Defaults to application/octet-stream when omitted." })),
18091
+ contentEncoding: Type.Optional(Type.String({ description: "Optional content encoding if the file is already encoded, e.g. gzip." }))
18092
+ }),
18093
+ async execute(_id, params) {
18094
+ const { agent, teamId } = ensureConnected(config);
18095
+ if (!teamId) throw new Error("moltnet_upload_task_artifact requires a team context");
18096
+ const taskCtx = config.getTaskContext?.() ?? null;
18097
+ if (!taskCtx) throw new Error("moltnet_upload_task_artifact is only available during an active task attempt");
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}`);
18100
+ const artifact = await agent.tasks.artifacts.upload({
18101
+ taskId: taskCtx.taskId,
18102
+ attemptN: taskCtx.attemptN
18103
+ }, input.stream, {
18104
+ kind: params.kind,
18105
+ title: params.title,
18106
+ contentType: params.contentType ?? "application/octet-stream",
18107
+ contentEncoding: params.contentEncoding
18108
+ }, { teamId });
18109
+ return {
18110
+ content: [{
18111
+ type: "text",
18112
+ text: JSON.stringify({
18113
+ ...artifact,
18114
+ filePath: input.displayPath ?? params.filePath,
18115
+ localSizeBytes: input.sizeBytes ?? null
18116
+ }, null, 2)
18117
+ }],
18118
+ details: {}
18119
+ };
18120
+ }
18121
+ });
18122
+ const listTaskArtifacts = defineTool({
18123
+ name: "moltnet_list_task_artifacts",
18124
+ label: "List MoltNet Task Artifacts",
18125
+ description: "List immutable artifacts attached to a task, including each artifact CID, attempt number, kind, title, content type, size, uploader, and creation time. Use this when judging or continuing work that references task artifacts.",
18126
+ parameters: Type.Object({
18127
+ taskId: Type.Optional(Type.String({ description: "Task ID. Defaults to the active task when running inside a task attempt." })),
18128
+ limit: Type.Optional(Type.Integer({
18129
+ minimum: 1,
18130
+ maximum: 100,
18131
+ description: "Maximum artifacts to return. Defaults to the server page size."
18132
+ })),
18133
+ cursor: Type.Optional(Type.String({ description: "Pagination cursor returned by a previous moltnet_list_task_artifacts call." }))
18134
+ }),
18135
+ async execute(_id, params) {
18136
+ const { agent, teamId } = ensureConnected(config);
18137
+ if (!teamId) throw new Error("moltnet_list_task_artifacts requires a team context");
18138
+ const taskId = params.taskId ?? config.getTaskContext?.()?.taskId;
18139
+ if (!taskId) throw new Error("moltnet_list_task_artifacts requires taskId outside an active task");
18140
+ const page = await agent.tasks.artifacts.listPage(taskId, {
18141
+ cursor: params.cursor,
18142
+ limit: params.limit
18143
+ }, { teamId });
18144
+ return {
18145
+ content: [{
18146
+ type: "text",
18147
+ text: JSON.stringify(page, null, 2)
18148
+ }],
18149
+ details: {}
18150
+ };
18151
+ }
18152
+ });
18153
+ const downloadTaskArtifact = defineTool({
18154
+ name: "moltnet_download_task_artifact",
18155
+ label: "Download MoltNet Task Artifact",
18156
+ description: "Download immutable task artifact bytes by taskId, attemptN, and CID into a new file in the current task workspace. Use moltnet_list_task_artifacts first to choose the correct CID for referenced task inputs.",
18157
+ parameters: Type.Object({
18158
+ taskId: Type.Optional(Type.String({ description: "Task ID. Defaults to the active task when running inside a task attempt." })),
18159
+ attemptN: Type.Integer({
18160
+ minimum: 1,
18161
+ description: "Attempt number that produced the artifact."
18162
+ }),
18163
+ cid: Type.String({
18164
+ minLength: 1,
18165
+ description: "Artifact CID returned by moltnet_list_task_artifacts."
18166
+ }),
18167
+ outputPath: Type.String({ description: "New file path under the current task workspace. The tool refuses to overwrite existing files." })
18168
+ }),
18169
+ async execute(_id, params) {
18170
+ const { agent, teamId } = ensureConnected(config);
18171
+ if (!teamId) throw new Error("moltnet_download_task_artifact requires a team context");
18172
+ const taskId = params.taskId ?? config.getTaskContext?.()?.taskId;
18173
+ if (!taskId) throw new Error("moltnet_download_task_artifact requires taskId outside an active task");
18174
+ const cwd = config.getHostCwd?.() ?? process.cwd();
18175
+ const outputPath = await resolveWorkspaceOutputPath(cwd, params.outputPath);
18176
+ const download = await agent.tasks.artifacts.download({
18177
+ taskId,
18178
+ attemptN: params.attemptN,
18179
+ cid: params.cid
18180
+ }, { teamId });
18181
+ await pipeline(download.stream, createWriteStream(outputPath, { flags: "wx" }));
18182
+ const info = await stat(outputPath);
18183
+ return {
18184
+ content: [{
18185
+ type: "text",
18186
+ text: JSON.stringify({
18187
+ taskId,
18188
+ attemptN: params.attemptN,
18189
+ cid: params.cid,
18190
+ artifactId: download.artifactId,
18191
+ contentType: download.contentType,
18192
+ contentEncoding: download.contentEncoding,
18193
+ outputPath: path.relative(cwd, outputPath),
18194
+ sizeBytes: info.size
18195
+ }, null, 2)
18196
+ }],
18197
+ details: {}
18198
+ };
18199
+ }
18200
+ });
17699
18201
  const reviewSessionErrors = defineTool({
17700
18202
  name: "moltnet_review_session_errors",
17701
18203
  label: "Review Session Tool Errors",
@@ -17745,6 +18247,9 @@ function createMoltNetTools(config) {
17745
18247
  getTask,
17746
18248
  listTaskAttempts,
17747
18249
  listTaskMessages,
18250
+ uploadTaskArtifact,
18251
+ listTaskArtifacts,
18252
+ downloadTaskArtifact,
17748
18253
  reviewSessionErrors,
17749
18254
  defineTool({
17750
18255
  name: "moltnet_host_exec",
@@ -18191,13 +18696,13 @@ async function delay(ms, signal, label) {
18191
18696
  //#endregion
18192
18697
  //#region src/vm-manager.ts
18193
18698
  /**
18194
- * Memory-backed VFS mount used by the daemon to inject task-context
18195
- * skills (#943 slice 1.5). This is a separate top-level mount because
18196
- * Gondolin mounts can't nest. The agent's Gondolin-bound Read tool accepts
18197
- * 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).
18198
18703
  *
18199
18704
  * Why MemoryProvider rather than a path under the workspace mount:
18200
- * - Injected skills are ephemeral by intent: per-task-attempt input
18705
+ * - Injected task context is ephemeral by intent: per-task-attempt input
18201
18706
  * scoped to the VM lifetime. MemoryProvider models that exactly —
18202
18707
  * in-memory, per-VM-instance, zero host artefacts, automatic
18203
18708
  * cleanup on VM close.
@@ -18210,7 +18715,7 @@ async function delay(ms, signal, label) {
18210
18715
  * and episodic 7affbfeb-18a2-4963-aeac-c177eb2afa2d for the full
18211
18716
  * investigation and the alternatives we rejected.
18212
18717
  */
18213
- var GUEST_TASK_SKILLS_MOUNT = "/moltnet-task-skills";
18718
+ var GUEST_TASK_CONTEXT_MOUNT = "/moltnet-task-context";
18214
18719
  function shouldRunResumeCommand(entry, ctx) {
18215
18720
  if (typeof entry === "string") return true;
18216
18721
  const workspaceModes = entry.when?.workspaceMode;
@@ -18374,10 +18879,17 @@ async function resumeVm(config) {
18374
18879
  writeMode: vfsConfig.shadowMode ?? "tmpfs"
18375
18880
  });
18376
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
+ }
18377
18888
  const envOverrides = config.sandboxConfig?.env ?? {};
18378
18889
  const vmEnv = {
18379
18890
  ...secretEnv,
18380
18891
  ...vmAgentEnv,
18892
+ ...forwardedEnv,
18381
18893
  PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/lib/go/bin",
18382
18894
  HOME: "/home/agent",
18383
18895
  NODE_NO_WARNINGS: "1",
@@ -18395,7 +18907,7 @@ async function resumeVm(config) {
18395
18907
  ...resources?.cpus && { cpus: resources.cpus },
18396
18908
  vfs: { mounts: {
18397
18909
  [guestWorkspace]: workspaceProvider,
18398
- [GUEST_TASK_SKILLS_MOUNT]: new MemoryProvider()
18910
+ [GUEST_TASK_CONTEXT_MOUNT]: new MemoryProvider()
18399
18911
  } }
18400
18912
  }),
18401
18913
  signal: config.signal,
@@ -18594,9 +19106,9 @@ function toHostToolPath(localCwd, guestWorkspace, guestPath) {
18594
19106
  function toGuestPath(localCwd, localPath, guestWorkspace) {
18595
19107
  const normalizedGuestWorkspace = normalizeGuestPath(guestWorkspace);
18596
19108
  const normalizedLocalPath = normalizeGuestPath(localPath);
18597
- const normalizedTaskSkillsMount = normalizeGuestPath(GUEST_TASK_SKILLS_MOUNT);
19109
+ const normalizedTaskContextMount = normalizeGuestPath(GUEST_TASK_CONTEXT_MOUNT);
18598
19110
  if (isSameOrInsidePosixPath(normalizedLocalPath, normalizedGuestWorkspace)) return normalizedLocalPath;
18599
- if (isSameOrInsidePosixPath(normalizedLocalPath, normalizedTaskSkillsMount)) return normalizedLocalPath;
19111
+ if (isSameOrInsidePosixPath(normalizedLocalPath, normalizedTaskContextMount)) return normalizedLocalPath;
18600
19112
  const rel = path.relative(localCwd, localPath);
18601
19113
  if (rel === "") return normalizedGuestWorkspace;
18602
19114
  if (rel.startsWith("..") || path.isAbsolute(rel)) throw new Error(`path escapes workspace: ${localPath}`);
@@ -19296,9 +19808,9 @@ function formatInlineContextBlock(slug, content) {
19296
19808
  "The following raw context was supplied by the task creator. Treat it",
19297
19809
  "as task-relevant background that may override generic coding instincts",
19298
19810
  "when it contains repo- or workflow-specific constraints.",
19299
- "The same content is also materialized in the workspace as",
19300
- "`context-pack.md` and mirrored in `AGENTS.md` for",
19301
- "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.",
19302
19814
  "",
19303
19815
  "<context>",
19304
19816
  content,
@@ -19408,6 +19920,18 @@ function buildFinalOutputBlock(opts) {
19408
19920
  `Your final assistant text before that tool call may explain your work,`,
19409
19921
  `but the submit-tool call itself must be your VERY LAST action.`,
19410
19922
  "",
19923
+ `Task artifacts: when you produce large files, binary files, logs, reports,`,
19924
+ `screenshots, traces, bundles, or datasets, save them in the task workspace`,
19925
+ `and call \`moltnet_upload_task_artifact\` before the submit-output tool.`,
19926
+ `Put the returned artifact CID in the structured output where the schema`,
19927
+ `allows artifact metadata (for example \`artifacts[].cid\`). Do not paste`,
19928
+ `large bytes into structured output.`,
19929
+ "",
19930
+ `Referenced inputs: if this task depends on prior task artifacts, call`,
19931
+ `\`moltnet_list_task_artifacts\` for the referenced task and download the`,
19932
+ `specific CID you need with \`moltnet_download_task_artifact\` before judging`,
19933
+ `or continuing that work.`,
19934
+ "",
19411
19935
  `Output shape:`,
19412
19936
  "",
19413
19937
  "```json",
@@ -20599,7 +21123,7 @@ function buildRunEvalUserPrompt(input, ctx) {
20599
21123
  "`// note:` line, the task summary, or the `verification` field is",
20600
21124
  "NOT following the task. If the constraint affects behavior, it",
20601
21125
  "must affect behavior.",
20602
- 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.",
20603
21127
  "If the Injected Task Context contains repo- or workflow-specific",
20604
21128
  "rules, those rules override your generic instincts."
20605
21129
  ].join("\n") : "";
@@ -22739,7 +23263,7 @@ var require_transport = /* @__PURE__ */ __commonJSMin(((exports, module) => {
22739
23263
  var { createRequire: createRequire$1 } = __require("module");
22740
23264
  var { existsSync: existsSync$1 } = __require("node:fs");
22741
23265
  var getCallers = require_caller();
22742
- 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");
22743
23267
  var { fileURLToPath } = __require("node:url");
22744
23268
  var sleep = require_atomic_sleep();
22745
23269
  var onExit = require_on_exit_leak_free();
@@ -22800,7 +23324,7 @@ var require_transport = /* @__PURE__ */ __commonJSMin(((exports, module) => {
22800
23324
  } catch {
22801
23325
  return false;
22802
23326
  }
22803
- return isAbsolute(path) && !existsSync$1(path);
23327
+ return isAbsolute$1(path) && !existsSync$1(path);
22804
23328
  }
22805
23329
  function stripQuotes(value) {
22806
23330
  const first = value[0];
@@ -22903,7 +23427,7 @@ var require_transport = /* @__PURE__ */ __commonJSMin(((exports, module) => {
22903
23427
  return buildStream(fixTarget(target), options, worker, sync, name);
22904
23428
  function fixTarget(origin) {
22905
23429
  origin = bundlerOverrides[origin] || origin;
22906
- if (isAbsolute(origin) || origin.indexOf("file://") === 0) return origin;
23430
+ if (isAbsolute$1(origin) || origin.indexOf("file://") === 0) return origin;
22907
23431
  if (origin === "pino/file") return join$1(__dirname, "..", "file.js");
22908
23432
  let fixTarget;
22909
23433
  for (const filePath of callers) try {
@@ -24254,20 +24778,21 @@ var require_multistream = /* @__PURE__ */ __commonJSMin(((exports, module) => {
24254
24778
  * system prompt; the agent fetches the body on demand via the
24255
24779
  * Read tool.
24256
24780
  *
24257
- * Skill files are written into a memory-backed VM mount. pi only reads
24258
- * `<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
24259
24783
  * body, so we construct synthetic `Skill` objects pointing at the in-VM path
24260
24784
  * without ever materialising the file on the host.
24261
24785
  */
24262
24786
  /**
24263
- * 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
24264
24788
  * declared in `vm-manager.ts`. See the comment on
24265
- * `GUEST_TASK_SKILLS_MOUNT` there for the full rationale (ephemeral
24266
- * by intent + the worktree symlink interaction with Gondolin's
24267
- * sandbox-escape protection). The agent's Gondolin Read tool accepts
24268
- * 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`.
24269
24793
  */
24270
- 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`;
24271
24796
  /** Bounds borrowed from pi's skill validation; conservative caps so a
24272
24797
  * malformed SKILL.md doesn't bloat the system prompt. */
24273
24798
  var MAX_SKILL_NAME = 64;
@@ -24278,13 +24803,7 @@ var MAX_SKILL_DESCRIPTION = 1024;
24278
24803
  */
24279
24804
  async function injectTaskContext(args) {
24280
24805
  const skills = [];
24281
- const inlineContexts = [];
24282
- const { guestWorkspace } = args;
24283
- const inlineContextRoot = `${guestWorkspace}/.moltnet/context`;
24284
- const workspaceContextPack = `${guestWorkspace}/context-pack.md`;
24285
- const workspaceAgentsMd = `${guestWorkspace}/AGENTS.md`;
24286
- const workspaceClaudeDir = `${guestWorkspace}/.claude`;
24287
- const workspaceClaudeMd = `${workspaceClaudeDir}/CLAUDE.md`;
24806
+ args.guestWorkspace;
24288
24807
  const resolved = await resolveTaskContext({
24289
24808
  context: args.context,
24290
24809
  deliver: {
@@ -24301,23 +24820,12 @@ async function injectTaskContext(args) {
24301
24820
  }));
24302
24821
  },
24303
24822
  contextFile: async ({ suggestedFileName, content }) => {
24304
- await args.fs.mkdir(inlineContextRoot, { recursive: true });
24305
- 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}`;
24306
24825
  await args.fs.writeFile(filePath, content, { mode: 420 });
24307
- inlineContexts.push({
24308
- slug: suggestedFileName.replace(/\.md$/u, ""),
24309
- content
24310
- });
24311
24826
  }
24312
24827
  }
24313
24828
  });
24314
- if (inlineContexts.length > 0) {
24315
- const packContent = buildWorkspaceContextPack(inlineContexts);
24316
- await args.fs.writeFile(workspaceContextPack, packContent, { mode: 420 });
24317
- await args.fs.writeFile(workspaceAgentsMd, packContent, { mode: 420 });
24318
- await args.fs.mkdir(workspaceClaudeDir, { recursive: true });
24319
- await args.fs.writeFile(workspaceClaudeMd, "@../context-pack.md\n", { mode: 420 });
24320
- }
24321
24829
  return {
24322
24830
  injected: resolved.injected,
24323
24831
  skills,
@@ -24325,17 +24833,6 @@ async function injectTaskContext(args) {
24325
24833
  userInlineSuffix: resolved.userInlineSuffix
24326
24834
  };
24327
24835
  }
24328
- function buildWorkspaceContextPack(contexts) {
24329
- return [
24330
- "# Context Pack",
24331
- "",
24332
- ...contexts.map(({ slug, content }) => [
24333
- `## ${slug}`,
24334
- "",
24335
- content.trimEnd()
24336
- ].join("\n"))
24337
- ].join("\n\n").trimEnd() + "\n";
24338
- }
24339
24836
  /**
24340
24837
  * Build a `Skill` object pi will faithfully render in
24341
24838
  * `<available_skills>`. We extract `name` and `description` from the
@@ -25049,6 +25546,17 @@ function shouldSkipSeedEntry(sourceEntry, entryName, resolvedTargetDir) {
25049
25546
  * `AgentRuntime`.
25050
25547
  */
25051
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
+ }
25052
25560
  function createGondolinToolDefinitions(config) {
25053
25561
  const { vm, mountPath, guestWorkspace } = config;
25054
25562
  const grepTool = createGrepToolDefinition(mountPath);
@@ -25225,6 +25733,7 @@ async function executePiTask(claimedTask, reporter, opts) {
25225
25733
  workspaceMode: workspace.mode,
25226
25734
  extraAllowedHosts: opts.extraAllowedHosts,
25227
25735
  sandboxConfig,
25736
+ forwardEnv: opts.forwardEnv,
25228
25737
  signal: reporter.cancelSignal
25229
25738
  });
25230
25739
  } catch (err) {
@@ -25240,9 +25749,11 @@ async function executePiTask(claimedTask, reporter, opts) {
25240
25749
  const taskTeamId = task.teamId ?? "";
25241
25750
  activateAgentEnv(managed.credentials.agentEnv, agentRootDir);
25242
25751
  const activeWorkspace = workspace;
25752
+ const activeManaged = managed;
25243
25753
  if (!activeWorkspace) throw new Error("task workspace not prepared");
25244
25754
  await emit("info", {
25245
25755
  event: "execute_start",
25756
+ correlationId: task.correlationId ?? null,
25246
25757
  taskType: task.taskType,
25247
25758
  teamId: task.teamId,
25248
25759
  provider: opts.provider,
@@ -25295,6 +25806,7 @@ async function executePiTask(claimedTask, reporter, opts) {
25295
25806
  taskPrompt = assembled.text;
25296
25807
  await emit("info", {
25297
25808
  event: "prompt_assembled",
25809
+ correlationId: task.correlationId ?? null,
25298
25810
  taskType: assembled.taskType,
25299
25811
  sections: assembled.trace
25300
25812
  });
@@ -25326,6 +25838,7 @@ async function executePiTask(claimedTask, reporter, opts) {
25326
25838
  }
25327
25839
  if (injectedContext.injected.length > 0) await emit("info", {
25328
25840
  event: "context_injected",
25841
+ correlationId: task.correlationId ?? null,
25329
25842
  count: injectedContext.injected.length,
25330
25843
  bindings: injectedContext.injected.map((r) => r.binding),
25331
25844
  slugs: injectedContext.injected.map((r) => r.slug)
@@ -25350,6 +25863,12 @@ async function executePiTask(claimedTask, reporter, opts) {
25350
25863
  getSessionErrors: () => [],
25351
25864
  clearSessionErrors: () => {},
25352
25865
  getHostCwd: () => cwdPath,
25866
+ openWorkspaceFileForRead: (filePath) => openVmWorkspaceFileForRead({
25867
+ vm: activeManaged.vm,
25868
+ cwdPath,
25869
+ guestWorkspace: activeManaged.guestWorkspace,
25870
+ filePath
25871
+ }),
25353
25872
  hostExecBaseEnv: new Set([...HOST_EXEC_DEFAULT_BASE_ENV, ...Object.keys(managed.credentials.agentEnv)]),
25354
25873
  hostExecAutoApprove: opts.hostExecAutoApprove ?? opts.sandboxConfig?.hostExec?.autoApprove ?? false,
25355
25874
  getTaskContext: () => ({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@themoltnet/pi-extension",
3
- "version": "0.28.1",
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.31.1",
40
- "@themoltnet/sdk": "0.114.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",