@themoltnet/pi-extension 0.33.0 → 0.34.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/README.md +36 -0
- package/dist/index.d.ts +18 -13
- package/dist/index.js +436 -204
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -163,6 +163,10 @@ the base snapshot is used (Alpine + git + gh + MoltNet CLI + agent user).
|
|
|
163
163
|
{ "argsPrefix": ["pr", "create"], "executable": "gh" }
|
|
164
164
|
]
|
|
165
165
|
},
|
|
166
|
+
"network": {
|
|
167
|
+
"allowedHosts": ["api.example.com", "*.services.example.com"],
|
|
168
|
+
"allowedInternalHosts": ["onboard-api.internal"]
|
|
169
|
+
},
|
|
166
170
|
"resources": {
|
|
167
171
|
"cpus": 2,
|
|
168
172
|
"memory": "6G"
|
|
@@ -201,6 +205,38 @@ Controls what's installed on top of the base layer during snapshot build.
|
|
|
201
205
|
| `allowedHosts` | Extra hosts allowed during build (base hosts always included) |
|
|
202
206
|
| `overlaySize` | qcow2 overlay disk size (default `"3G"`) |
|
|
203
207
|
|
|
208
|
+
### `network`
|
|
209
|
+
|
|
210
|
+
Controls HTTP(S) egress while a VM is running. Both arrays accept exact
|
|
211
|
+
hostnames such as `api.example.com` and leading wildcard patterns such as
|
|
212
|
+
`*.example.com`. Do not include a URL scheme, port, or path.
|
|
213
|
+
|
|
214
|
+
- `allowedHosts` grants ordinary hostname egress. Gondolin still rejects a
|
|
215
|
+
matching hostname if DNS resolves it to loopback, link-local, or a private IP
|
|
216
|
+
range. This protects public allowlist entries from DNS rebinding and SSRF.
|
|
217
|
+
- `allowedInternalHosts` explicitly permits matching hostnames to resolve to
|
|
218
|
+
internal/private IP ranges. Gondolin automatically includes these entries in
|
|
219
|
+
its effective hostname allowlist, so they do not need to appear in both
|
|
220
|
+
arrays. Treat this as the stronger, security-sensitive permission.
|
|
221
|
+
|
|
222
|
+
The base runtime hosts, MoltNet API host, and legacy `extraAllowedHosts` remain
|
|
223
|
+
external-only. They are not implicitly allowed to resolve internally, and VM
|
|
224
|
+
resume rejects an `allowedInternalHosts` pattern that overlaps one of those
|
|
225
|
+
protected patterns. Use a distinct internal service hostname when private
|
|
226
|
+
resolution is required.
|
|
227
|
+
|
|
228
|
+
Runtime hosts are deliberately separate from `snapshot.allowedHosts`: build
|
|
229
|
+
dependencies do not become task-time egress grants, and runtime services do not
|
|
230
|
+
become snapshot build dependencies. Private destinations require an explicit
|
|
231
|
+
`allowedInternalHosts` grant; unlisted internal and external hosts remain
|
|
232
|
+
blocked.
|
|
233
|
+
|
|
234
|
+
Profiles and repo-local `sandbox.json` use the same field. For profiles, treat
|
|
235
|
+
this as a team-editable security boundary: forwarded environment values and
|
|
236
|
+
other VM-accessible secrets can be sent to any granted host. An internal grant
|
|
237
|
+
can additionally expose localhost services, cloud metadata, and private network
|
|
238
|
+
infrastructure if the hostname is attacker-controlled.
|
|
239
|
+
|
|
204
240
|
### `resources`
|
|
205
241
|
|
|
206
242
|
VM resource limits applied at runtime.
|
package/dist/index.d.ts
CHANGED
|
@@ -308,6 +308,12 @@ export declare interface ExecutePiTaskOptions {
|
|
|
308
308
|
sandboxConfig?: SandboxConfig;
|
|
309
309
|
/** Host environment variable names to forward into the Pi VM. */
|
|
310
310
|
forwardEnv?: string[];
|
|
311
|
+
/**
|
|
312
|
+
* Runtime profile context defaults. Merged with task.input.context at
|
|
313
|
+
* execution time because the selected runtime profile is known only after
|
|
314
|
+
* claim. Task entries override profile entries with the same slug.
|
|
315
|
+
*/
|
|
316
|
+
runtimeProfileContext?: readonly ContextRef[];
|
|
311
317
|
/**
|
|
312
318
|
* Forwarded to `buildTaskUserPrompt` for per-type builders. Static
|
|
313
319
|
* across tasks. Today no built-in builder needs per-task `extras` —
|
|
@@ -480,14 +486,14 @@ export declare interface InjectedTaskContext {
|
|
|
480
486
|
}
|
|
481
487
|
|
|
482
488
|
/**
|
|
483
|
-
* Resolve
|
|
489
|
+
* Resolve effective runtime context and inject the side effects Pi
|
|
484
490
|
* needs. Safe to call with an empty array — returns an inert result.
|
|
485
491
|
*/
|
|
486
492
|
export declare function injectTaskContext(args: InjectTaskContextArgs): Promise<InjectedTaskContext>;
|
|
487
493
|
|
|
488
494
|
export declare interface InjectTaskContextArgs {
|
|
489
495
|
/** Empty array (the default for any non-eval task) is a no-op. */
|
|
490
|
-
context:
|
|
496
|
+
context: readonly ContextRef[];
|
|
491
497
|
/** Guest filesystem handle. In production this is `managed.vm.fs`. */
|
|
492
498
|
fs: VmFsForContext;
|
|
493
499
|
/** Guest path where the active host workspace is mounted. */
|
|
@@ -760,6 +766,14 @@ export declare interface SandboxConfig {
|
|
|
760
766
|
/** Overlay disk size (default '3G'). */
|
|
761
767
|
overlaySize?: string;
|
|
762
768
|
};
|
|
769
|
+
/** Runtime network egress policy. Separate from snapshot build access. */
|
|
770
|
+
network?: {
|
|
771
|
+
/** Additional host patterns allowed while the VM is running.
|
|
772
|
+
* Internal and private address resolution remains blocked. */
|
|
773
|
+
allowedHosts?: string[];
|
|
774
|
+
/** Host patterns explicitly allowed to resolve to internal/private IPs. */
|
|
775
|
+
allowedInternalHosts?: string[];
|
|
776
|
+
};
|
|
763
777
|
/** Shell commands to run every VM resume, after platform setup
|
|
764
778
|
* (TLS, DNS, git safe.directory, tmpfs node_modules) and before
|
|
765
779
|
* the agent session starts. Use for per-session bootstrap that
|
|
@@ -881,7 +895,7 @@ declare const Task: Type.TObject<{
|
|
|
881
895
|
inputCid: Type.TString;
|
|
882
896
|
references: Type.TArray<Type.TObject<{
|
|
883
897
|
taskId: Type.TUnion<[Type.TString, Type.TNull]>;
|
|
884
|
-
outputCid: Type.TString
|
|
898
|
+
outputCid: Type.TOptional<Type.TString>;
|
|
885
899
|
role: Type.TUnion<[Type.TLiteral<"judged_work">, Type.TLiteral<"reviewed_diff">, Type.TLiteral<"target_source">, Type.TLiteral<"context">]>;
|
|
886
900
|
external: Type.TOptional<Type.TObject<{
|
|
887
901
|
kind: Type.TUnion<[Type.TLiteral<"github_pr">, Type.TLiteral<"github_issue">, Type.TLiteral<"http_url">]>;
|
|
@@ -893,7 +907,7 @@ declare const Task: Type.TObject<{
|
|
|
893
907
|
}>>;
|
|
894
908
|
artifact: Type.TOptional<Type.TObject<{
|
|
895
909
|
cid: Type.TString;
|
|
896
|
-
attemptN: Type.TInteger
|
|
910
|
+
attemptN: Type.TOptional<Type.TInteger>;
|
|
897
911
|
kind: Type.TOptional<Type.TString>;
|
|
898
912
|
title: Type.TOptional<Type.TString>;
|
|
899
913
|
contentType: Type.TOptional<Type.TString>;
|
|
@@ -922,15 +936,6 @@ declare const Task: Type.TObject<{
|
|
|
922
936
|
|
|
923
937
|
declare type Task = Static<typeof Task>;
|
|
924
938
|
|
|
925
|
-
/** Reusable input fragment for any task type. Soft cap at 5 items. */
|
|
926
|
-
declare const TaskContext: Type.TArray<Type.TObject<{
|
|
927
|
-
slug: Type.TString;
|
|
928
|
-
binding: Type.TUnion<[Type.TLiteral<"skill">, Type.TLiteral<"context_inline">, Type.TLiteral<"prompt_prefix">, Type.TLiteral<"user_inline">]>;
|
|
929
|
-
content: Type.TString;
|
|
930
|
-
}>>;
|
|
931
|
-
|
|
932
|
-
declare type TaskContext = Static<typeof TaskContext>;
|
|
933
|
-
|
|
934
939
|
declare const TaskMessage: Type.TObject<{
|
|
935
940
|
taskId: Type.TString;
|
|
936
941
|
attemptN: Type.TNumber;
|
package/dist/index.js
CHANGED
|
@@ -1965,7 +1965,34 @@ var findLatestRuntimeSlotForAttempt = (options) => (options.client ?? client).ge
|
|
|
1965
1965
|
...options
|
|
1966
1966
|
});
|
|
1967
1967
|
/**
|
|
1968
|
-
*
|
|
1968
|
+
* Stage immutable content-addressed artifact bytes for later binding as task input artifacts via task creation references. Creates no metadata row; staged bytes are not downloadable until bound to a task, and unbound objects are garbage-collected after a grace window.
|
|
1969
|
+
*/
|
|
1970
|
+
var stageTaskArtifact = (options) => (options.client ?? client).put({
|
|
1971
|
+
bodySerializer: null,
|
|
1972
|
+
security: [
|
|
1973
|
+
{
|
|
1974
|
+
scheme: "bearer",
|
|
1975
|
+
type: "http"
|
|
1976
|
+
},
|
|
1977
|
+
{
|
|
1978
|
+
name: "X-Moltnet-Session-Token",
|
|
1979
|
+
type: "apiKey"
|
|
1980
|
+
},
|
|
1981
|
+
{
|
|
1982
|
+
in: "cookie",
|
|
1983
|
+
name: "ory_kratos_session",
|
|
1984
|
+
type: "apiKey"
|
|
1985
|
+
}
|
|
1986
|
+
],
|
|
1987
|
+
url: "/task-artifacts/staged",
|
|
1988
|
+
...options,
|
|
1989
|
+
headers: {
|
|
1990
|
+
"Content-Type": "application/octet-stream",
|
|
1991
|
+
...options.headers
|
|
1992
|
+
}
|
|
1993
|
+
});
|
|
1994
|
+
/**
|
|
1995
|
+
* Queue asynchronous deletion of waiting, queued, and terminal tasks in bulk. By default, dispatched, running, unauthorized, missing, and protected tasks are skipped. Set force: true with a reason to delete protected terminal tasks.
|
|
1969
1996
|
*/
|
|
1970
1997
|
var batchDeleteTasks = (options) => (options.client ?? client).delete({
|
|
1971
1998
|
security: [
|
|
@@ -9671,6 +9698,11 @@ var RuntimeProfileSandboxResumeCommand = Union([String$1({
|
|
|
9671
9698
|
maximum: 6e4
|
|
9672
9699
|
}))
|
|
9673
9700
|
}, { additionalProperties: false })]);
|
|
9701
|
+
var RuntimeProfileAllowedHost = String$1({
|
|
9702
|
+
minLength: 1,
|
|
9703
|
+
maxLength: 255,
|
|
9704
|
+
pattern: "^(?:\\*\\.)?(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)(?:\\.(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?))*$"
|
|
9705
|
+
});
|
|
9674
9706
|
var RuntimeProfileSandbox = _Object_({
|
|
9675
9707
|
snapshot: Optional(_Object_({
|
|
9676
9708
|
setupCommands: Optional(_Array_(String$1({
|
|
@@ -9687,6 +9719,10 @@ var RuntimeProfileSandbox = _Object_({
|
|
|
9687
9719
|
pattern: "^[0-9]+[KMGTP]?$"
|
|
9688
9720
|
}))
|
|
9689
9721
|
}, { additionalProperties: false })),
|
|
9722
|
+
network: Optional(_Object_({
|
|
9723
|
+
allowedHosts: Optional(_Array_(RuntimeProfileAllowedHost, { maxItems: 50 })),
|
|
9724
|
+
allowedInternalHosts: Optional(_Array_(RuntimeProfileAllowedHost, { maxItems: 50 }))
|
|
9725
|
+
}, { additionalProperties: false })),
|
|
9690
9726
|
resumeCommands: Optional(_Array_(RuntimeProfileSandboxResumeCommand, { maxItems: 30 })),
|
|
9691
9727
|
vfs: Optional(_Object_({
|
|
9692
9728
|
shadow: Optional(_Array_(String$1({
|
|
@@ -10030,7 +10066,7 @@ _Object_({
|
|
|
10030
10066
|
* (server-side schema check). Self-assessment is a truthful self-rating,
|
|
10031
10067
|
* NOT enforcement — `verification.passed=false` does not block /complete
|
|
10032
10068
|
* and does not affect `acceptedAttemptN`. See
|
|
10033
|
-
* `docs/
|
|
10069
|
+
* `docs/use/tasks-and-runtime.md` for the full producer/judge flow.
|
|
10034
10070
|
*
|
|
10035
10071
|
* **Binding evaluation** (judgment tasks: `assess_brief`, `judge_pack`).
|
|
10036
10072
|
* A separate task whose IS the application of `successCriteria` to
|
|
@@ -10146,7 +10182,7 @@ _Object_({
|
|
|
10146
10182
|
id: String$1({ format: "uuid" }),
|
|
10147
10183
|
teamId: String$1({ format: "uuid" }),
|
|
10148
10184
|
taskId: String$1({ format: "uuid" }),
|
|
10149
|
-
attemptN: Integer({ minimum: 1 }),
|
|
10185
|
+
attemptN: Union([Integer({ minimum: 1 }), Null()]),
|
|
10150
10186
|
kind: String$1({
|
|
10151
10187
|
minLength: 1,
|
|
10152
10188
|
maxLength: 100
|
|
@@ -10168,7 +10204,7 @@ _Object_({
|
|
|
10168
10204
|
minLength: 1,
|
|
10169
10205
|
maxLength: 100
|
|
10170
10206
|
}),
|
|
10171
|
-
createdByAgentId: String$1({ format: "uuid" }),
|
|
10207
|
+
createdByAgentId: Union([String$1({ format: "uuid" }), Null()]),
|
|
10172
10208
|
expiresAt: Union([String$1({ format: "date-time" }), Null()]),
|
|
10173
10209
|
createdAt: String$1({ format: "date-time" })
|
|
10174
10210
|
}, { $id: "TaskArtifact" })),
|
|
@@ -10184,6 +10220,16 @@ _Object_({
|
|
|
10184
10220
|
$id: "ListTaskArtifactsQuery",
|
|
10185
10221
|
additionalProperties: false
|
|
10186
10222
|
});
|
|
10223
|
+
var HeaderSafeContentType = String$1({
|
|
10224
|
+
minLength: 1,
|
|
10225
|
+
maxLength: 200,
|
|
10226
|
+
pattern: "^[\\x21-\\x7e][\\x20-\\x7e]*$"
|
|
10227
|
+
});
|
|
10228
|
+
var HeaderSafeContentEncoding = String$1({
|
|
10229
|
+
minLength: 1,
|
|
10230
|
+
maxLength: 100,
|
|
10231
|
+
pattern: "^[\\x21-\\x7e][\\x20-\\x7e]*$"
|
|
10232
|
+
});
|
|
10187
10233
|
_Object_({
|
|
10188
10234
|
kind: String$1({
|
|
10189
10235
|
minLength: 1,
|
|
@@ -10193,14 +10239,8 @@ _Object_({
|
|
|
10193
10239
|
minLength: 1,
|
|
10194
10240
|
maxLength: 255
|
|
10195
10241
|
}),
|
|
10196
|
-
contentType: Optional(
|
|
10197
|
-
|
|
10198
|
-
maxLength: 200
|
|
10199
|
-
})),
|
|
10200
|
-
contentEncoding: Optional(String$1({
|
|
10201
|
-
minLength: 1,
|
|
10202
|
-
maxLength: 100
|
|
10203
|
-
}))
|
|
10242
|
+
contentType: Optional(HeaderSafeContentType),
|
|
10243
|
+
contentEncoding: Optional(HeaderSafeContentEncoding)
|
|
10204
10244
|
}, {
|
|
10205
10245
|
$id: "UploadTaskArtifactQuery",
|
|
10206
10246
|
additionalProperties: false
|
|
@@ -10232,6 +10272,34 @@ _Object_({
|
|
|
10232
10272
|
$id: "TaskArtifactContentParams",
|
|
10233
10273
|
additionalProperties: false
|
|
10234
10274
|
});
|
|
10275
|
+
_Object_({
|
|
10276
|
+
contentType: Optional(HeaderSafeContentType),
|
|
10277
|
+
contentEncoding: Optional(HeaderSafeContentEncoding)
|
|
10278
|
+
}, {
|
|
10279
|
+
$id: "StageTaskArtifactQuery",
|
|
10280
|
+
additionalProperties: false
|
|
10281
|
+
});
|
|
10282
|
+
_Object_({
|
|
10283
|
+
cid: String$1({
|
|
10284
|
+
minLength: 1,
|
|
10285
|
+
maxLength: 100
|
|
10286
|
+
}),
|
|
10287
|
+
sizeBytes: Integer({ minimum: 0 }),
|
|
10288
|
+
contentType: String$1({
|
|
10289
|
+
minLength: 1,
|
|
10290
|
+
maxLength: 200
|
|
10291
|
+
})
|
|
10292
|
+
}, { $id: "StagedTaskArtifact" });
|
|
10293
|
+
_Object_({
|
|
10294
|
+
taskId: String$1({ format: "uuid" }),
|
|
10295
|
+
cid: String$1({
|
|
10296
|
+
minLength: 1,
|
|
10297
|
+
maxLength: 100
|
|
10298
|
+
})
|
|
10299
|
+
}, {
|
|
10300
|
+
$id: "TaskArtifactTaskContentParams",
|
|
10301
|
+
additionalProperties: false
|
|
10302
|
+
});
|
|
10235
10303
|
//#endregion
|
|
10236
10304
|
//#region ../../node_modules/.pnpm/multiformats@13.4.2/node_modules/multiformats/dist/src/codecs/json.js
|
|
10237
10305
|
var textEncoder$2 = new TextEncoder();
|
|
@@ -13847,6 +13915,42 @@ function validateTaskCreateRequest(args) {
|
|
|
13847
13915
|
field: "references",
|
|
13848
13916
|
message: `At least one reference is required for task type: ${args.taskType}`
|
|
13849
13917
|
});
|
|
13918
|
+
errors.push(...validateTaskReferences(args.references));
|
|
13919
|
+
return errors;
|
|
13920
|
+
}
|
|
13921
|
+
/**
|
|
13922
|
+
* Cross-field rules for the reference shapes the schema alone cannot
|
|
13923
|
+
* express. Every reference must be exactly one of:
|
|
13924
|
+
*
|
|
13925
|
+
* - **task output ref**: taskId set, outputCid required; an optional
|
|
13926
|
+
* artifact must name its producing attempt (attemptN).
|
|
13927
|
+
* - **input artifact ref**: taskId null, artifact without attemptN;
|
|
13928
|
+
* outputCid omitted (or equal to artifact.cid when sent) -- the bytes
|
|
13929
|
+
* were staged before any task existed, so there is no output to name.
|
|
13930
|
+
* - **external ref**: taskId null, external present, no artifact.
|
|
13931
|
+
*
|
|
13932
|
+
* Anything else used to be silently persisted into taskRefs and never
|
|
13933
|
+
* materialized; now it fails fast with a field error.
|
|
13934
|
+
*/
|
|
13935
|
+
function validateTaskReferences(references) {
|
|
13936
|
+
const errors = [];
|
|
13937
|
+
(references ?? []).forEach((ref, index) => {
|
|
13938
|
+
const invalid = (message) => errors.push({
|
|
13939
|
+
field: `references[${index}]`,
|
|
13940
|
+
message
|
|
13941
|
+
});
|
|
13942
|
+
if (ref.taskId !== null) {
|
|
13943
|
+
if (ref.outputCid === void 0) invalid("outputCid is required when referencing a task output");
|
|
13944
|
+
if (ref.artifact && ref.artifact.attemptN === void 0) invalid("artifact references to another task must include attemptN; input artifacts are referenced with taskId null");
|
|
13945
|
+
return;
|
|
13946
|
+
}
|
|
13947
|
+
if (ref.artifact) {
|
|
13948
|
+
if (ref.artifact.attemptN !== void 0) invalid("input artifact references (taskId null) must not include attemptN; reference the producing task by id instead");
|
|
13949
|
+
if (ref.outputCid !== void 0 && ref.outputCid !== ref.artifact.cid) invalid("outputCid on an input artifact reference must be omitted or equal artifact.cid");
|
|
13950
|
+
return;
|
|
13951
|
+
}
|
|
13952
|
+
if (!ref.external) invalid("references with taskId null must carry either an artifact (input artifact) or an external descriptor");
|
|
13953
|
+
});
|
|
13850
13954
|
return errors;
|
|
13851
13955
|
}
|
|
13852
13956
|
//#endregion
|
|
@@ -13963,7 +14067,7 @@ Unsafe(Cyclic({ ClaimCondition: Unsafe(Union([
|
|
|
13963
14067
|
*/
|
|
13964
14068
|
var TaskRef = _Object_({
|
|
13965
14069
|
taskId: Union([Uuid, Null()]),
|
|
13966
|
-
outputCid: Cid,
|
|
14070
|
+
outputCid: Optional(Cid),
|
|
13967
14071
|
role: Union([
|
|
13968
14072
|
Literal("judged_work"),
|
|
13969
14073
|
Literal("reviewed_diff"),
|
|
@@ -13984,7 +14088,7 @@ var TaskRef = _Object_({
|
|
|
13984
14088
|
})),
|
|
13985
14089
|
artifact: Optional(_Object_({
|
|
13986
14090
|
cid: Cid,
|
|
13987
|
-
attemptN: Integer({ minimum: 1 }),
|
|
14091
|
+
attemptN: Optional(Integer({ minimum: 1 })),
|
|
13988
14092
|
kind: Optional(String$1({
|
|
13989
14093
|
minLength: 1,
|
|
13990
14094
|
maxLength: 100
|
|
@@ -14454,30 +14558,53 @@ var TaskBuilder = class {
|
|
|
14454
14558
|
return this;
|
|
14455
14559
|
}
|
|
14456
14560
|
/**
|
|
14457
|
-
* Add a
|
|
14458
|
-
*
|
|
14561
|
+
* Add either a staged input artifact or a persistent attempt artifact.
|
|
14562
|
+
* Staged metadata returned by `tasks.artifacts.stage()` carries
|
|
14563
|
+
* `artifactSource: 'staged'` and produces a reference with `taskId: null`, no
|
|
14564
|
+
* `outputCid`, and no `attemptN`. Persistent artifacts require the producing
|
|
14565
|
+
* task, accepted output CID, artifact CID, and positive attempt number so
|
|
14566
|
+
* their provenance remains explicit.
|
|
14459
14567
|
*
|
|
14460
|
-
* @param source -
|
|
14568
|
+
* @param source - Staged SDK metadata, a result reader, raw artifact reference, or `TaskRef`.
|
|
14461
14569
|
* @param role - The role the referenced artifact plays.
|
|
14462
14570
|
* @returns This builder, for chaining.
|
|
14463
|
-
* @throws {TaskBuildError} when
|
|
14571
|
+
* @throws {TaskBuildError} when the source is ambiguous or required provenance is missing.
|
|
14464
14572
|
*/
|
|
14465
14573
|
artifactReference(source, role) {
|
|
14466
14574
|
let ref;
|
|
14467
14575
|
if ("artifactRef" in source && typeof source.artifactRef === "function") ref = source.artifactRef(role);
|
|
14468
|
-
else if ("
|
|
14469
|
-
|
|
14470
|
-
|
|
14471
|
-
|
|
14472
|
-
|
|
14473
|
-
|
|
14474
|
-
...source,
|
|
14475
|
-
|
|
14476
|
-
}
|
|
14477
|
-
}
|
|
14576
|
+
else if ("artifactSource" in source && source.artifactSource === "staged" && source.cid) ref = {
|
|
14577
|
+
taskId: null,
|
|
14578
|
+
role,
|
|
14579
|
+
artifact: {
|
|
14580
|
+
cid: source.cid,
|
|
14581
|
+
...source.kind ? { kind: source.kind } : {},
|
|
14582
|
+
...source.title ? { title: source.title } : {},
|
|
14583
|
+
...source.contentType ? { contentType: source.contentType } : {}
|
|
14584
|
+
}
|
|
14585
|
+
};
|
|
14586
|
+
else if ("cid" in source) throw new TaskBuildError([{
|
|
14587
|
+
field: "references/artifactSource",
|
|
14588
|
+
message: "top-level artifact CID is ambiguous; use metadata returned by tasks.artifacts.stage()"
|
|
14589
|
+
}]);
|
|
14590
|
+
else if ("artifact" in source && source.artifact?.cid) if (source.taskId === null && source.artifact.attemptN === void 0) ref = {
|
|
14591
|
+
taskId: null,
|
|
14592
|
+
role,
|
|
14593
|
+
artifact: { ...source.artifact }
|
|
14594
|
+
};
|
|
14595
|
+
else if (typeof source.artifact.attemptN !== "number" || !Number.isInteger(source.artifact.attemptN) || source.artifact.attemptN < 1) throw new TaskBuildError([{
|
|
14596
|
+
field: "references/artifact/attemptN",
|
|
14597
|
+
message: "artifact reference is missing required attemptN"
|
|
14598
|
+
}]);
|
|
14599
|
+
else ref = {
|
|
14600
|
+
...source,
|
|
14601
|
+
role
|
|
14602
|
+
};
|
|
14603
|
+
else {
|
|
14478
14604
|
const s = source;
|
|
14479
14605
|
const errors = [];
|
|
14480
|
-
|
|
14606
|
+
const inputArtifact = s.taskId === null && s.attemptN === void 0;
|
|
14607
|
+
if (!inputArtifact && !s.outputCid) errors.push({
|
|
14481
14608
|
field: "references/outputCid",
|
|
14482
14609
|
message: "reference is missing required outputCid"
|
|
14483
14610
|
});
|
|
@@ -14485,19 +14612,18 @@ var TaskBuilder = class {
|
|
|
14485
14612
|
field: "references/artifact/cid",
|
|
14486
14613
|
message: "artifact reference is missing required cid"
|
|
14487
14614
|
});
|
|
14488
|
-
if (typeof s.attemptN !== "number" || !Number.isInteger(s.attemptN) || s.attemptN < 1) errors.push({
|
|
14615
|
+
if (!inputArtifact && (typeof s.attemptN !== "number" || !Number.isInteger(s.attemptN) || s.attemptN < 1)) errors.push({
|
|
14489
14616
|
field: "references/artifact/attemptN",
|
|
14490
14617
|
message: "artifact reference is missing required attemptN"
|
|
14491
14618
|
});
|
|
14492
14619
|
if (errors.length > 0) throw new TaskBuildError(errors);
|
|
14493
|
-
const attemptN = s.attemptN;
|
|
14494
14620
|
ref = {
|
|
14495
14621
|
taskId: s.taskId ?? null,
|
|
14496
|
-
outputCid: s.outputCid,
|
|
14622
|
+
...!inputArtifact && s.outputCid ? { outputCid: s.outputCid } : {},
|
|
14497
14623
|
role,
|
|
14498
14624
|
artifact: {
|
|
14499
14625
|
cid: s.artifactCid,
|
|
14500
|
-
attemptN,
|
|
14626
|
+
...s.attemptN !== void 0 ? { attemptN: s.attemptN } : {},
|
|
14501
14627
|
...s.kind ? { kind: s.kind } : {},
|
|
14502
14628
|
...s.title ? { title: s.title } : {},
|
|
14503
14629
|
...s.contentType ? { contentType: s.contentType } : {}
|
|
@@ -14967,6 +15093,22 @@ function createTasksNamespace(context) {
|
|
|
14967
15093
|
}));
|
|
14968
15094
|
},
|
|
14969
15095
|
artifacts: {
|
|
15096
|
+
async stage(body, query, options) {
|
|
15097
|
+
return {
|
|
15098
|
+
...unwrapResult(await stageTaskArtifact({
|
|
15099
|
+
auth,
|
|
15100
|
+
body,
|
|
15101
|
+
client,
|
|
15102
|
+
duplex: "half",
|
|
15103
|
+
headers: {
|
|
15104
|
+
...requiredTeamHeaders(options),
|
|
15105
|
+
"content-type": "application/octet-stream"
|
|
15106
|
+
},
|
|
15107
|
+
query
|
|
15108
|
+
})),
|
|
15109
|
+
artifactSource: "staged"
|
|
15110
|
+
};
|
|
15111
|
+
},
|
|
14970
15112
|
async upload(path, body, query, options) {
|
|
14971
15113
|
return unwrapResult(await uploadTaskArtifact({
|
|
14972
15114
|
auth,
|
|
@@ -15000,17 +15142,24 @@ function createTasksNamespace(context) {
|
|
|
15000
15142
|
}));
|
|
15001
15143
|
},
|
|
15002
15144
|
async download(path, options) {
|
|
15003
|
-
const
|
|
15145
|
+
const request = {
|
|
15004
15146
|
auth,
|
|
15005
15147
|
headers: requiredTeamHeaders(options),
|
|
15006
15148
|
method: "GET",
|
|
15007
15149
|
parseAs: "stream",
|
|
15008
|
-
path,
|
|
15009
15150
|
security: [{
|
|
15010
15151
|
scheme: "bearer",
|
|
15011
15152
|
type: "http"
|
|
15012
|
-
}]
|
|
15153
|
+
}]
|
|
15154
|
+
};
|
|
15155
|
+
const result = "attemptN" in path ? await client.request({
|
|
15156
|
+
...request,
|
|
15157
|
+
path,
|
|
15013
15158
|
url: "/tasks/{taskId}/attempts/{attemptN}/artifacts/{cid}/content"
|
|
15159
|
+
}) : await client.request({
|
|
15160
|
+
...request,
|
|
15161
|
+
path,
|
|
15162
|
+
url: "/tasks/{taskId}/artifacts/{cid}/content"
|
|
15014
15163
|
});
|
|
15015
15164
|
const normalizedStream = normalizeDownloadStream(unwrapResult(result));
|
|
15016
15165
|
if (normalizedStream) return {
|
|
@@ -19010,6 +19159,39 @@ var BASE_ALLOWED_HOSTS = [
|
|
|
19010
19159
|
"*.googlesource.com"
|
|
19011
19160
|
];
|
|
19012
19161
|
/**
|
|
19162
|
+
* Return whether two Gondolin hostname globs can match at least one common
|
|
19163
|
+
* string. Each `*` is an arbitrary substring, so this walks the product of the
|
|
19164
|
+
* two small glob automata instead of relying on exact-string comparisons.
|
|
19165
|
+
*/
|
|
19166
|
+
function hostnamePatternsOverlap(left, right) {
|
|
19167
|
+
const a = left.trim().toLowerCase();
|
|
19168
|
+
const b = right.trim().toLowerCase();
|
|
19169
|
+
if (!a || !b) return false;
|
|
19170
|
+
const pending = [[0, 0]];
|
|
19171
|
+
const visited = /* @__PURE__ */ new Set();
|
|
19172
|
+
while (pending.length > 0) {
|
|
19173
|
+
const next = pending.pop();
|
|
19174
|
+
if (!next) continue;
|
|
19175
|
+
const [aIndex, bIndex] = next;
|
|
19176
|
+
const state = `${aIndex}:${bIndex}`;
|
|
19177
|
+
if (visited.has(state)) continue;
|
|
19178
|
+
visited.add(state);
|
|
19179
|
+
if (aIndex === a.length && bIndex === b.length) return true;
|
|
19180
|
+
const aChar = a[aIndex];
|
|
19181
|
+
const bChar = b[bIndex];
|
|
19182
|
+
if (aChar === "*") pending.push([aIndex + 1, bIndex]);
|
|
19183
|
+
if (bChar === "*") pending.push([aIndex, bIndex + 1]);
|
|
19184
|
+
if (aChar !== void 0 && bChar !== void 0 && (aChar === "*" || bChar === "*" || aChar === bChar)) pending.push([aChar === "*" ? aIndex : aIndex + 1, bChar === "*" ? bIndex : bIndex + 1]);
|
|
19185
|
+
}
|
|
19186
|
+
return false;
|
|
19187
|
+
}
|
|
19188
|
+
function assertInternalHostsDoNotOverlapProtectedHosts(internalHosts, protectedHosts) {
|
|
19189
|
+
for (const internalHost of internalHosts) {
|
|
19190
|
+
const protectedHost = protectedHosts.find((candidate) => hostnamePatternsOverlap(internalHost, candidate));
|
|
19191
|
+
if (protectedHost) throw new Error(`sandbox.network.allowedInternalHosts pattern "${internalHost}" overlaps external-only host pattern "${protectedHost}"`);
|
|
19192
|
+
}
|
|
19193
|
+
}
|
|
19194
|
+
/**
|
|
19013
19195
|
* Run a shell command in the guest and throw if it fails. Mirror of
|
|
19014
19196
|
* `run()` in `snapshot.ts` for the resume-side hook chain — every
|
|
19015
19197
|
* setup step is essential to a healthy session, so a silent non-zero
|
|
@@ -19051,11 +19233,18 @@ async function resumeVm(config) {
|
|
|
19051
19233
|
const creds = loadCredentials(agentDir);
|
|
19052
19234
|
const moltnetConfig = JSON.parse(creds.moltnetJson);
|
|
19053
19235
|
const apiHost = new URL(moltnetConfig.endpoints.api).hostname;
|
|
19054
|
-
const
|
|
19236
|
+
const runtimeAllowedHosts = config.sandboxConfig?.network?.allowedHosts ?? [];
|
|
19237
|
+
const runtimeAllowedInternalHosts = config.sandboxConfig?.network?.allowedInternalHosts ?? [];
|
|
19238
|
+
const protectedExternalHosts = [...new Set([
|
|
19055
19239
|
...BASE_ALLOWED_HOSTS,
|
|
19056
19240
|
apiHost,
|
|
19057
19241
|
...config.extraAllowedHosts ?? []
|
|
19058
|
-
]
|
|
19242
|
+
])];
|
|
19243
|
+
assertInternalHostsDoNotOverlapProtectedHosts(runtimeAllowedInternalHosts, protectedExternalHosts);
|
|
19244
|
+
const { httpHooks, env: secretEnv } = createHttpHooks({
|
|
19245
|
+
allowedHosts: [...new Set([...protectedExternalHosts, ...runtimeAllowedHosts])],
|
|
19246
|
+
allowedInternalHosts: runtimeAllowedInternalHosts
|
|
19247
|
+
});
|
|
19059
19248
|
const vmAgentDir = `/home/agent/.moltnet/${config.agentName}`;
|
|
19060
19249
|
const vmAgentEnv = {};
|
|
19061
19250
|
for (const [k, v] of Object.entries(creds.agentEnv)) {
|
|
@@ -19937,8 +20126,19 @@ async function resolvePersistentSessionManager(args) {
|
|
|
19937
20126
|
//#endregion
|
|
19938
20127
|
//#region ../agent-runtime/src/context-bindings.ts
|
|
19939
20128
|
var PROMPT_SEPARATOR = "\n\n---\n\n";
|
|
20129
|
+
var MAX_MERGED_RUNTIME_CONTEXT_ENTRIES = 10;
|
|
20130
|
+
/**
|
|
20131
|
+
* Merge runtime-profile context defaults with task-scoped context. Profile
|
|
20132
|
+
* entries are defaults; task entries with the same slug override them.
|
|
20133
|
+
*/
|
|
20134
|
+
function mergeRuntimeProfileContext(profileContext, taskContext) {
|
|
20135
|
+
const taskSlugs = new Set(taskContext.map((ref) => ref.slug));
|
|
20136
|
+
const merged = [...profileContext.filter((ref) => !taskSlugs.has(ref.slug)), ...taskContext];
|
|
20137
|
+
if (merged.length > MAX_MERGED_RUNTIME_CONTEXT_ENTRIES) throw new Error(`merged runtime context has ${merged.length} entries; maximum is ${MAX_MERGED_RUNTIME_CONTEXT_ENTRIES}`);
|
|
20138
|
+
return merged;
|
|
20139
|
+
}
|
|
19940
20140
|
/**
|
|
19941
|
-
* Resolve
|
|
20141
|
+
* Resolve runtime context entries into delivered side-effects (skills
|
|
19942
20142
|
* persisted via `deliver.skill`) and prompt fragments
|
|
19943
20143
|
* (`systemPromptPrefix`, `userInlineSuffix`) the caller weaves into the
|
|
19944
20144
|
* built prompt.
|
|
@@ -19958,9 +20158,10 @@ var PROMPT_SEPARATOR = "\n\n---\n\n";
|
|
|
19958
20158
|
* - `user_inline` → content appended to `userInlineSuffix` in
|
|
19959
20159
|
* declared order, same separator.
|
|
19960
20160
|
*
|
|
19961
|
-
* No fetching, no hashing — bytes are inlined in `ContextRef.content
|
|
19962
|
-
*
|
|
19963
|
-
*
|
|
20161
|
+
* No fetching, no hashing — bytes are inlined in `ContextRef.content`.
|
|
20162
|
+
* Task-scoped entries are pinned by the task's `inputCid`; profile-scoped
|
|
20163
|
+
* entries are pinned by the runtime profile revision/source the daemon
|
|
20164
|
+
* resolved. The resolver just dispatches already-selected bytes.
|
|
19964
20165
|
*
|
|
19965
20166
|
* The function is pure with respect to its arguments: file writes are
|
|
19966
20167
|
* confined to the injected `deliver` callback, which makes the
|
|
@@ -20006,12 +20207,13 @@ function formatInlineContextBlock(slug, content) {
|
|
|
20006
20207
|
"### Injected Task Context",
|
|
20007
20208
|
"",
|
|
20008
20209
|
`Context id: \`${slug}\``,
|
|
20009
|
-
"The following raw context was
|
|
20010
|
-
"as task-relevant background that may
|
|
20011
|
-
"when it contains repo- or
|
|
20210
|
+
"The following raw context was selected for this task by its task input",
|
|
20211
|
+
"or runtime profile. Treat it as task-relevant background that may",
|
|
20212
|
+
"override generic coding instincts when it contains repo- or",
|
|
20213
|
+
"workflow-specific constraints.",
|
|
20012
20214
|
"The same content may also be materialized by the runtime under",
|
|
20013
20215
|
"`/moltnet-task-context/context` for tool-based inspection. Do not",
|
|
20014
|
-
"create or rely on workspace mirror files for this
|
|
20216
|
+
"create or rely on workspace mirror files for this runtime context.",
|
|
20015
20217
|
"",
|
|
20016
20218
|
"<context>",
|
|
20017
20219
|
content,
|
|
@@ -21334,11 +21536,11 @@ function buildRenderPackUserPrompt(input, ctx) {
|
|
|
21334
21536
|
* `judge_eval_attempt` task(s) grade against their own hidden rubric.
|
|
21335
21537
|
*
|
|
21336
21538
|
* Context delivery is handled by `resolveTaskContext` (see
|
|
21337
|
-
* libs/agent-runtime/src/context-bindings.ts) and
|
|
21338
|
-
* prompt is rendered
|
|
21339
|
-
*
|
|
21340
|
-
*
|
|
21341
|
-
*
|
|
21539
|
+
* libs/agent-runtime/src/context-bindings.ts) and is selected BEFORE this
|
|
21540
|
+
* prompt is rendered. Task-scoped context lives in `input.context`; runtime
|
|
21541
|
+
* profile defaults arrive as `ctx.effectiveRuntimeContext` after the runtime
|
|
21542
|
+
* merges them with task context. This builder only renders context
|
|
21543
|
+
* discipline; it does NOT inline context bytes itself.
|
|
21342
21544
|
*
|
|
21343
21545
|
* Prompt-shape notes (issue #1175, area 1):
|
|
21344
21546
|
* - No `Correlation` section: the agent never acts on it. The id is
|
|
@@ -21356,12 +21558,13 @@ function buildRenderPackUserPrompt(input, ctx) {
|
|
|
21356
21558
|
*/
|
|
21357
21559
|
function buildRunEvalUserPrompt(input, ctx) {
|
|
21358
21560
|
const { scenario, variantLabel, successCriteria } = input;
|
|
21359
|
-
const
|
|
21360
|
-
const
|
|
21561
|
+
const effectiveRuntimeContext = ctx.effectiveRuntimeContext ?? input.context;
|
|
21562
|
+
const hasContext = effectiveRuntimeContext.length > 0;
|
|
21563
|
+
const hasInlineContext = effectiveRuntimeContext.some((entry) => entry.binding === "context_inline");
|
|
21361
21564
|
const header = `# Run Eval Agent\n\nYou are running an evaluation scenario as variant \`${variantLabel}\`.\nTask id: \`${ctx.taskId}\``;
|
|
21362
21565
|
const contextDiscipline = hasContext ? [
|
|
21363
21566
|
"This task includes Injected Task Context supplied by the task",
|
|
21364
|
-
"
|
|
21567
|
+
"input or runtime profile. You MUST inspect it BEFORE you write solution files or",
|
|
21365
21568
|
"draft your final answer — not after.",
|
|
21366
21569
|
"",
|
|
21367
21570
|
"Reconcile every constraint from that context **into the code path",
|
|
@@ -21534,7 +21737,8 @@ function buildTaskUserPrompt(task, ctx) {
|
|
|
21534
21737
|
return buildRunEvalUserPrompt(task.input, {
|
|
21535
21738
|
diaryId: ctx.diaryId,
|
|
21536
21739
|
taskId: ctx.taskId,
|
|
21537
|
-
correlationId: task.correlationId
|
|
21740
|
+
correlationId: task.correlationId,
|
|
21741
|
+
effectiveRuntimeContext: ctx.effectiveRuntimeContext
|
|
21538
21742
|
});
|
|
21539
21743
|
default: throw new Error(`No prompt builder registered for taskType="${task.taskType}"`);
|
|
21540
21744
|
}
|
|
@@ -25007,119 +25211,6 @@ var require_multistream = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
25007
25211
|
module.exports.pino = pino;
|
|
25008
25212
|
})))();
|
|
25009
25213
|
//#endregion
|
|
25010
|
-
//#region src/runtime/inject-task-context.ts
|
|
25011
|
-
/**
|
|
25012
|
-
* Slice 1.5 of #943 — wire the agent-runtime resolver into the
|
|
25013
|
-
* pi-extension execution path.
|
|
25014
|
-
*
|
|
25015
|
-
* `resolveTaskContext` is a pure dispatcher; this module provides the
|
|
25016
|
-
* Gondolin-aware deliverer and the post-resolution shape the
|
|
25017
|
-
* `execute-pi-task` caller needs to splice into pi's setup:
|
|
25018
|
-
*
|
|
25019
|
-
* - `systemPromptPrefix` → fed into `appendSystemPrompt` alongside
|
|
25020
|
-
* the runtime instructor (it IS a system-prompt fragment).
|
|
25021
|
-
* - `userInlineSuffix` → appended to the `buildTaskUserPrompt`
|
|
25022
|
-
* output BEFORE `session.prompt(text)`.
|
|
25023
|
-
* - `skills` → spliced into the `skillsOverride` callback's
|
|
25024
|
-
* return value. pi includes them in `<available_skills>` in the
|
|
25025
|
-
* system prompt; the agent fetches the body on demand via the
|
|
25026
|
-
* Read tool.
|
|
25027
|
-
*
|
|
25028
|
-
* Task-context files are written into a memory-backed VM mount. pi only reads
|
|
25029
|
-
* `<available_skills>` metadata (name, description, location), never the skill
|
|
25030
|
-
* body, so we construct synthetic `Skill` objects pointing at the in-VM path
|
|
25031
|
-
* without ever materialising the file on the host.
|
|
25032
|
-
*/
|
|
25033
|
-
/**
|
|
25034
|
-
* Where in the VM we write task-context bodies — the memory-backed mount
|
|
25035
|
-
* declared in `vm-manager.ts`. See the comment on
|
|
25036
|
-
* `GUEST_TASK_CONTEXT_MOUNT` there for the full rationale (ephemeral by
|
|
25037
|
-
* intent + the worktree symlink interaction with Gondolin's sandbox-escape
|
|
25038
|
-
* protection). The agent's Gondolin Read tool accepts paths under this mount
|
|
25039
|
-
* via `toGuestPath` in `tool-operations.ts`.
|
|
25040
|
-
*/
|
|
25041
|
-
var SKILL_ROOT_IN_VM = `${GUEST_TASK_CONTEXT_MOUNT}/skills`;
|
|
25042
|
-
var INLINE_CONTEXT_ROOT_IN_VM = `${GUEST_TASK_CONTEXT_MOUNT}/context`;
|
|
25043
|
-
/** Bounds borrowed from pi's skill validation; conservative caps so a
|
|
25044
|
-
* malformed SKILL.md doesn't bloat the system prompt. */
|
|
25045
|
-
var MAX_SKILL_NAME = 64;
|
|
25046
|
-
var MAX_SKILL_DESCRIPTION = 1024;
|
|
25047
|
-
/**
|
|
25048
|
-
* Resolve a task's `input.context[]` and inject the side effects pi
|
|
25049
|
-
* needs. Safe to call with an empty array — returns an inert result.
|
|
25050
|
-
*/
|
|
25051
|
-
async function injectTaskContext(args) {
|
|
25052
|
-
const skills = [];
|
|
25053
|
-
args.guestWorkspace;
|
|
25054
|
-
const resolved = await resolveTaskContext({
|
|
25055
|
-
context: args.context,
|
|
25056
|
-
deliver: {
|
|
25057
|
-
skill: async ({ slug, content }) => {
|
|
25058
|
-
const dir = `${SKILL_ROOT_IN_VM}/${slug}`;
|
|
25059
|
-
const filePath = `${dir}/SKILL.md`;
|
|
25060
|
-
await args.fs.mkdir(dir, { recursive: true });
|
|
25061
|
-
await args.fs.writeFile(filePath, content, { mode: 420 });
|
|
25062
|
-
skills.push(buildSyntheticSkill({
|
|
25063
|
-
slug,
|
|
25064
|
-
content,
|
|
25065
|
-
filePath,
|
|
25066
|
-
dir
|
|
25067
|
-
}));
|
|
25068
|
-
},
|
|
25069
|
-
contextFile: async ({ suggestedFileName, content }) => {
|
|
25070
|
-
await args.fs.mkdir(INLINE_CONTEXT_ROOT_IN_VM, { recursive: true });
|
|
25071
|
-
const filePath = `${INLINE_CONTEXT_ROOT_IN_VM}/${suggestedFileName}`;
|
|
25072
|
-
await args.fs.writeFile(filePath, content, { mode: 420 });
|
|
25073
|
-
}
|
|
25074
|
-
}
|
|
25075
|
-
});
|
|
25076
|
-
return {
|
|
25077
|
-
injected: resolved.injected,
|
|
25078
|
-
skills,
|
|
25079
|
-
systemPromptPrefix: resolved.systemPromptPrefix,
|
|
25080
|
-
userInlineSuffix: resolved.userInlineSuffix
|
|
25081
|
-
};
|
|
25082
|
-
}
|
|
25083
|
-
/**
|
|
25084
|
-
* Build a `Skill` object pi will faithfully render in
|
|
25085
|
-
* `<available_skills>`. We extract `name` and `description` from the
|
|
25086
|
-
* skill content's YAML frontmatter using pi's own `parseFrontmatter`
|
|
25087
|
-
* helper (proper YAML, not a regex hack) and fall back to the slug +
|
|
25088
|
-
* a generic description so a SKILL.md without frontmatter still
|
|
25089
|
-
* renders something meaningful.
|
|
25090
|
-
*
|
|
25091
|
-
* Frontmatter parsing is best-effort: a malformed YAML block is
|
|
25092
|
-
* optional metadata, not a reason to fail the task. We swallow parser
|
|
25093
|
-
* errors and fall back to the slug-derived metadata; the skill body
|
|
25094
|
-
* is unaffected.
|
|
25095
|
-
*
|
|
25096
|
-
* pi's `formatSkillsForPrompt` only reads `name`, `description`, and
|
|
25097
|
-
* `filePath` — `sourceInfo`/`baseDir` exist on the type but never
|
|
25098
|
-
* surface in the prompt, so a synthetic `SourceInfo` is enough.
|
|
25099
|
-
*/
|
|
25100
|
-
function buildSyntheticSkill(args) {
|
|
25101
|
-
let fm = {};
|
|
25102
|
-
try {
|
|
25103
|
-
fm = parseFrontmatter(args.content).frontmatter;
|
|
25104
|
-
} catch {}
|
|
25105
|
-
return {
|
|
25106
|
-
name: clip(typeof fm.name === "string" && fm.name.trim().length > 0 ? fm.name.trim() : args.slug, MAX_SKILL_NAME),
|
|
25107
|
-
description: clip(typeof fm.description === "string" && fm.description.trim().length > 0 ? fm.description.trim() : `Task-injected context skill (${args.slug})`, MAX_SKILL_DESCRIPTION),
|
|
25108
|
-
filePath: args.filePath,
|
|
25109
|
-
baseDir: args.dir,
|
|
25110
|
-
sourceInfo: createSyntheticSourceInfo(args.filePath, {
|
|
25111
|
-
source: "moltnet:task-context",
|
|
25112
|
-
scope: "temporary",
|
|
25113
|
-
origin: "top-level",
|
|
25114
|
-
baseDir: args.dir
|
|
25115
|
-
}),
|
|
25116
|
-
disableModelInvocation: fm["disable-model-invocation"] === true
|
|
25117
|
-
};
|
|
25118
|
-
}
|
|
25119
|
-
function clip(s, max) {
|
|
25120
|
-
return s.length > max ? s.slice(0, max) : s;
|
|
25121
|
-
}
|
|
25122
|
-
//#endregion
|
|
25123
25214
|
//#region src/runtime/resolve-prior-context.ts
|
|
25124
25215
|
/**
|
|
25125
25216
|
* Fetch the named attempt's output and project it into the prompt's
|
|
@@ -25306,6 +25397,113 @@ async function withTimeout(promise, timeoutMs, onTimeout) {
|
|
|
25306
25397
|
}
|
|
25307
25398
|
}
|
|
25308
25399
|
//#endregion
|
|
25400
|
+
//#region src/runtime/runtime-context.ts
|
|
25401
|
+
/**
|
|
25402
|
+
* Pi-specific runtime context handling.
|
|
25403
|
+
*
|
|
25404
|
+
* `@themoltnet/agent-runtime` owns generic context semantics: merge profile
|
|
25405
|
+
* defaults with task context, resolve bindings, and produce prompt fragments.
|
|
25406
|
+
* This module owns the Pi/Gondolin boundary: validate effective context for an
|
|
25407
|
+
* attempt, write skill/context files into the VM, and build synthetic Pi Skill
|
|
25408
|
+
* metadata for injected skill bindings.
|
|
25409
|
+
*/
|
|
25410
|
+
/**
|
|
25411
|
+
* Where in the VM we write runtime-context bodies — the memory-backed mount
|
|
25412
|
+
* declared in `vm-manager.ts`. See the comment on
|
|
25413
|
+
* `GUEST_TASK_CONTEXT_MOUNT` there for the full rationale (ephemeral by
|
|
25414
|
+
* intent + the worktree symlink interaction with Gondolin's sandbox-escape
|
|
25415
|
+
* protection). The agent's Gondolin Read tool accepts paths under this mount
|
|
25416
|
+
* via `toGuestPath` in `tool-operations.ts`.
|
|
25417
|
+
*/
|
|
25418
|
+
var SKILL_ROOT_IN_VM = `${GUEST_TASK_CONTEXT_MOUNT}/skills`;
|
|
25419
|
+
var INLINE_CONTEXT_ROOT_IN_VM = `${GUEST_TASK_CONTEXT_MOUNT}/context`;
|
|
25420
|
+
/** Bounds borrowed from pi's skill validation; conservative caps so a
|
|
25421
|
+
* malformed SKILL.md doesn't bloat the system prompt. */
|
|
25422
|
+
var MAX_SKILL_NAME = 64;
|
|
25423
|
+
var MAX_SKILL_DESCRIPTION = 1024;
|
|
25424
|
+
function resolveEffectiveRuntimeContext(args) {
|
|
25425
|
+
const taskContext = args.rawTaskContext === void 0 ? [] : args.rawTaskContext;
|
|
25426
|
+
if (!Check(TaskContext, taskContext)) throw new Error(`task.input.context failed TaskContext validation: ${JSON.stringify([...Errors(TaskContext, taskContext)].slice(0, 3))}`);
|
|
25427
|
+
const profileContext = args.runtimeProfileContext ?? [];
|
|
25428
|
+
if (!Check(TaskContext, profileContext)) throw new Error(`runtime profile context failed TaskContext validation: ${JSON.stringify([...Errors(TaskContext, profileContext)].slice(0, 3))}`);
|
|
25429
|
+
return mergeRuntimeProfileContext(profileContext, taskContext);
|
|
25430
|
+
}
|
|
25431
|
+
/**
|
|
25432
|
+
* Resolve effective runtime context and inject the side effects Pi
|
|
25433
|
+
* needs. Safe to call with an empty array — returns an inert result.
|
|
25434
|
+
*/
|
|
25435
|
+
async function injectRuntimeContext(args) {
|
|
25436
|
+
const skills = [];
|
|
25437
|
+
args.guestWorkspace;
|
|
25438
|
+
const resolved = await resolveTaskContext({
|
|
25439
|
+
context: args.context,
|
|
25440
|
+
deliver: {
|
|
25441
|
+
skill: async ({ slug, content }) => {
|
|
25442
|
+
const dir = `${SKILL_ROOT_IN_VM}/${slug}`;
|
|
25443
|
+
const filePath = `${dir}/SKILL.md`;
|
|
25444
|
+
await args.fs.mkdir(dir, { recursive: true });
|
|
25445
|
+
await args.fs.writeFile(filePath, content, { mode: 420 });
|
|
25446
|
+
skills.push(buildSyntheticSkill({
|
|
25447
|
+
slug,
|
|
25448
|
+
content,
|
|
25449
|
+
filePath,
|
|
25450
|
+
dir
|
|
25451
|
+
}));
|
|
25452
|
+
},
|
|
25453
|
+
contextFile: async ({ suggestedFileName, content }) => {
|
|
25454
|
+
await args.fs.mkdir(INLINE_CONTEXT_ROOT_IN_VM, { recursive: true });
|
|
25455
|
+
const filePath = `${INLINE_CONTEXT_ROOT_IN_VM}/${suggestedFileName}`;
|
|
25456
|
+
await args.fs.writeFile(filePath, content, { mode: 420 });
|
|
25457
|
+
}
|
|
25458
|
+
}
|
|
25459
|
+
});
|
|
25460
|
+
return {
|
|
25461
|
+
injected: resolved.injected,
|
|
25462
|
+
skills,
|
|
25463
|
+
systemPromptPrefix: resolved.systemPromptPrefix,
|
|
25464
|
+
userInlineSuffix: resolved.userInlineSuffix
|
|
25465
|
+
};
|
|
25466
|
+
}
|
|
25467
|
+
/**
|
|
25468
|
+
* Build a `Skill` object pi will faithfully render in
|
|
25469
|
+
* `<available_skills>`. We extract `name` and `description` from the
|
|
25470
|
+
* skill content's YAML frontmatter using pi's own `parseFrontmatter`
|
|
25471
|
+
* helper (proper YAML, not a regex hack) and fall back to the slug +
|
|
25472
|
+
* a generic description so a SKILL.md without frontmatter still
|
|
25473
|
+
* renders something meaningful.
|
|
25474
|
+
*
|
|
25475
|
+
* Frontmatter parsing is best-effort: a malformed YAML block is
|
|
25476
|
+
* optional metadata, not a reason to fail the task. We swallow parser
|
|
25477
|
+
* errors and fall back to the slug-derived metadata; the skill body
|
|
25478
|
+
* is unaffected.
|
|
25479
|
+
*
|
|
25480
|
+
* pi's `formatSkillsForPrompt` only reads `name`, `description`, and
|
|
25481
|
+
* `filePath` — `sourceInfo`/`baseDir` exist on the type but never
|
|
25482
|
+
* surface in the prompt, so a synthetic `SourceInfo` is enough.
|
|
25483
|
+
*/
|
|
25484
|
+
function buildSyntheticSkill(args) {
|
|
25485
|
+
let fm = {};
|
|
25486
|
+
try {
|
|
25487
|
+
fm = parseFrontmatter(args.content).frontmatter;
|
|
25488
|
+
} catch {}
|
|
25489
|
+
return {
|
|
25490
|
+
name: clip(typeof fm.name === "string" && fm.name.trim().length > 0 ? fm.name.trim() : args.slug, MAX_SKILL_NAME),
|
|
25491
|
+
description: clip(typeof fm.description === "string" && fm.description.trim().length > 0 ? fm.description.trim() : `Runtime-injected context skill (${args.slug})`, MAX_SKILL_DESCRIPTION),
|
|
25492
|
+
filePath: args.filePath,
|
|
25493
|
+
baseDir: args.dir,
|
|
25494
|
+
sourceInfo: createSyntheticSourceInfo(args.filePath, {
|
|
25495
|
+
source: "moltnet:runtime-context",
|
|
25496
|
+
scope: "temporary",
|
|
25497
|
+
origin: "top-level",
|
|
25498
|
+
baseDir: args.dir
|
|
25499
|
+
}),
|
|
25500
|
+
disableModelInvocation: fm["disable-model-invocation"] === true
|
|
25501
|
+
};
|
|
25502
|
+
}
|
|
25503
|
+
function clip(s, max) {
|
|
25504
|
+
return s.length > max ? s.slice(0, max) : s;
|
|
25505
|
+
}
|
|
25506
|
+
//#endregion
|
|
25309
25507
|
//#region src/runtime/subagent-tool.ts
|
|
25310
25508
|
var SUBAGENT_SUBMIT_TOOL_NAME = "submit_subagent_output";
|
|
25311
25509
|
var DEFAULT_SUBAGENT_SUBMIT_VALIDATION_RETRIES = 2;
|
|
@@ -25841,6 +26039,46 @@ function resolveSubmitTools(taskType, opts = {}) {
|
|
|
25841
26039
|
};
|
|
25842
26040
|
}
|
|
25843
26041
|
//#endregion
|
|
26042
|
+
//#region src/runtime/task-event-emitter.ts
|
|
26043
|
+
var LOG_TRUNCATE_LIMIT = 4 * 1024;
|
|
26044
|
+
async function emitTaskEvent(input) {
|
|
26045
|
+
try {
|
|
26046
|
+
input.onTurnEvent(input.kind, summarizePayloadForLog(input.kind, input.payload));
|
|
26047
|
+
} catch (err) {
|
|
26048
|
+
process.stderr.write(`[emit] onTurnEvent threw for kind="${input.kind}": ${err instanceof Error ? err.message : String(err)}\n`);
|
|
26049
|
+
}
|
|
26050
|
+
try {
|
|
26051
|
+
await input.reporter.record({
|
|
26052
|
+
kind: input.kind,
|
|
26053
|
+
payload: input.payload
|
|
26054
|
+
});
|
|
26055
|
+
} catch (err) {
|
|
26056
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
26057
|
+
input.log(`executePiTask: reporter.record() failed for task ${input.taskId} attempt ${input.attemptN} kind="${input.kind}": ${detail}`);
|
|
26058
|
+
}
|
|
26059
|
+
}
|
|
26060
|
+
function summarizePayloadForLog(kind, payload) {
|
|
26061
|
+
switch (kind) {
|
|
26062
|
+
case "text_delta": {
|
|
26063
|
+
const delta = payload.delta;
|
|
26064
|
+
return { chars: typeof delta === "string" ? delta.length : 0 };
|
|
26065
|
+
}
|
|
26066
|
+
case "tool_call_start": return { tool: payload.tool_name };
|
|
26067
|
+
case "tool_call_end": return {
|
|
26068
|
+
tool: payload.tool_name,
|
|
26069
|
+
is_error: payload.is_error === true,
|
|
26070
|
+
...payload.is_error === true && payload.result !== void 0 ? { result: payload.result } : {}
|
|
26071
|
+
};
|
|
26072
|
+
case "turn_end": return { stop_reason: payload.stop_reason };
|
|
26073
|
+
case "error": return {
|
|
26074
|
+
phase: payload.phase,
|
|
26075
|
+
message: typeof payload.message === "string" ? payload.message.slice(0, LOG_TRUNCATE_LIMIT) : payload.message
|
|
26076
|
+
};
|
|
26077
|
+
case "info": return Object.fromEntries(Object.entries(payload).map(([k, v]) => [k, typeof v === "string" ? v.slice(0, LOG_TRUNCATE_LIMIT) : v]));
|
|
26078
|
+
default: return payload;
|
|
26079
|
+
}
|
|
26080
|
+
}
|
|
26081
|
+
//#endregion
|
|
25844
26082
|
//#region src/runtime/task-workspace.ts
|
|
25845
26083
|
function prepareTaskWorkspace(task, requestedMountPath, executionPlan) {
|
|
25846
26084
|
const branch = executionPlan?.worktreeBranch ?? null;
|
|
@@ -26209,15 +26447,17 @@ async function executePiTask(claimedTask, reporter, opts) {
|
|
|
26209
26447
|
onTurnEvent = noopTurnEventHandler;
|
|
26210
26448
|
}
|
|
26211
26449
|
else onTurnEvent = opts.onTurnEvent ?? noopTurnEventHandler;
|
|
26212
|
-
const emit = (kind, payload) => {
|
|
26213
|
-
|
|
26214
|
-
onTurnEvent(kind, summarizePayloadForLog(kind, payload));
|
|
26215
|
-
} catch (err) {
|
|
26216
|
-
process.stderr.write(`[emit] onTurnEvent threw for kind="${kind}": ${err instanceof Error ? err.message : String(err)}\n`);
|
|
26217
|
-
}
|
|
26218
|
-
return reporter.record({
|
|
26450
|
+
const emit = async (kind, payload) => {
|
|
26451
|
+
await emitTaskEvent({
|
|
26219
26452
|
kind,
|
|
26220
|
-
payload
|
|
26453
|
+
payload,
|
|
26454
|
+
onTurnEvent,
|
|
26455
|
+
reporter,
|
|
26456
|
+
taskId: task.id,
|
|
26457
|
+
attemptN,
|
|
26458
|
+
log: (message) => {
|
|
26459
|
+
process.stderr.write(`${message}\n`);
|
|
26460
|
+
}
|
|
26221
26461
|
});
|
|
26222
26462
|
};
|
|
26223
26463
|
const emitError = async (phase, message, extra = {}) => {
|
|
@@ -26321,6 +26561,21 @@ async function executePiTask(claimedTask, reporter, opts) {
|
|
|
26321
26561
|
message
|
|
26322
26562
|
});
|
|
26323
26563
|
}
|
|
26564
|
+
const rawContext = task.input.context;
|
|
26565
|
+
let effectiveRuntimeContext;
|
|
26566
|
+
try {
|
|
26567
|
+
effectiveRuntimeContext = resolveEffectiveRuntimeContext({
|
|
26568
|
+
rawTaskContext: rawContext,
|
|
26569
|
+
runtimeProfileContext: opts.runtimeProfileContext
|
|
26570
|
+
});
|
|
26571
|
+
} catch (err) {
|
|
26572
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
26573
|
+
await emit("error", {
|
|
26574
|
+
message,
|
|
26575
|
+
phase: "context_resolution"
|
|
26576
|
+
});
|
|
26577
|
+
return makeFailedOutput("context_resolution_failed", message);
|
|
26578
|
+
}
|
|
26324
26579
|
let taskPrompt;
|
|
26325
26580
|
try {
|
|
26326
26581
|
const assembled = buildTaskUserPrompt(task, {
|
|
@@ -26333,7 +26588,8 @@ async function executePiTask(claimedTask, reporter, opts) {
|
|
|
26333
26588
|
source: executionPlan?.workspaceSeed?.source === "producer" ? "producer_copy" : executionPlan?.workspaceAttachment !== void 0 ? "producer_attachment" : void 0
|
|
26334
26589
|
},
|
|
26335
26590
|
extras: opts.promptExtras,
|
|
26336
|
-
priorContext: resolvedPriorContext
|
|
26591
|
+
priorContext: resolvedPriorContext,
|
|
26592
|
+
effectiveRuntimeContext
|
|
26337
26593
|
});
|
|
26338
26594
|
taskPrompt = assembled.text;
|
|
26339
26595
|
await emit("info", {
|
|
@@ -26350,13 +26606,10 @@ async function executePiTask(claimedTask, reporter, opts) {
|
|
|
26350
26606
|
});
|
|
26351
26607
|
return makeFailedOutput("prompt_build_failed", message);
|
|
26352
26608
|
}
|
|
26353
|
-
const rawContext = task.input.context;
|
|
26354
26609
|
let injectedContext;
|
|
26355
26610
|
try {
|
|
26356
|
-
|
|
26357
|
-
|
|
26358
|
-
injectedContext = await injectTaskContext({
|
|
26359
|
-
context: contextArray,
|
|
26611
|
+
injectedContext = await injectRuntimeContext({
|
|
26612
|
+
context: effectiveRuntimeContext,
|
|
26360
26613
|
fs: managed.vm.fs,
|
|
26361
26614
|
guestWorkspace: managed.guestWorkspace
|
|
26362
26615
|
});
|
|
@@ -26948,27 +27201,6 @@ function wireSessionAbort(cancelSignal, session) {
|
|
|
26948
27201
|
* `task_messages.payload` row. Bodies above 4 KiB are replaced with a
|
|
26949
27202
|
* `{ truncated, original_size }` marker so the JSONL/DB size stays bounded.
|
|
26950
27203
|
*/
|
|
26951
|
-
function summarizePayloadForLog(kind, payload) {
|
|
26952
|
-
switch (kind) {
|
|
26953
|
-
case "text_delta": {
|
|
26954
|
-
const delta = payload.delta;
|
|
26955
|
-
return { chars: typeof delta === "string" ? delta.length : 0 };
|
|
26956
|
-
}
|
|
26957
|
-
case "tool_call_start": return { tool: payload.tool_name };
|
|
26958
|
-
case "tool_call_end": return {
|
|
26959
|
-
tool: payload.tool_name,
|
|
26960
|
-
is_error: payload.is_error === true,
|
|
26961
|
-
...payload.is_error === true && payload.result !== void 0 ? { result: payload.result } : {}
|
|
26962
|
-
};
|
|
26963
|
-
case "turn_end": return { stop_reason: payload.stop_reason };
|
|
26964
|
-
case "error": return {
|
|
26965
|
-
phase: payload.phase,
|
|
26966
|
-
message: typeof payload.message === "string" ? payload.message.slice(0, TRUNCATE_LIMIT) : payload.message
|
|
26967
|
-
};
|
|
26968
|
-
case "info": return Object.fromEntries(Object.entries(payload).map(([k, v]) => [k, typeof v === "string" ? v.slice(0, TRUNCATE_LIMIT) : v]));
|
|
26969
|
-
default: return payload;
|
|
26970
|
-
}
|
|
26971
|
-
}
|
|
26972
27204
|
/**
|
|
26973
27205
|
* Classify a `tool_execution_end` event for telemetry purposes.
|
|
26974
27206
|
*
|
|
@@ -27565,4 +27797,4 @@ function moltnetExtension(pi) {
|
|
|
27565
27797
|
registerMoltnetReflectCommand(pi, state);
|
|
27566
27798
|
}
|
|
27567
27799
|
//#endregion
|
|
27568
|
-
export { HOST_EXEC_DEFAULT_BASE_ENV, activateAgentEnv, buildAgentSession, createGondolinBashOps, createGondolinEditOps, createGondolinReadOps, createGondolinWriteOps, createMoltNetTools, createPiOtelExtension, createPiProviderErrorRetryUi, createPiRetryTriage, createPiTaskExecutor, createSubagentTool, moltnetExtension as default, ensureSnapshot, executePiTask, findMainWorktree, injectTaskContext, loadCredentials, normalizeRetryTriageResult, redactRetryTriageSecrets, resolveTaskWorktreePath, resumeVm, toGuestPath };
|
|
27800
|
+
export { HOST_EXEC_DEFAULT_BASE_ENV, activateAgentEnv, buildAgentSession, createGondolinBashOps, createGondolinEditOps, createGondolinReadOps, createGondolinWriteOps, createMoltNetTools, createPiOtelExtension, createPiProviderErrorRetryUi, createPiRetryTriage, createPiTaskExecutor, createSubagentTool, moltnetExtension as default, ensureSnapshot, executePiTask, findMainWorktree, injectRuntimeContext as injectTaskContext, loadCredentials, normalizeRetryTriageResult, redactRetryTriageSecrets, resolveTaskWorktreePath, resumeVm, toGuestPath };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@themoltnet/pi-extension",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.34.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.35.
|
|
40
|
-
"@themoltnet/sdk": "0.
|
|
39
|
+
"@themoltnet/agent-runtime": "0.35.2",
|
|
40
|
+
"@themoltnet/sdk": "0.120.0"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
43
|
"@earendil-works/pi-coding-agent": ">=0.74.0",
|