@themoltnet/pi-extension 0.33.1 → 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 +10 -2
- package/dist/index.js +225 -36
- 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
|
@@ -766,6 +766,14 @@ export declare interface SandboxConfig {
|
|
|
766
766
|
/** Overlay disk size (default '3G'). */
|
|
767
767
|
overlaySize?: string;
|
|
768
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
|
+
};
|
|
769
777
|
/** Shell commands to run every VM resume, after platform setup
|
|
770
778
|
* (TLS, DNS, git safe.directory, tmpfs node_modules) and before
|
|
771
779
|
* the agent session starts. Use for per-session bootstrap that
|
|
@@ -887,7 +895,7 @@ declare const Task: Type.TObject<{
|
|
|
887
895
|
inputCid: Type.TString;
|
|
888
896
|
references: Type.TArray<Type.TObject<{
|
|
889
897
|
taskId: Type.TUnion<[Type.TString, Type.TNull]>;
|
|
890
|
-
outputCid: Type.TString
|
|
898
|
+
outputCid: Type.TOptional<Type.TString>;
|
|
891
899
|
role: Type.TUnion<[Type.TLiteral<"judged_work">, Type.TLiteral<"reviewed_diff">, Type.TLiteral<"target_source">, Type.TLiteral<"context">]>;
|
|
892
900
|
external: Type.TOptional<Type.TObject<{
|
|
893
901
|
kind: Type.TUnion<[Type.TLiteral<"github_pr">, Type.TLiteral<"github_issue">, Type.TLiteral<"http_url">]>;
|
|
@@ -899,7 +907,7 @@ declare const Task: Type.TObject<{
|
|
|
899
907
|
}>>;
|
|
900
908
|
artifact: Type.TOptional<Type.TObject<{
|
|
901
909
|
cid: Type.TString;
|
|
902
|
-
attemptN: Type.TInteger
|
|
910
|
+
attemptN: Type.TOptional<Type.TInteger>;
|
|
903
911
|
kind: Type.TOptional<Type.TString>;
|
|
904
912
|
title: Type.TOptional<Type.TString>;
|
|
905
913
|
contentType: Type.TOptional<Type.TString>;
|
package/dist/index.js
CHANGED
|
@@ -1965,6 +1965,33 @@ var findLatestRuntimeSlotForAttempt = (options) => (options.client ?? client).ge
|
|
|
1965
1965
|
...options
|
|
1966
1966
|
});
|
|
1967
1967
|
/**
|
|
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
|
+
/**
|
|
1968
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({
|
|
@@ -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({
|
|
@@ -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)) {
|
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",
|