@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 +29 -0
- package/dist/index.js +578 -59
- package/package.json +3 -3
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
|
|
18195
|
-
*
|
|
18196
|
-
*
|
|
18197
|
-
*
|
|
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
|
|
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
|
|
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
|
-
[
|
|
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
|
|
19109
|
+
const normalizedTaskContextMount = normalizeGuestPath(GUEST_TASK_CONTEXT_MOUNT);
|
|
18598
19110
|
if (isSameOrInsidePosixPath(normalizedLocalPath, normalizedGuestWorkspace)) return normalizedLocalPath;
|
|
18599
|
-
if (isSameOrInsidePosixPath(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
|
|
19300
|
-
"
|
|
19301
|
-
"
|
|
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
|
|
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
|
-
*
|
|
24258
|
-
* `<available_skills>` metadata (name, description, location), never the
|
|
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
|
|
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
|
-
* `
|
|
24266
|
-
*
|
|
24267
|
-
*
|
|
24268
|
-
*
|
|
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 =
|
|
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
|
-
|
|
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(
|
|
24305
|
-
const filePath = `${
|
|
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.
|
|
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.
|
|
40
|
-
"@themoltnet/sdk": "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",
|