@themoltnet/pi-extension 0.3.0 → 0.5.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.js CHANGED
@@ -4,7 +4,7 @@ import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync } fr
4
4
  import path, { join } from "node:path";
5
5
  import { DefaultResourceLoader, SessionManager, createAgentSession, createBashTool, createBashToolDefinition, createEditTool, createEditToolDefinition, createReadTool, createReadToolDefinition, createWriteTool, createWriteToolDefinition, defineTool } from "@mariozechner/pi-coding-agent";
6
6
  import { createHash, randomUUID } from "node:crypto";
7
- import { createHash as createHash$1 } from "crypto";
7
+ import crypto, { createHash as createHash$1 } from "crypto";
8
8
  import { readFile } from "node:fs/promises";
9
9
  import { homedir } from "node:os";
10
10
  import { Type, complete, getModel } from "@mariozechner/pi-ai";
@@ -13,7 +13,6 @@ import { RealFSProvider, ShadowProvider, VM, VmCheckpoint, createHttpHooks, crea
13
13
  import { parseEnv } from "node:util";
14
14
  import { FormatRegistry, Type as Type$1 } from "@sinclair/typebox";
15
15
  import { Value } from "@sinclair/typebox/value";
16
- import { TypeCompiler } from "@sinclair/typebox/compiler";
17
16
  //#region ../api-client/src/generated/core/bodySerializer.gen.ts
18
17
  var jsonBodySerializer = { bodySerializer: (body) => JSON.stringify(body, (_key, value) => typeof value === "bigint" ? value.toString() : value) };
19
18
  Object.entries({
@@ -2039,6 +2038,276 @@ var getLegreffierOnboardingStatus = (options) => (options.client ?? client).get(
2039
2038
  ...options
2040
2039
  });
2041
2040
  /**
2041
+ * List tasks for a team with optional filters.
2042
+ */
2043
+ var listTasks = (options) => (options.client ?? client).get({
2044
+ security: [
2045
+ {
2046
+ scheme: "bearer",
2047
+ type: "http"
2048
+ },
2049
+ {
2050
+ name: "X-Moltnet-Session-Token",
2051
+ type: "apiKey"
2052
+ },
2053
+ {
2054
+ in: "cookie",
2055
+ name: "ory_kratos_session",
2056
+ type: "apiKey"
2057
+ }
2058
+ ],
2059
+ url: "/tasks",
2060
+ ...options
2061
+ });
2062
+ /**
2063
+ * Create and enqueue a new task.
2064
+ */
2065
+ var createTask = (options) => (options.client ?? client).post({
2066
+ security: [
2067
+ {
2068
+ scheme: "bearer",
2069
+ type: "http"
2070
+ },
2071
+ {
2072
+ name: "X-Moltnet-Session-Token",
2073
+ type: "apiKey"
2074
+ },
2075
+ {
2076
+ in: "cookie",
2077
+ name: "ory_kratos_session",
2078
+ type: "apiKey"
2079
+ }
2080
+ ],
2081
+ url: "/tasks",
2082
+ ...options,
2083
+ headers: {
2084
+ "Content-Type": "application/json",
2085
+ ...options.headers
2086
+ }
2087
+ });
2088
+ /**
2089
+ * Get a task by ID.
2090
+ */
2091
+ var getTask = (options) => (options.client ?? client).get({
2092
+ security: [
2093
+ {
2094
+ scheme: "bearer",
2095
+ type: "http"
2096
+ },
2097
+ {
2098
+ name: "X-Moltnet-Session-Token",
2099
+ type: "apiKey"
2100
+ },
2101
+ {
2102
+ in: "cookie",
2103
+ name: "ory_kratos_session",
2104
+ type: "apiKey"
2105
+ }
2106
+ ],
2107
+ url: "/tasks/{id}",
2108
+ ...options
2109
+ });
2110
+ /**
2111
+ * Claim a queued task and start an attempt.
2112
+ */
2113
+ var claimTask = (options) => (options.client ?? client).post({
2114
+ security: [
2115
+ {
2116
+ scheme: "bearer",
2117
+ type: "http"
2118
+ },
2119
+ {
2120
+ name: "X-Moltnet-Session-Token",
2121
+ type: "apiKey"
2122
+ },
2123
+ {
2124
+ in: "cookie",
2125
+ name: "ory_kratos_session",
2126
+ type: "apiKey"
2127
+ }
2128
+ ],
2129
+ url: "/tasks/{id}/claim",
2130
+ ...options,
2131
+ headers: {
2132
+ "Content-Type": "application/json",
2133
+ ...options.headers
2134
+ }
2135
+ });
2136
+ /**
2137
+ * Send a heartbeat to keep the attempt lease alive.
2138
+ */
2139
+ var taskHeartbeat = (options) => (options.client ?? client).post({
2140
+ security: [
2141
+ {
2142
+ scheme: "bearer",
2143
+ type: "http"
2144
+ },
2145
+ {
2146
+ name: "X-Moltnet-Session-Token",
2147
+ type: "apiKey"
2148
+ },
2149
+ {
2150
+ in: "cookie",
2151
+ name: "ory_kratos_session",
2152
+ type: "apiKey"
2153
+ }
2154
+ ],
2155
+ url: "/tasks/{id}/attempts/{n}/heartbeat",
2156
+ ...options,
2157
+ headers: {
2158
+ "Content-Type": "application/json",
2159
+ ...options.headers
2160
+ }
2161
+ });
2162
+ /**
2163
+ * Mark an attempt as completed with output.
2164
+ */
2165
+ var completeTask = (options) => (options.client ?? client).post({
2166
+ security: [
2167
+ {
2168
+ scheme: "bearer",
2169
+ type: "http"
2170
+ },
2171
+ {
2172
+ name: "X-Moltnet-Session-Token",
2173
+ type: "apiKey"
2174
+ },
2175
+ {
2176
+ in: "cookie",
2177
+ name: "ory_kratos_session",
2178
+ type: "apiKey"
2179
+ }
2180
+ ],
2181
+ url: "/tasks/{id}/attempts/{n}/complete",
2182
+ ...options,
2183
+ headers: {
2184
+ "Content-Type": "application/json",
2185
+ ...options.headers
2186
+ }
2187
+ });
2188
+ /**
2189
+ * Mark an attempt as failed with error details.
2190
+ */
2191
+ var failTask = (options) => (options.client ?? client).post({
2192
+ security: [
2193
+ {
2194
+ scheme: "bearer",
2195
+ type: "http"
2196
+ },
2197
+ {
2198
+ name: "X-Moltnet-Session-Token",
2199
+ type: "apiKey"
2200
+ },
2201
+ {
2202
+ in: "cookie",
2203
+ name: "ory_kratos_session",
2204
+ type: "apiKey"
2205
+ }
2206
+ ],
2207
+ url: "/tasks/{id}/attempts/{n}/fail",
2208
+ ...options,
2209
+ headers: {
2210
+ "Content-Type": "application/json",
2211
+ ...options.headers
2212
+ }
2213
+ });
2214
+ /**
2215
+ * Cancel a task.
2216
+ */
2217
+ var cancelTask = (options) => (options.client ?? client).post({
2218
+ security: [
2219
+ {
2220
+ scheme: "bearer",
2221
+ type: "http"
2222
+ },
2223
+ {
2224
+ name: "X-Moltnet-Session-Token",
2225
+ type: "apiKey"
2226
+ },
2227
+ {
2228
+ in: "cookie",
2229
+ name: "ory_kratos_session",
2230
+ type: "apiKey"
2231
+ }
2232
+ ],
2233
+ url: "/tasks/{id}/cancel",
2234
+ ...options,
2235
+ headers: {
2236
+ "Content-Type": "application/json",
2237
+ ...options.headers
2238
+ }
2239
+ });
2240
+ /**
2241
+ * List all attempts for a task.
2242
+ */
2243
+ var listTaskAttempts = (options) => (options.client ?? client).get({
2244
+ security: [
2245
+ {
2246
+ scheme: "bearer",
2247
+ type: "http"
2248
+ },
2249
+ {
2250
+ name: "X-Moltnet-Session-Token",
2251
+ type: "apiKey"
2252
+ },
2253
+ {
2254
+ in: "cookie",
2255
+ name: "ory_kratos_session",
2256
+ type: "apiKey"
2257
+ }
2258
+ ],
2259
+ url: "/tasks/{id}/attempts",
2260
+ ...options
2261
+ });
2262
+ /**
2263
+ * List messages for a task attempt.
2264
+ */
2265
+ var listTaskMessages = (options) => (options.client ?? client).get({
2266
+ security: [
2267
+ {
2268
+ scheme: "bearer",
2269
+ type: "http"
2270
+ },
2271
+ {
2272
+ name: "X-Moltnet-Session-Token",
2273
+ type: "apiKey"
2274
+ },
2275
+ {
2276
+ in: "cookie",
2277
+ name: "ory_kratos_session",
2278
+ type: "apiKey"
2279
+ }
2280
+ ],
2281
+ url: "/tasks/{id}/attempts/{n}/messages",
2282
+ ...options
2283
+ });
2284
+ /**
2285
+ * Append messages to a task attempt.
2286
+ */
2287
+ var appendTaskMessages = (options) => (options.client ?? client).post({
2288
+ security: [
2289
+ {
2290
+ scheme: "bearer",
2291
+ type: "http"
2292
+ },
2293
+ {
2294
+ name: "X-Moltnet-Session-Token",
2295
+ type: "apiKey"
2296
+ },
2297
+ {
2298
+ in: "cookie",
2299
+ name: "ory_kratos_session",
2300
+ type: "apiKey"
2301
+ }
2302
+ ],
2303
+ url: "/tasks/{id}/attempts/{n}/messages",
2304
+ ...options,
2305
+ headers: {
2306
+ "Content-Type": "application/json",
2307
+ ...options.headers
2308
+ }
2309
+ });
2310
+ /**
2042
2311
  * List all problem types used in API error responses (RFC 9457).
2043
2312
  */
2044
2313
  var listProblemTypes = (options) => (options?.client ?? client).get({
@@ -2810,7 +3079,7 @@ K512[1];
2810
3079
  * To break sha256 using birthday attack, attackers need to try 2^128 hashes.
2811
3080
  * BTC network is doing 2^70 hashes/sec (2^95 hashes/year) as per 2025.
2812
3081
  */
2813
- var sha256 = /* @__PURE__ */ createHasher(() => new SHA256());
3082
+ var sha256$1 = /* @__PURE__ */ createHasher(() => new SHA256());
2814
3083
  //#endregion
2815
3084
  //#region ../../node_modules/.pnpm/multiformats@13.4.2/node_modules/multiformats/dist/src/bytes.js
2816
3085
  function equals$1(aa, bb) {
@@ -3027,12 +3296,12 @@ var Codec = class {
3027
3296
  return this.decoder.decode(input);
3028
3297
  }
3029
3298
  };
3030
- function from({ name, prefix, encode, decode }) {
3299
+ function from$1({ name, prefix, encode, decode }) {
3031
3300
  return new Codec(name, prefix, encode, decode);
3032
3301
  }
3033
3302
  function baseX({ name, prefix, alphabet }) {
3034
3303
  const { encode, decode } = _brrp__multiformats_scope_baseX(alphabet, name);
3035
- return from({
3304
+ return from$1({
3036
3305
  prefix,
3037
3306
  name,
3038
3307
  encode,
@@ -3059,7 +3328,7 @@ function decode$3(string, alphabetIdx, bitsPerChar, name) {
3059
3328
  if (bits >= bitsPerChar || (255 & buffer << 8 - bits) !== 0) throw new SyntaxError("Unexpected end of data");
3060
3329
  return out;
3061
3330
  }
3062
- function encode$1(data, alphabet, bitsPerChar) {
3331
+ function encode$2(data, alphabet, bitsPerChar) {
3063
3332
  const pad = alphabet[alphabet.length - 1] === "=";
3064
3333
  const mask = (1 << bitsPerChar) - 1;
3065
3334
  let out = "";
@@ -3087,11 +3356,11 @@ function createAlphabetIdx(alphabet) {
3087
3356
  */
3088
3357
  function rfc4648({ name, prefix, bitsPerChar, alphabet }) {
3089
3358
  const alphabetIdx = createAlphabetIdx(alphabet);
3090
- return from({
3359
+ return from$1({
3091
3360
  prefix,
3092
3361
  name,
3093
3362
  encode(input) {
3094
- return encode$1(input, alphabet, bitsPerChar);
3363
+ return encode$2(input, alphabet, bitsPerChar);
3095
3364
  },
3096
3365
  decode(input) {
3097
3366
  return decode$3(input, alphabetIdx, bitsPerChar, name);
@@ -3180,14 +3449,14 @@ baseX({
3180
3449
  });
3181
3450
  //#endregion
3182
3451
  //#region ../../node_modules/.pnpm/multiformats@13.4.2/node_modules/multiformats/dist/src/vendor/varint.js
3183
- var encode_1 = encode;
3452
+ var encode_1 = encode$1;
3184
3453
  var MSB = 128, MSBALL = -128, INT = Math.pow(2, 31);
3185
3454
  /**
3186
3455
  * @param {number} num
3187
3456
  * @param {number[]} out
3188
3457
  * @param {number} offset
3189
3458
  */
3190
- function encode(num, out, offset) {
3459
+ function encode$1(num, out, offset) {
3191
3460
  out = out || [];
3192
3461
  offset = offset || 0;
3193
3462
  var oldOffset = offset;
@@ -3200,7 +3469,7 @@ function encode(num, out, offset) {
3200
3469
  num >>>= 7;
3201
3470
  }
3202
3471
  out[offset] = num | 0;
3203
- encode.bytes = offset - oldOffset + 1;
3472
+ encode$1.bytes = offset - oldOffset + 1;
3204
3473
  return out;
3205
3474
  }
3206
3475
  var decode$2 = read;
@@ -3623,7 +3892,7 @@ function buildCanonicalInput(entryType, title, content, tags) {
3623
3892
  */
3624
3893
  function computeCanonicalHash(entryType, title, content, tags) {
3625
3894
  const input = buildCanonicalInput(entryType, title, content, tags);
3626
- return sha256(new TextEncoder().encode(input));
3895
+ return sha256$1(new TextEncoder().encode(input));
3627
3896
  }
3628
3897
  /**
3629
3898
  * Compute a CIDv1 content identifier for a diary entry.
@@ -4070,25 +4339,104 @@ etc.sha512Sync = (...m) => {
4070
4339
  return hash.digest();
4071
4340
  };
4072
4341
  //#endregion
4073
- //#region ../../node_modules/.pnpm/cborg@4.5.8/node_modules/cborg/lib/is.js
4074
- var objectTypeNames = [
4075
- "Object",
4076
- "RegExp",
4077
- "Date",
4078
- "Error",
4079
- "Map",
4080
- "Set",
4081
- "WeakMap",
4082
- "WeakSet",
4083
- "ArrayBuffer",
4084
- "SharedArrayBuffer",
4085
- "DataView",
4086
- "Promise",
4087
- "URL",
4088
- "HTMLElement",
4089
- "Int8Array",
4090
- "Uint8ClampedArray",
4091
- "Int16Array",
4342
+ //#region ../../node_modules/.pnpm/multiformats@13.4.2/node_modules/multiformats/dist/src/codecs/json.js
4343
+ var textEncoder$1 = new TextEncoder();
4344
+ new TextDecoder();
4345
+ function encode(node) {
4346
+ return textEncoder$1.encode(JSON.stringify(node));
4347
+ }
4348
+ //#endregion
4349
+ //#region ../../node_modules/.pnpm/multiformats@13.4.2/node_modules/multiformats/dist/src/hashes/hasher.js
4350
+ var DEFAULT_MIN_DIGEST_LENGTH = 20;
4351
+ function from({ name, code, encode, minDigestLength, maxDigestLength }) {
4352
+ return new Hasher(name, code, encode, minDigestLength, maxDigestLength);
4353
+ }
4354
+ /**
4355
+ * Hasher represents a hashing algorithm implementation that produces as
4356
+ * `MultihashDigest`.
4357
+ */
4358
+ var Hasher = class {
4359
+ name;
4360
+ code;
4361
+ encode;
4362
+ minDigestLength;
4363
+ maxDigestLength;
4364
+ constructor(name, code, encode, minDigestLength, maxDigestLength) {
4365
+ this.name = name;
4366
+ this.code = code;
4367
+ this.encode = encode;
4368
+ this.minDigestLength = minDigestLength ?? DEFAULT_MIN_DIGEST_LENGTH;
4369
+ this.maxDigestLength = maxDigestLength;
4370
+ }
4371
+ digest(input, options) {
4372
+ if (options?.truncate != null) {
4373
+ if (options.truncate < this.minDigestLength) throw new Error(`Invalid truncate option, must be greater than or equal to ${this.minDigestLength}`);
4374
+ if (this.maxDigestLength != null && options.truncate > this.maxDigestLength) throw new Error(`Invalid truncate option, must be less than or equal to ${this.maxDigestLength}`);
4375
+ }
4376
+ if (input instanceof Uint8Array) {
4377
+ const result = this.encode(input);
4378
+ if (result instanceof Uint8Array) return createDigest(result, this.code, options?.truncate);
4379
+ return result.then((digest) => createDigest(digest, this.code, options?.truncate));
4380
+ } else throw Error("Unknown type, must be binary type");
4381
+ }
4382
+ };
4383
+ /**
4384
+ * Create a Digest from the passed uint8array and code, optionally truncating it
4385
+ * first.
4386
+ */
4387
+ function createDigest(digest, code, truncate) {
4388
+ if (truncate != null && truncate !== digest.byteLength) {
4389
+ if (truncate > digest.byteLength) throw new Error(`Invalid truncate option, must be less than or equal to ${digest.byteLength}`);
4390
+ digest = digest.subarray(0, truncate);
4391
+ }
4392
+ return create(code, digest);
4393
+ }
4394
+ //#endregion
4395
+ //#region ../../node_modules/.pnpm/multiformats@13.4.2/node_modules/multiformats/dist/src/hashes/sha2.js
4396
+ var sha256 = from({
4397
+ name: "sha2-256",
4398
+ code: 18,
4399
+ encode: (input) => coerce(crypto.createHash("sha256").update(input).digest())
4400
+ });
4401
+ from({
4402
+ name: "sha2-512",
4403
+ code: 19,
4404
+ encode: (input) => coerce(crypto.createHash("sha512").update(input).digest())
4405
+ });
4406
+ //#endregion
4407
+ //#region ../crypto-service/src/json-cid.ts
4408
+ /**
4409
+ * Generic JSON CID — CIDv1 for arbitrary JSON-serialisable values.
4410
+ *
4411
+ * Uses the dag-json codec and sha2-256, producing a base32lower CIDv1.
4412
+ * Suitable for content-addressing task inputs, schema objects, and other
4413
+ * JSON payloads that don't need diary-entry canonical normalisation.
4414
+ */
4415
+ async function computeJsonCid(value) {
4416
+ const bytes = encode(value);
4417
+ const hash = await sha256.digest(bytes);
4418
+ return CID.create(1, 512, hash).toString();
4419
+ }
4420
+ //#endregion
4421
+ //#region ../../node_modules/.pnpm/cborg@4.5.8/node_modules/cborg/lib/is.js
4422
+ var objectTypeNames = [
4423
+ "Object",
4424
+ "RegExp",
4425
+ "Date",
4426
+ "Error",
4427
+ "Map",
4428
+ "Set",
4429
+ "WeakMap",
4430
+ "WeakSet",
4431
+ "ArrayBuffer",
4432
+ "SharedArrayBuffer",
4433
+ "DataView",
4434
+ "Promise",
4435
+ "URL",
4436
+ "HTMLElement",
4437
+ "Int8Array",
4438
+ "Uint8ClampedArray",
4439
+ "Int16Array",
4092
4440
  "Uint16Array",
4093
4441
  "Int32Array",
4094
4442
  "Uint32Array",
@@ -6244,6 +6592,112 @@ function createSigningRequestsNamespace(context) {
6244
6592
  };
6245
6593
  }
6246
6594
  //#endregion
6595
+ //#region ../sdk/src/namespaces/tasks.ts
6596
+ function createTasksNamespace(context) {
6597
+ const { client, auth } = context;
6598
+ return {
6599
+ async list(query) {
6600
+ return unwrapResult(await listTasks({
6601
+ client,
6602
+ auth,
6603
+ query
6604
+ }));
6605
+ },
6606
+ async create(body) {
6607
+ return unwrapResult(await createTask({
6608
+ client,
6609
+ auth,
6610
+ body
6611
+ }));
6612
+ },
6613
+ async get(id) {
6614
+ return unwrapResult(await getTask({
6615
+ client,
6616
+ auth,
6617
+ path: { id }
6618
+ }));
6619
+ },
6620
+ async claim(id, body) {
6621
+ return unwrapResult(await claimTask({
6622
+ client,
6623
+ auth,
6624
+ path: { id },
6625
+ body
6626
+ }));
6627
+ },
6628
+ async heartbeat(id, n, body) {
6629
+ return unwrapResult(await taskHeartbeat({
6630
+ client,
6631
+ auth,
6632
+ path: {
6633
+ id,
6634
+ n
6635
+ },
6636
+ body
6637
+ }));
6638
+ },
6639
+ async complete(id, n, body) {
6640
+ return unwrapResult(await completeTask({
6641
+ client,
6642
+ auth,
6643
+ path: {
6644
+ id,
6645
+ n
6646
+ },
6647
+ body
6648
+ }));
6649
+ },
6650
+ async fail(id, n, body) {
6651
+ return unwrapResult(await failTask({
6652
+ client,
6653
+ auth,
6654
+ path: {
6655
+ id,
6656
+ n
6657
+ },
6658
+ body
6659
+ }));
6660
+ },
6661
+ async cancel(id, body) {
6662
+ return unwrapResult(await cancelTask({
6663
+ client,
6664
+ auth,
6665
+ path: { id },
6666
+ body
6667
+ }));
6668
+ },
6669
+ async listAttempts(id) {
6670
+ return unwrapResult(await listTaskAttempts({
6671
+ client,
6672
+ auth,
6673
+ path: { id }
6674
+ }));
6675
+ },
6676
+ async listMessages(id, n, query) {
6677
+ return unwrapResult(await listTaskMessages({
6678
+ client,
6679
+ auth,
6680
+ path: {
6681
+ id,
6682
+ n
6683
+ },
6684
+ query
6685
+ }));
6686
+ },
6687
+ async appendMessages(id, n, body) {
6688
+ return unwrapResult(await appendTaskMessages({
6689
+ client,
6690
+ auth,
6691
+ path: {
6692
+ id,
6693
+ n
6694
+ },
6695
+ body
6696
+ }));
6697
+ }
6698
+ };
6699
+ }
6700
+ //#endregion
6247
6701
  //#region ../sdk/src/namespaces/teams.ts
6248
6702
  function createTeamsNamespace(context) {
6249
6703
  const { client, auth } = context;
@@ -6375,6 +6829,7 @@ function createAgent(options) {
6375
6829
  legreffier: createLegreffierNamespace(context),
6376
6830
  problems: createProblemsNamespace(context),
6377
6831
  teams: createTeamsNamespace(context),
6832
+ tasks: createTasksNamespace(context),
6378
6833
  client,
6379
6834
  getToken: () => tokenManager.getToken()
6380
6835
  };
@@ -7113,6 +7568,24 @@ function renderPhase6Markdown(pack) {
7113
7568
  * These tools run on the host (not in the VM) via the MoltNet SDK,
7114
7569
  * so agent credentials never touch the VM filesystem.
7115
7570
  */
7571
+ /**
7572
+ * Baseline env keys forwarded to host-exec child processes.
7573
+ * Callers can extend this set at sandbox startup via `MoltNetToolsConfig.hostExecBaseEnv`.
7574
+ */
7575
+ var HOST_EXEC_DEFAULT_BASE_ENV = new Set([
7576
+ "PATH",
7577
+ "HOME",
7578
+ "LANG",
7579
+ "LC_ALL",
7580
+ "TMPDIR",
7581
+ "GIT_CONFIG_GLOBAL",
7582
+ "MOLTNET_CREDENTIALS_PATH",
7583
+ "GIT_AUTHOR_NAME",
7584
+ "GIT_AUTHOR_EMAIL",
7585
+ "GIT_COMMITTER_NAME",
7586
+ "GIT_COMMITTER_EMAIL",
7587
+ "SSH_AUTH_SOCK"
7588
+ ]);
7116
7589
  function ensureConnected(config) {
7117
7590
  const agent = config.getAgent();
7118
7591
  const diaryId = config.getDiaryId();
@@ -7126,303 +7599,548 @@ function ensureConnected(config) {
7126
7599
  * Create all MoltNet tool definitions, ready to pass to `pi.registerTool()`.
7127
7600
  */
7128
7601
  function createMoltNetTools(config) {
7129
- return [
7130
- defineTool({
7131
- name: "moltnet_pack_get",
7132
- label: "Get MoltNet Pack",
7133
- description: "Get a context pack by ID. Optionally expand included entries.",
7134
- parameters: Type.Object({
7135
- packId: Type.String({ description: "Context pack ID" }),
7136
- expandEntries: Type.Optional(Type.Boolean({ description: "Include full expanded entries" }))
7137
- }),
7138
- async execute(_id, params) {
7139
- const { agent } = ensureConnected(config);
7140
- const pack = await agent.packs.get(params.packId, { expand: params.expandEntries ? "entries" : void 0 });
7141
- return {
7142
- content: [{
7143
- type: "text",
7144
- text: JSON.stringify(pack, null, 2)
7145
- }],
7146
- details: {}
7147
- };
7148
- }
7602
+ const getPack = defineTool({
7603
+ name: "moltnet_pack_get",
7604
+ label: "Get MoltNet Pack",
7605
+ description: "Get a context pack by ID. Optionally expand included entries.",
7606
+ parameters: Type.Object({
7607
+ packId: Type.String({ description: "Context pack ID" }),
7608
+ expandEntries: Type.Optional(Type.Boolean({ description: "Include full expanded entries" }))
7149
7609
  }),
7150
- defineTool({
7151
- name: "moltnet_pack_create",
7152
- label: "Create MoltNet Pack",
7153
- description: "Persist a curated context pack. Entries are caller-ranked (lower rank = more prominent). Recipe/prompt/selection_rationale belong in params. Defaults to pinned=false — packs in the attribution pipeline are ephemeral unless the caller explicitly opts in.",
7154
- parameters: Type.Object({
7155
- entries: Type.Array(Type.Object({
7156
- entryId: Type.String({ description: "Diary entry UUID" }),
7157
- rank: Type.Number({ description: "Rank (1..N, lower = more prominent)" })
7158
- }), { description: "Selected entries with their ranks" }),
7159
- params: Type.Optional(Type.Record(Type.String(), Type.Unknown(), { description: "Free-form recipe parameters (recipe name, prompt, selection rationale, etc.)" })),
7160
- tokenBudget: Type.Optional(Type.Number({ description: "Soft token budget recorded on the pack (optional)" })),
7161
- pinned: Type.Optional(Type.Boolean({ description: "Pin the pack against retention policy (default false)" }))
7162
- }),
7163
- async execute(_id, params) {
7164
- const { agent, diaryId } = ensureConnected(config);
7165
- const pack = await agent.packs.create(diaryId, {
7166
- packType: "custom",
7167
- params: params.params ?? {},
7168
- entries: params.entries,
7169
- tokenBudget: params.tokenBudget,
7170
- pinned: params.pinned ?? false
7171
- });
7172
- return {
7173
- content: [{
7174
- type: "text",
7175
- text: JSON.stringify(pack, null, 2)
7176
- }],
7177
- details: {}
7178
- };
7179
- }
7610
+ async execute(_id, params) {
7611
+ const { agent } = ensureConnected(config);
7612
+ const pack = await agent.packs.get(params.packId, { expand: params.expandEntries ? "entries" : void 0 });
7613
+ return {
7614
+ content: [{
7615
+ type: "text",
7616
+ text: JSON.stringify(pack, null, 2)
7617
+ }],
7618
+ details: {}
7619
+ };
7620
+ }
7621
+ });
7622
+ const createPack = defineTool({
7623
+ name: "moltnet_pack_create",
7624
+ label: "Create MoltNet Pack",
7625
+ description: "Persist a curated context pack. Entries are caller-ranked (lower rank = more prominent). Recipe/prompt/selection_rationale belong in params. Defaults to pinned=false — packs in the attribution pipeline are ephemeral unless the caller explicitly opts in.",
7626
+ parameters: Type.Object({
7627
+ entries: Type.Array(Type.Object({
7628
+ entryId: Type.String({ description: "Diary entry UUID" }),
7629
+ rank: Type.Number({ description: "Rank (1..N, lower = more prominent)" })
7630
+ }), { description: "Selected entries with their ranks" }),
7631
+ params: Type.Optional(Type.Record(Type.String(), Type.Unknown(), { description: "Free-form recipe parameters (recipe name, prompt, selection rationale, etc.)" })),
7632
+ tokenBudget: Type.Optional(Type.Number({ description: "Soft token budget recorded on the pack (optional)" })),
7633
+ pinned: Type.Optional(Type.Boolean({ description: "Pin the pack against retention policy (default false)" }))
7180
7634
  }),
7181
- defineTool({
7182
- name: "moltnet_pack_provenance",
7183
- label: "Get MoltNet Pack Provenance",
7184
- description: "Get the provenance graph for a context pack by ID or CID.",
7185
- parameters: Type.Object({
7186
- packId: Type.Optional(Type.String({ description: "Context pack ID" })),
7187
- packCid: Type.Optional(Type.String({ description: "Context pack CID" })),
7188
- depth: Type.Optional(Type.Number({ description: "Supersession ancestry depth to include (default 2)" }))
7189
- }),
7190
- async execute(_id, params) {
7191
- const { agent } = ensureConnected(config);
7192
- if (!params.packId && !params.packCid) throw new Error("Provide either packId or packCid");
7193
- if (params.packId && params.packCid) throw new Error("Provide only one of packId or packCid");
7194
- const graph = params.packId ? await agent.packs.getProvenance(params.packId, { depth: params.depth ?? 2 }) : await agent.packs.getProvenanceByCid(params.packCid, { depth: params.depth ?? 2 });
7195
- const payload = {
7196
- metadata: graph.metadata,
7197
- counts: {
7198
- nodes: graph.nodes.length,
7199
- edges: graph.edges.length
7200
- },
7201
- graph
7202
- };
7203
- return {
7204
- content: [{
7205
- type: "text",
7206
- text: JSON.stringify(payload, null, 2)
7207
- }],
7208
- details: {}
7209
- };
7210
- }
7635
+ async execute(_id, params) {
7636
+ const { agent, diaryId } = ensureConnected(config);
7637
+ const pack = await agent.packs.create(diaryId, {
7638
+ packType: "custom",
7639
+ params: params.params ?? {},
7640
+ entries: params.entries,
7641
+ tokenBudget: params.tokenBudget,
7642
+ pinned: params.pinned ?? false
7643
+ });
7644
+ return {
7645
+ content: [{
7646
+ type: "text",
7647
+ text: JSON.stringify(pack, null, 2)
7648
+ }],
7649
+ details: {}
7650
+ };
7651
+ }
7652
+ });
7653
+ const getPackProvenance = defineTool({
7654
+ name: "moltnet_pack_provenance",
7655
+ label: "Get MoltNet Pack Provenance",
7656
+ description: "Get the provenance graph for a context pack by ID or CID.",
7657
+ parameters: Type.Object({
7658
+ packId: Type.Optional(Type.String({ description: "Context pack ID" })),
7659
+ packCid: Type.Optional(Type.String({ description: "Context pack CID" })),
7660
+ depth: Type.Optional(Type.Number({ description: "Supersession ancestry depth to include (default 2)" }))
7211
7661
  }),
7212
- defineTool({
7213
- name: "moltnet_pack_render",
7214
- label: "Render MoltNet Pack",
7215
- description: "Fetch a pack with entries, transform it into docs, then preview or persist the rendered pack.",
7216
- parameters: Type.Object({
7217
- packId: Type.String({ description: "Context pack ID" }),
7218
- renderMethod: Type.Optional(Type.String({ description: "Render method label. Defaults to pi:pack-to-docs-v1" })),
7219
- markdown: Type.Optional(Type.String({ description: "Optional caller-authored markdown override" })),
7220
- preview: Type.Optional(Type.Boolean({ description: "Preview without persisting (default false)" })),
7221
- pinned: Type.Optional(Type.Boolean({ description: "Persist the rendered pack as pinned (default false)" }))
7222
- }),
7223
- async execute(_id, params) {
7224
- const { agent } = ensureConnected(config);
7225
- const renderMethod = params.renderMethod ?? "pi:pack-to-docs-v1";
7226
- let renderedMarkdown = params.markdown;
7227
- if (!renderedMarkdown && !renderMethod.startsWith("server:")) renderedMarkdown = renderPhase6Markdown(await agent.packs.get(params.packId, { expand: "entries" }));
7228
- const result = params.preview ?? false ? await agent.packs.previewRendered(params.packId, {
7229
- renderMethod,
7230
- renderedMarkdown
7231
- }) : await agent.packs.render(params.packId, {
7232
- renderMethod,
7233
- renderedMarkdown,
7234
- pinned: params.pinned
7235
- });
7236
- return {
7237
- content: [{
7238
- type: "text",
7239
- text: JSON.stringify(result, null, 2)
7240
- }],
7241
- details: {}
7242
- };
7243
- }
7662
+ async execute(_id, params) {
7663
+ const { agent } = ensureConnected(config);
7664
+ if (!params.packId && !params.packCid) throw new Error("Provide either packId or packCid");
7665
+ if (params.packId && params.packCid) throw new Error("Provide only one of packId or packCid");
7666
+ const graph = params.packId ? await agent.packs.getProvenance(params.packId, { depth: params.depth ?? 2 }) : await agent.packs.getProvenanceByCid(params.packCid, { depth: params.depth ?? 2 });
7667
+ const payload = {
7668
+ metadata: graph.metadata,
7669
+ counts: {
7670
+ nodes: graph.nodes.length,
7671
+ edges: graph.edges.length
7672
+ },
7673
+ graph
7674
+ };
7675
+ return {
7676
+ content: [{
7677
+ type: "text",
7678
+ text: JSON.stringify(payload, null, 2)
7679
+ }],
7680
+ details: {}
7681
+ };
7682
+ }
7683
+ });
7684
+ const renderPack = defineTool({
7685
+ name: "moltnet_pack_render",
7686
+ label: "Render MoltNet Pack",
7687
+ description: "Fetch a pack with entries, transform it into docs, then preview or persist the rendered pack.",
7688
+ parameters: Type.Object({
7689
+ packId: Type.String({ description: "Context pack ID" }),
7690
+ renderMethod: Type.Optional(Type.String({ description: "Render method label. Defaults to pi:pack-to-docs-v1" })),
7691
+ markdown: Type.Optional(Type.String({ description: "Optional caller-authored markdown override" })),
7692
+ preview: Type.Optional(Type.Boolean({ description: "Preview without persisting (default false)" })),
7693
+ pinned: Type.Optional(Type.Boolean({ description: "Persist the rendered pack as pinned (default false)" }))
7244
7694
  }),
7245
- defineTool({
7246
- name: "moltnet_rendered_pack_list",
7247
- label: "List MoltNet Rendered Packs",
7248
- description: "List rendered packs for the current MoltNet diary, optionally filtered by source pack or render method.",
7249
- parameters: Type.Object({
7250
- sourcePackId: Type.Optional(Type.String({ description: "Filter by source pack ID" })),
7251
- renderMethod: Type.Optional(Type.String({ description: "Filter by render method" })),
7252
- limit: Type.Optional(Type.Number({ description: "Max results (default 10)" })),
7253
- offset: Type.Optional(Type.Number({ description: "Offset for pagination (default 0)" }))
7254
- }),
7255
- async execute(_id, params) {
7256
- const { agent, diaryId } = ensureConnected(config);
7257
- const rendered = await agent.packs.listRendered(diaryId, {
7258
- sourcePackId: params.sourcePackId,
7259
- renderMethod: params.renderMethod,
7260
- limit: params.limit ?? 10,
7261
- offset: params.offset ?? 0
7262
- });
7263
- return {
7264
- content: [{
7265
- type: "text",
7266
- text: JSON.stringify(rendered, null, 2)
7267
- }],
7268
- details: {}
7269
- };
7270
- }
7695
+ async execute(_id, params) {
7696
+ const { agent } = ensureConnected(config);
7697
+ const renderMethod = params.renderMethod ?? "pi:pack-to-docs-v1";
7698
+ let renderedMarkdown = params.markdown;
7699
+ if (!renderedMarkdown && !renderMethod.startsWith("server:")) renderedMarkdown = renderPhase6Markdown(await agent.packs.get(params.packId, { expand: "entries" }));
7700
+ const result = params.preview ?? false ? await agent.packs.previewRendered(params.packId, {
7701
+ renderMethod,
7702
+ renderedMarkdown
7703
+ }) : await agent.packs.render(params.packId, {
7704
+ renderMethod,
7705
+ renderedMarkdown,
7706
+ pinned: params.pinned
7707
+ });
7708
+ return {
7709
+ content: [{
7710
+ type: "text",
7711
+ text: JSON.stringify(result, null, 2)
7712
+ }],
7713
+ details: {}
7714
+ };
7715
+ }
7716
+ });
7717
+ const listRenderedPacks = defineTool({
7718
+ name: "moltnet_rendered_pack_list",
7719
+ label: "List MoltNet Rendered Packs",
7720
+ description: "List rendered packs for the current MoltNet diary, optionally filtered by source pack or render method.",
7721
+ parameters: Type.Object({
7722
+ sourcePackId: Type.Optional(Type.String({ description: "Filter by source pack ID" })),
7723
+ renderMethod: Type.Optional(Type.String({ description: "Filter by render method" })),
7724
+ limit: Type.Optional(Type.Number({ description: "Max results (default 10)" })),
7725
+ offset: Type.Optional(Type.Number({ description: "Offset for pagination (default 0)" }))
7271
7726
  }),
7272
- defineTool({
7273
- name: "moltnet_rendered_pack_get",
7274
- label: "Get MoltNet Rendered Pack",
7275
- description: "Get a rendered pack by ID.",
7276
- parameters: Type.Object({ renderedPackId: Type.String({ description: "Rendered pack ID" }) }),
7277
- async execute(_id, params) {
7278
- const { agent } = ensureConnected(config);
7727
+ async execute(_id, params) {
7728
+ const { agent, diaryId } = ensureConnected(config);
7729
+ const rendered = await agent.packs.listRendered(diaryId, {
7730
+ sourcePackId: params.sourcePackId,
7731
+ renderMethod: params.renderMethod,
7732
+ limit: params.limit ?? 10,
7733
+ offset: params.offset ?? 0
7734
+ });
7735
+ return {
7736
+ content: [{
7737
+ type: "text",
7738
+ text: JSON.stringify(rendered, null, 2)
7739
+ }],
7740
+ details: {}
7741
+ };
7742
+ }
7743
+ });
7744
+ const getRenderedPack = defineTool({
7745
+ name: "moltnet_rendered_pack_get",
7746
+ label: "Get MoltNet Rendered Pack",
7747
+ description: "Get a rendered pack by ID.",
7748
+ parameters: Type.Object({ renderedPackId: Type.String({ description: "Rendered pack ID" }) }),
7749
+ async execute(_id, params) {
7750
+ const { agent } = ensureConnected(config);
7751
+ const rendered = await agent.packs.getRendered(params.renderedPackId);
7752
+ return {
7753
+ content: [{
7754
+ type: "text",
7755
+ text: JSON.stringify(rendered, null, 2)
7756
+ }],
7757
+ details: {}
7758
+ };
7759
+ }
7760
+ });
7761
+ const verifyRenderedPack = defineTool({
7762
+ name: "moltnet_rendered_pack_verify",
7763
+ label: "Verify MoltNet Rendered Pack",
7764
+ description: "Create a verification workflow for a rendered pack and return the verification ID and nonce.",
7765
+ parameters: Type.Object({
7766
+ renderedPackId: Type.String({ description: "Rendered pack ID" }),
7767
+ nonce: Type.Optional(Type.String({ description: "Caller-supplied idempotency nonce. Generated automatically if omitted." }))
7768
+ }),
7769
+ async execute(_id, params) {
7770
+ const { agent } = ensureConnected(config);
7771
+ const nonce = params.nonce ?? randomUUID();
7772
+ const verification = await agent.packs.verifyRendered(params.renderedPackId, { nonce });
7773
+ return {
7774
+ content: [{
7775
+ type: "text",
7776
+ text: JSON.stringify({
7777
+ ...verification,
7778
+ nonce
7779
+ }, null, 2)
7780
+ }],
7781
+ details: {}
7782
+ };
7783
+ }
7784
+ });
7785
+ const judgeRenderedPack = defineTool({
7786
+ name: "moltnet_rendered_pack_judge",
7787
+ label: "Judge MoltNet Rendered Pack",
7788
+ description: "Run the fidelity judge against a rendered pack. Local mode (no nonce): fetch the rendered pack + its source pack with entries, judge locally, return scores. Proctored mode (nonce): claim the verification payload from the API, judge, and submit scores with a Pi judge-recipe CID.",
7789
+ parameters: Type.Object({
7790
+ renderedPackId: Type.String({ description: "Rendered pack ID" }),
7791
+ nonce: Type.Optional(Type.String({ description: "Verification nonce from moltnet_rendered_pack_verify. If set, runs proctored mode and submits scores. If omitted, runs local mode and does not submit." })),
7792
+ rubric: Type.Optional(Type.String({ description: "Custom rubric override (local mode only). Defaults to the built-in rubric when omitted." }))
7793
+ }),
7794
+ async execute(_id, params, _signal, _onUpdate, ctx) {
7795
+ const { agent } = ensureConnected(config);
7796
+ const model = ctx?.model;
7797
+ if (!model) throw new Error("No active model in pi session — cannot run the fidelity judge.");
7798
+ let sourceEntriesMd;
7799
+ let renderedContent;
7800
+ let rubric;
7801
+ if (params.nonce) {
7802
+ if (params.rubric) throw new Error("`rubric` is only supported in local mode (omit `nonce`).");
7803
+ const claim = await agent.packs.claimVerification(params.renderedPackId);
7804
+ sourceEntriesMd = buildSourceEntriesMarkdown(claim.sourceEntries);
7805
+ renderedContent = claim.renderedContent;
7806
+ rubric = claim.rubric?.trim() ? claim.rubric : DEFAULT_RUBRIC;
7807
+ } else {
7279
7808
  const rendered = await agent.packs.getRendered(params.renderedPackId);
7280
- return {
7281
- content: [{
7282
- type: "text",
7283
- text: JSON.stringify(rendered, null, 2)
7284
- }],
7285
- details: {}
7286
- };
7809
+ if (!rendered.content?.trim()) throw new Error(`rendered pack ${params.renderedPackId} has empty content`);
7810
+ const sourcePack = await agent.packs.get(rendered.sourcePackId, { expand: "entries" });
7811
+ if (!sourcePack.entries || sourcePack.entries.length === 0) throw new Error(`source pack ${rendered.sourcePackId} has no entries`);
7812
+ sourceEntriesMd = buildSourceEntriesMarkdown(sourcePack.entries.map((entry) => ({
7813
+ title: entry.entry.title,
7814
+ content: entry.entry.content
7815
+ })));
7816
+ renderedContent = rendered.content;
7817
+ rubric = params.rubric?.trim() ? params.rubric : DEFAULT_RUBRIC;
7818
+ }
7819
+ let scores;
7820
+ try {
7821
+ scores = await runFidelityJudge({
7822
+ model,
7823
+ sourceEntries: sourceEntriesMd,
7824
+ renderedContent,
7825
+ rubric
7826
+ });
7827
+ } catch (err) {
7828
+ throw new Error(`judge failed: ${err.message ?? String(err)}`);
7287
7829
  }
7830
+ if (!params.nonce) return {
7831
+ content: [{
7832
+ type: "text",
7833
+ text: JSON.stringify({
7834
+ mode: "local",
7835
+ renderedPackId: params.renderedPackId,
7836
+ scores
7837
+ }, null, 2)
7838
+ }],
7839
+ details: {}
7840
+ };
7841
+ const recipe = computePiJudgeRecipeCid({
7842
+ judgePrompt: JUDGE_SYSTEM_PROMPT,
7843
+ rubric,
7844
+ promptAsset: JUDGE_PROMPT_ASSET_PATH,
7845
+ rubricAsset: RUBRIC_ASSET_PATH
7846
+ });
7847
+ const providerName = model.provider ?? "pi";
7848
+ const modelId = model.id ?? "unknown";
7849
+ const submit = await agent.packs.submitVerification(params.renderedPackId, {
7850
+ nonce: params.nonce,
7851
+ coverage: scores.coverage,
7852
+ grounding: scores.grounding,
7853
+ faithfulness: scores.faithfulness,
7854
+ transcript: scores.reasoning,
7855
+ judgeModel: modelId,
7856
+ judgeProvider: providerName,
7857
+ judgeBinaryCid: recipe.cid
7858
+ });
7859
+ return {
7860
+ content: [{
7861
+ type: "text",
7862
+ text: JSON.stringify({
7863
+ mode: "proctored",
7864
+ renderedPackId: params.renderedPackId,
7865
+ scores,
7866
+ submission: submit,
7867
+ judgeRecipeCid: recipe.cid,
7868
+ judgeRecipeManifest: recipe.manifest
7869
+ }, null, 2)
7870
+ }],
7871
+ details: {}
7872
+ };
7873
+ }
7874
+ });
7875
+ const diaryTags = defineTool({
7876
+ name: "moltnet_diary_tags",
7877
+ label: "List MoltNet Diary Tags",
7878
+ description: "Inventory tags on the current diary with entry counts. Cheap reconnaissance before committing to a search or list — use it to discover scope prefixes and cluster sizes. Optional prefix/minCount/entryTypes filters narrow the result.",
7879
+ parameters: Type.Object({
7880
+ prefix: Type.Optional(Type.String({ description: "Filter to tags starting with this prefix (e.g. \"scope:\")" })),
7881
+ minCount: Type.Optional(Type.Number({ description: "Exclude tags with fewer than this many entries" })),
7882
+ entryTypes: Type.Optional(Type.Array(Type.Union([
7883
+ Type.Literal("episodic"),
7884
+ Type.Literal("semantic"),
7885
+ Type.Literal("procedural"),
7886
+ Type.Literal("reflection"),
7887
+ Type.Literal("identity"),
7888
+ Type.Literal("soul")
7889
+ ]), { description: "Scope the tag count to these entry types" }))
7288
7890
  }),
7289
- defineTool({
7290
- name: "moltnet_rendered_pack_verify",
7291
- label: "Verify MoltNet Rendered Pack",
7292
- description: "Create a verification workflow for a rendered pack and return the verification ID and nonce.",
7293
- parameters: Type.Object({
7294
- renderedPackId: Type.String({ description: "Rendered pack ID" }),
7295
- nonce: Type.Optional(Type.String({ description: "Caller-supplied idempotency nonce. Generated automatically if omitted." }))
7296
- }),
7297
- async execute(_id, params) {
7298
- const { agent } = ensureConnected(config);
7299
- const nonce = params.nonce ?? randomUUID();
7300
- const verification = await agent.packs.verifyRendered(params.renderedPackId, { nonce });
7301
- return {
7302
- content: [{
7303
- type: "text",
7304
- text: JSON.stringify({
7305
- ...verification,
7306
- nonce
7307
- }, null, 2)
7308
- }],
7309
- details: {}
7310
- };
7891
+ async execute(_id, params) {
7892
+ const { agent, diaryId } = ensureConnected(config);
7893
+ const result = await agent.diaries.tags(diaryId, {
7894
+ prefix: params.prefix,
7895
+ minCount: params.minCount,
7896
+ entryTypes: params.entryTypes
7897
+ });
7898
+ return {
7899
+ content: [{
7900
+ type: "text",
7901
+ text: JSON.stringify(result, null, 2)
7902
+ }],
7903
+ details: {}
7904
+ };
7905
+ }
7906
+ });
7907
+ const listEntries = defineTool({
7908
+ name: "moltnet_list_entries",
7909
+ label: "List MoltNet Diary Entries",
7910
+ description: "List entries from the MoltNet diary. When `entryIds` is provided, batch-fetches those specific entries (max 50) and returns full fields including entryType, contentSignature, and contentHash for signature checks. Otherwise returns recent entries with a content preview.",
7911
+ parameters: Type.Object({
7912
+ limit: Type.Optional(Type.Number({ description: "Max entries to return (default 10)" })),
7913
+ tag: Type.Optional(Type.String({ description: "Filter by tag (optional)" })),
7914
+ entryIds: Type.Optional(Type.Array(Type.String(), {
7915
+ description: "Batch-fetch specific entries by UUID (max 50). Overrides `limit` and `tag` for selection.",
7916
+ maxItems: 50
7917
+ }))
7918
+ }),
7919
+ async execute(_id, params) {
7920
+ const { agent, diaryId } = ensureConnected(config);
7921
+ const query = {
7922
+ orderBy: "createdAt",
7923
+ order: "desc"
7924
+ };
7925
+ const batchMode = !!params.entryIds?.length;
7926
+ if (batchMode) query.ids = params.entryIds;
7927
+ else {
7928
+ query.limit = params.limit ?? 10;
7929
+ if (params.tag) query.tag = params.tag;
7311
7930
  }
7931
+ const entries = await agent.entries.list(diaryId, query);
7932
+ return {
7933
+ content: [{
7934
+ type: "text",
7935
+ text: JSON.stringify(entries.items?.map((e) => batchMode ? {
7936
+ id: e.id,
7937
+ title: e.title,
7938
+ entryType: e.entryType,
7939
+ tags: e.tags,
7940
+ importance: e.importance,
7941
+ contentHash: e.contentHash,
7942
+ contentSignature: e.contentSignature,
7943
+ signingNonce: e.signingNonce,
7944
+ createdAt: e.createdAt
7945
+ } : {
7946
+ id: e.id,
7947
+ title: e.title,
7948
+ tags: e.tags,
7949
+ importance: e.importance,
7950
+ createdAt: e.createdAt,
7951
+ contentPreview: typeof e.content === "string" ? e.content.slice(0, 200) : void 0
7952
+ }), null, 2)
7953
+ }],
7954
+ details: {}
7955
+ };
7956
+ }
7957
+ });
7958
+ const getEntry = defineTool({
7959
+ name: "moltnet_get_entry",
7960
+ label: "Get MoltNet Diary Entry",
7961
+ description: "Get the full content of a specific diary entry by ID.",
7962
+ parameters: Type.Object({ entryId: Type.String({ description: "The entry ID to fetch" }) }),
7963
+ async execute(_id, params) {
7964
+ const { agent } = ensureConnected(config);
7965
+ const entry = await agent.entries.get(params.entryId);
7966
+ return {
7967
+ content: [{
7968
+ type: "text",
7969
+ text: JSON.stringify({
7970
+ id: entry.id,
7971
+ title: entry.title,
7972
+ content: entry.content,
7973
+ tags: entry.tags,
7974
+ importance: entry.importance,
7975
+ createdAt: entry.createdAt
7976
+ }, null, 2)
7977
+ }],
7978
+ details: {}
7979
+ };
7980
+ }
7981
+ });
7982
+ const searchEntries = defineTool({
7983
+ name: "moltnet_search_entries",
7984
+ label: "Search MoltNet Diary Entries",
7985
+ description: "Search diary entries by semantic query. Uses vector similarity to find relevant entries.",
7986
+ parameters: Type.Object({
7987
+ query: Type.String({ description: "Natural language search query" }),
7988
+ limit: Type.Optional(Type.Number({ description: "Max results (default 5)" }))
7312
7989
  }),
7990
+ async execute(_id, params) {
7991
+ const { agent, diaryId } = ensureConnected(config);
7992
+ const results = await agent.entries.search({
7993
+ diaryId,
7994
+ query: params.query,
7995
+ limit: params.limit ?? 5
7996
+ });
7997
+ return {
7998
+ content: [{
7999
+ type: "text",
8000
+ text: JSON.stringify(results.results?.map((e) => ({
8001
+ id: e.id,
8002
+ title: e.title,
8003
+ tags: e.tags,
8004
+ importance: e.importance,
8005
+ contentPreview: typeof e.content === "string" ? e.content.slice(0, 200) : void 0
8006
+ })), null, 2)
8007
+ }],
8008
+ details: {}
8009
+ };
8010
+ }
8011
+ });
8012
+ const createEntry = defineTool({
8013
+ name: "moltnet_create_entry",
8014
+ label: "Create MoltNet Diary Entry",
8015
+ description: "Create a new diary entry to record decisions, findings, incidents, or reflections.",
8016
+ parameters: Type.Object({
8017
+ title: Type.String({ description: "Entry title (concise, descriptive)" }),
8018
+ content: Type.String({ description: "Entry content (markdown)" }),
8019
+ tags: Type.Optional(Type.Array(Type.String(), { description: "Tags for categorization" })),
8020
+ importance: Type.Optional(Type.Number({ description: "Importance 1-10 (default 5)" }))
8021
+ }),
8022
+ async execute(_id, params) {
8023
+ const { agent, diaryId } = ensureConnected(config);
8024
+ const entry = await agent.entries.create(diaryId, {
8025
+ title: params.title,
8026
+ content: params.content,
8027
+ tags: params.tags ?? [],
8028
+ importance: params.importance ?? 5
8029
+ });
8030
+ return {
8031
+ content: [{
8032
+ type: "text",
8033
+ text: JSON.stringify({
8034
+ id: entry.id,
8035
+ title: entry.title,
8036
+ createdAt: entry.createdAt
8037
+ }, null, 2)
8038
+ }],
8039
+ details: {}
8040
+ };
8041
+ }
8042
+ });
8043
+ const reviewSessionErrors = defineTool({
8044
+ name: "moltnet_review_session_errors",
8045
+ label: "Review Session Tool Errors",
8046
+ description: "Review tool failures buffered during this session (isError=true results). Use this to decide whether any failures are worth persisting as a diary entry via moltnet_create_entry. Most failures are transient (denied prompts, empty greps, mid-iteration typecheck errors) and should NOT be written to the diary — only persist incidents that represent a real finding (root cause identified, non-obvious workaround, recurring pattern). Pass clear=true to drop the buffer after reviewing.",
8047
+ parameters: Type.Object({ clear: Type.Optional(Type.Boolean({ description: "If true, empty the buffer after returning it. Use once you have decided whether to persist." })) }),
8048
+ async execute(_id, params) {
8049
+ const errors = config.getSessionErrors();
8050
+ const payload = {
8051
+ count: errors.length,
8052
+ errors: errors.map((e) => ({
8053
+ toolName: e.toolName,
8054
+ toolCallId: e.toolCallId,
8055
+ timestamp: new Date(e.timestamp).toISOString(),
8056
+ input: e.input,
8057
+ error: e.error
8058
+ }))
8059
+ };
8060
+ if (params.clear) config.clearSessionErrors();
8061
+ return {
8062
+ content: [{
8063
+ type: "text",
8064
+ text: JSON.stringify(payload, null, 2)
8065
+ }],
8066
+ details: {}
8067
+ };
8068
+ }
8069
+ });
8070
+ const HOST_EXEC_ALLOWED = new Set([
8071
+ "git",
8072
+ "gh",
8073
+ "moltnet"
8074
+ ]);
8075
+ const hostExecBaseEnv = config.hostExecBaseEnv ?? HOST_EXEC_DEFAULT_BASE_ENV;
8076
+ const HOST_EXEC_TIMEOUT_MS = 6e4;
8077
+ return [
8078
+ getPack,
8079
+ createPack,
8080
+ getPackProvenance,
8081
+ renderPack,
8082
+ listRenderedPacks,
8083
+ getRenderedPack,
8084
+ verifyRenderedPack,
8085
+ judgeRenderedPack,
8086
+ diaryTags,
8087
+ listEntries,
8088
+ getEntry,
8089
+ searchEntries,
8090
+ createEntry,
8091
+ reviewSessionErrors,
7313
8092
  defineTool({
7314
- name: "moltnet_rendered_pack_judge",
7315
- label: "Judge MoltNet Rendered Pack",
7316
- description: "Run the fidelity judge against a rendered pack. Local mode (no nonce): fetch the rendered pack + its source pack with entries, judge locally, return scores. Proctored mode (nonce): claim the verification payload from the API, judge, and submit scores with a Pi judge-recipe CID.",
8093
+ name: "moltnet_host_exec",
8094
+ label: "Run command on host (escape hatch — requires user approval)",
8095
+ description: "Runs a command on the HOST machine, outside the sandbox VM. The user will be prompted to approve each invocation via a UI dialog — do NOT call this tool speculatively. Use ONLY when a sandboxed operation is impossible e.g. `git push`, `gh pr create`.\n\nAllowed executables: git, gh, moltnet. Runs with a minimal env (PATH, HOME, GIT_CONFIG_GLOBAL, …); pass any additional vars via the `env` parameter (e.g. GH_TOKEN). Every invocation is logged as an auditable host execution.",
7317
8096
  parameters: Type.Object({
7318
- renderedPackId: Type.String({ description: "Rendered pack ID" }),
7319
- nonce: Type.Optional(Type.String({ description: "Verification nonce from moltnet_rendered_pack_verify. If set, runs proctored mode and submits scores. If omitted, runs local mode and does not submit." })),
7320
- rubric: Type.Optional(Type.String({ description: "Custom rubric override (local mode only). Defaults to the built-in rubric when omitted." }))
8097
+ executable: Type.String({ description: "Executable to run (git | gh | moltnet)" }),
8098
+ args: Type.Array(Type.String(), { description: "Arguments to pass to the executable" }),
8099
+ env: Type.Optional(Type.Record(Type.String(), Type.String(), { description: "Additional environment variables for this invocation (e.g. { \"GH_TOKEN\": \"...\" }). Merged on top of the minimal base env." }))
7321
8100
  }),
7322
8101
  async execute(_id, params, _signal, _onUpdate, ctx) {
7323
- const { agent } = ensureConnected(config);
7324
- const model = ctx?.model;
7325
- if (!model) throw new Error("No active model in pi session — cannot run the fidelity judge.");
7326
- let sourceEntriesMd;
7327
- let renderedContent;
7328
- let rubric;
7329
- if (params.nonce) {
7330
- if (params.rubric) throw new Error("`rubric` is only supported in local mode (omit `nonce`).");
7331
- const claim = await agent.packs.claimVerification(params.renderedPackId);
7332
- sourceEntriesMd = buildSourceEntriesMarkdown(claim.sourceEntries);
7333
- renderedContent = claim.renderedContent;
7334
- rubric = claim.rubric?.trim() ? claim.rubric : DEFAULT_RUBRIC;
7335
- } else {
7336
- const rendered = await agent.packs.getRendered(params.renderedPackId);
7337
- if (!rendered.content?.trim()) throw new Error(`rendered pack ${params.renderedPackId} has empty content`);
7338
- const sourcePack = await agent.packs.get(rendered.sourcePackId, { expand: "entries" });
7339
- if (!sourcePack.entries || sourcePack.entries.length === 0) throw new Error(`source pack ${rendered.sourcePackId} has no entries`);
7340
- sourceEntriesMd = buildSourceEntriesMarkdown(sourcePack.entries.map((entry) => ({
7341
- title: entry.entry.title,
7342
- content: entry.entry.content
7343
- })));
7344
- renderedContent = rendered.content;
7345
- rubric = params.rubric?.trim() ? params.rubric : DEFAULT_RUBRIC;
8102
+ if (!HOST_EXEC_ALLOWED.has(params.executable)) throw new Error(`host_exec: '${params.executable}' is not in the allowed list (${[...HOST_EXEC_ALLOWED].join(", ")}). Extend HOST_EXEC_ALLOWED only after explicit security review.`);
8103
+ if (ctx?.ui) {
8104
+ const cmdDisplay = [params.executable, ...params.args].join(" ");
8105
+ if (!await ctx.ui.confirm("Allow host command?", `The agent wants to run on your machine:\n\n ${cmdDisplay}\n\nAllow?`)) throw new Error(`host_exec: user declined approval for: ${cmdDisplay}`);
7346
8106
  }
7347
- let scores;
8107
+ const cwd = config.getHostCwd?.() ?? process.cwd();
8108
+ const baseEnv = {};
8109
+ for (const key of hostExecBaseEnv) {
8110
+ const val = process.env[key];
8111
+ if (val !== void 0) baseEnv[key] = val;
8112
+ }
8113
+ const mergedEnv = {
8114
+ ...baseEnv,
8115
+ ...params.env ?? {}
8116
+ };
8117
+ let stdout;
8118
+ let stderr = "";
7348
8119
  try {
7349
- scores = await runFidelityJudge({
7350
- model,
7351
- sourceEntries: sourceEntriesMd,
7352
- renderedContent,
7353
- rubric
8120
+ stdout = execFileSync(params.executable, params.args, {
8121
+ encoding: "utf8",
8122
+ cwd,
8123
+ env: mergedEnv,
8124
+ stdio: [
8125
+ "pipe",
8126
+ "pipe",
8127
+ "pipe"
8128
+ ],
8129
+ timeout: HOST_EXEC_TIMEOUT_MS
7354
8130
  });
7355
8131
  } catch (err) {
7356
- throw new Error(`judge failed: ${err.message ?? String(err)}`);
8132
+ const e = err;
8133
+ stdout = e.stdout ?? "";
8134
+ stderr = e.stderr ?? e.message ?? String(err);
7357
8135
  }
7358
- if (!params.nonce) return {
7359
- content: [{
7360
- type: "text",
7361
- text: JSON.stringify({
7362
- mode: "local",
7363
- renderedPackId: params.renderedPackId,
7364
- scores
7365
- }, null, 2)
7366
- }],
7367
- details: {}
8136
+ const result = {
8137
+ host_exec: true,
8138
+ executable: params.executable,
8139
+ args: params.args,
8140
+ cwd,
8141
+ stdout: stdout.trimEnd(),
8142
+ stderr: stderr.trimEnd() || void 0
7368
8143
  };
7369
- const recipe = computePiJudgeRecipeCid({
7370
- judgePrompt: JUDGE_SYSTEM_PROMPT,
7371
- rubric,
7372
- promptAsset: JUDGE_PROMPT_ASSET_PATH,
7373
- rubricAsset: RUBRIC_ASSET_PATH
7374
- });
7375
- const providerName = model.provider ?? "pi";
7376
- const modelId = model.id ?? "unknown";
7377
- const submit = await agent.packs.submitVerification(params.renderedPackId, {
7378
- nonce: params.nonce,
7379
- coverage: scores.coverage,
7380
- grounding: scores.grounding,
7381
- faithfulness: scores.faithfulness,
7382
- transcript: scores.reasoning,
7383
- judgeModel: modelId,
7384
- judgeProvider: providerName,
7385
- judgeBinaryCid: recipe.cid
7386
- });
7387
- return {
7388
- content: [{
7389
- type: "text",
7390
- text: JSON.stringify({
7391
- mode: "proctored",
7392
- renderedPackId: params.renderedPackId,
7393
- scores,
7394
- submission: submit,
7395
- judgeRecipeCid: recipe.cid,
7396
- judgeRecipeManifest: recipe.manifest
7397
- }, null, 2)
7398
- }],
7399
- details: {}
7400
- };
7401
- }
7402
- }),
7403
- defineTool({
7404
- name: "moltnet_diary_tags",
7405
- label: "List MoltNet Diary Tags",
7406
- description: "Inventory tags on the current diary with entry counts. Cheap reconnaissance before committing to a search or list — use it to discover scope prefixes and cluster sizes. Optional prefix/minCount/entryTypes filters narrow the result.",
7407
- parameters: Type.Object({
7408
- prefix: Type.Optional(Type.String({ description: "Filter to tags starting with this prefix (e.g. \"scope:\")" })),
7409
- minCount: Type.Optional(Type.Number({ description: "Exclude tags with fewer than this many entries" })),
7410
- entryTypes: Type.Optional(Type.Array(Type.Union([
7411
- Type.Literal("episodic"),
7412
- Type.Literal("semantic"),
7413
- Type.Literal("procedural"),
7414
- Type.Literal("reflection"),
7415
- Type.Literal("identity"),
7416
- Type.Literal("soul")
7417
- ]), { description: "Scope the tag count to these entry types" }))
7418
- }),
7419
- async execute(_id, params) {
7420
- const { agent, diaryId } = ensureConnected(config);
7421
- const result = await agent.diaries.tags(diaryId, {
7422
- prefix: params.prefix,
7423
- minCount: params.minCount,
7424
- entryTypes: params.entryTypes
7425
- });
7426
8144
  return {
7427
8145
  content: [{
7428
8146
  type: "text",
@@ -7431,169 +8149,6 @@ function createMoltNetTools(config) {
7431
8149
  details: {}
7432
8150
  };
7433
8151
  }
7434
- }),
7435
- defineTool({
7436
- name: "moltnet_list_entries",
7437
- label: "List MoltNet Diary Entries",
7438
- description: "List entries from the MoltNet diary. When `entryIds` is provided, batch-fetches those specific entries (max 50) and returns full fields including entryType, contentSignature, and contentHash for signature checks. Otherwise returns recent entries with a content preview.",
7439
- parameters: Type.Object({
7440
- limit: Type.Optional(Type.Number({ description: "Max entries to return (default 10)" })),
7441
- tag: Type.Optional(Type.String({ description: "Filter by tag (optional)" })),
7442
- entryIds: Type.Optional(Type.Array(Type.String(), {
7443
- description: "Batch-fetch specific entries by UUID (max 50). Overrides `limit` and `tag` for selection.",
7444
- maxItems: 50
7445
- }))
7446
- }),
7447
- async execute(_id, params) {
7448
- const { agent, diaryId } = ensureConnected(config);
7449
- const query = {
7450
- orderBy: "createdAt",
7451
- order: "desc"
7452
- };
7453
- const batchMode = !!params.entryIds?.length;
7454
- if (batchMode) query.ids = params.entryIds;
7455
- else {
7456
- query.limit = params.limit ?? 10;
7457
- if (params.tag) query.tag = params.tag;
7458
- }
7459
- const entries = await agent.entries.list(diaryId, query);
7460
- return {
7461
- content: [{
7462
- type: "text",
7463
- text: JSON.stringify(entries.items?.map((e) => batchMode ? {
7464
- id: e.id,
7465
- title: e.title,
7466
- entryType: e.entryType,
7467
- tags: e.tags,
7468
- importance: e.importance,
7469
- contentHash: e.contentHash,
7470
- contentSignature: e.contentSignature,
7471
- signingNonce: e.signingNonce,
7472
- createdAt: e.createdAt
7473
- } : {
7474
- id: e.id,
7475
- title: e.title,
7476
- tags: e.tags,
7477
- importance: e.importance,
7478
- createdAt: e.createdAt,
7479
- contentPreview: typeof e.content === "string" ? e.content.slice(0, 200) : void 0
7480
- }), null, 2)
7481
- }],
7482
- details: {}
7483
- };
7484
- }
7485
- }),
7486
- defineTool({
7487
- name: "moltnet_get_entry",
7488
- label: "Get MoltNet Diary Entry",
7489
- description: "Get the full content of a specific diary entry by ID.",
7490
- parameters: Type.Object({ entryId: Type.String({ description: "The entry ID to fetch" }) }),
7491
- async execute(_id, params) {
7492
- const { agent } = ensureConnected(config);
7493
- const entry = await agent.entries.get(params.entryId);
7494
- return {
7495
- content: [{
7496
- type: "text",
7497
- text: JSON.stringify({
7498
- id: entry.id,
7499
- title: entry.title,
7500
- content: entry.content,
7501
- tags: entry.tags,
7502
- importance: entry.importance,
7503
- createdAt: entry.createdAt
7504
- }, null, 2)
7505
- }],
7506
- details: {}
7507
- };
7508
- }
7509
- }),
7510
- defineTool({
7511
- name: "moltnet_search_entries",
7512
- label: "Search MoltNet Diary Entries",
7513
- description: "Search diary entries by semantic query. Uses vector similarity to find relevant entries.",
7514
- parameters: Type.Object({
7515
- query: Type.String({ description: "Natural language search query" }),
7516
- limit: Type.Optional(Type.Number({ description: "Max results (default 5)" }))
7517
- }),
7518
- async execute(_id, params) {
7519
- const { agent, diaryId } = ensureConnected(config);
7520
- const results = await agent.entries.search({
7521
- diaryId,
7522
- query: params.query,
7523
- limit: params.limit ?? 5
7524
- });
7525
- return {
7526
- content: [{
7527
- type: "text",
7528
- text: JSON.stringify(results.results?.map((e) => ({
7529
- id: e.id,
7530
- title: e.title,
7531
- tags: e.tags,
7532
- importance: e.importance,
7533
- contentPreview: typeof e.content === "string" ? e.content.slice(0, 200) : void 0
7534
- })), null, 2)
7535
- }],
7536
- details: {}
7537
- };
7538
- }
7539
- }),
7540
- defineTool({
7541
- name: "moltnet_create_entry",
7542
- label: "Create MoltNet Diary Entry",
7543
- description: "Create a new diary entry to record decisions, findings, incidents, or reflections.",
7544
- parameters: Type.Object({
7545
- title: Type.String({ description: "Entry title (concise, descriptive)" }),
7546
- content: Type.String({ description: "Entry content (markdown)" }),
7547
- tags: Type.Optional(Type.Array(Type.String(), { description: "Tags for categorization" })),
7548
- importance: Type.Optional(Type.Number({ description: "Importance 1-10 (default 5)" }))
7549
- }),
7550
- async execute(_id, params) {
7551
- const { agent, diaryId } = ensureConnected(config);
7552
- const entry = await agent.entries.create(diaryId, {
7553
- title: params.title,
7554
- content: params.content,
7555
- tags: params.tags ?? [],
7556
- importance: params.importance ?? 5
7557
- });
7558
- return {
7559
- content: [{
7560
- type: "text",
7561
- text: JSON.stringify({
7562
- id: entry.id,
7563
- title: entry.title,
7564
- createdAt: entry.createdAt
7565
- }, null, 2)
7566
- }],
7567
- details: {}
7568
- };
7569
- }
7570
- }),
7571
- defineTool({
7572
- name: "moltnet_review_session_errors",
7573
- label: "Review Session Tool Errors",
7574
- description: "Review tool failures buffered during this session (isError=true results). Use this to decide whether any failures are worth persisting as a diary entry via moltnet_create_entry. Most failures are transient (denied prompts, empty greps, mid-iteration typecheck errors) and should NOT be written to the diary — only persist incidents that represent a real finding (root cause identified, non-obvious workaround, recurring pattern). Pass clear=true to drop the buffer after reviewing.",
7575
- parameters: Type.Object({ clear: Type.Optional(Type.Boolean({ description: "If true, empty the buffer after returning it. Use once you have decided whether to persist." })) }),
7576
- async execute(_id, params) {
7577
- const errors = config.getSessionErrors();
7578
- const payload = {
7579
- count: errors.length,
7580
- errors: errors.map((e) => ({
7581
- toolName: e.toolName,
7582
- toolCallId: e.toolCallId,
7583
- timestamp: new Date(e.timestamp).toISOString(),
7584
- input: e.input,
7585
- error: e.error
7586
- }))
7587
- };
7588
- if (params.clear) config.clearSessionErrors();
7589
- return {
7590
- content: [{
7591
- type: "text",
7592
- text: JSON.stringify(payload, null, 2)
7593
- }],
7594
- details: {}
7595
- };
7596
- }
7597
8152
  })
7598
8153
  ];
7599
8154
  }
@@ -7929,10 +8484,6 @@ function createGondolinBashOps(vm, localCwd) {
7929
8484
  }
7930
8485
  //#endregion
7931
8486
  //#region src/vm-manager.ts
7932
- /**
7933
- * VM lifecycle manager: resume checkpoint, inject credentials, configure
7934
- * egress, fix TLS, and provide clean shutdown.
7935
- */
7936
8487
  var GUEST_WORKSPACE$1 = "/workspace";
7937
8488
  /**
7938
8489
  * Resolve the main worktree root (where .moltnet/ lives — it's untracked,
@@ -7963,6 +8514,15 @@ function loadCredentials(agentDir) {
7963
8514
  const sshPrivateKey = existsSync(path.join(sshDir, "id_ed25519")) ? readFileSync(path.join(sshDir, "id_ed25519"), "utf8") : null;
7964
8515
  const sshPublicKey = existsSync(path.join(sshDir, "id_ed25519.pub")) ? readFileSync(path.join(sshDir, "id_ed25519.pub"), "utf8") : null;
7965
8516
  const allowedSigners = existsSync(path.join(sshDir, "allowed_signers")) ? readFileSync(path.join(sshDir, "allowed_signers"), "utf8") : null;
8517
+ let githubAppPem = null;
8518
+ let githubAppPemFilename = null;
8519
+ const pemPath = JSON.parse(moltnetJson).github?.private_key_path;
8520
+ if (pemPath) if (!existsSync(pemPath)) process.stderr.write(`[pi-extension] Warning: github.private_key_path not found at ${pemPath} — moltnet github token will fail inside the guest
8521
+ `);
8522
+ else {
8523
+ githubAppPem = readFileSync(pemPath, "utf8");
8524
+ githubAppPemFilename = path.basename(pemPath);
8525
+ }
7966
8526
  return {
7967
8527
  moltnetJson,
7968
8528
  agentEnvRaw,
@@ -7971,7 +8531,9 @@ function loadCredentials(agentDir) {
7971
8531
  gitconfig,
7972
8532
  sshPrivateKey,
7973
8533
  sshPublicKey,
7974
- allowedSigners
8534
+ allowedSigners,
8535
+ githubAppPem,
8536
+ githubAppPemFilename
7975
8537
  };
7976
8538
  }
7977
8539
  /**
@@ -8018,13 +8580,15 @@ async function resumeVm(config) {
8018
8580
  apiHost,
8019
8581
  ...config.extraAllowedHosts ?? []
8020
8582
  ] });
8583
+ const vmAgentDir = `/home/agent/.moltnet/${config.agentName}`;
8021
8584
  const vmAgentEnv = {};
8022
8585
  for (const [k, v] of Object.entries(creds.agentEnv)) {
8023
8586
  if (v === void 0 || v === "") continue;
8024
- if (k === "GIT_CONFIG_GLOBAL") vmAgentEnv[k] = `/home/agent/.moltnet/${config.agentName}/gitconfig`;
8025
- else if (k.endsWith("_PRIVATE_KEY_PATH")) vmAgentEnv[k] = `/home/agent/.moltnet/${config.agentName}/${path.basename(v)}`;
8587
+ if (k === "GIT_CONFIG_GLOBAL") vmAgentEnv[k] = `${vmAgentDir}/gitconfig`;
8588
+ else if (k.endsWith("_PRIVATE_KEY_PATH")) vmAgentEnv[k] = `${vmAgentDir}/${path.basename(v)}`;
8026
8589
  else vmAgentEnv[k] = v;
8027
8590
  }
8591
+ vmAgentEnv.MOLTNET_CREDENTIALS_PATH = `${vmAgentDir}/moltnet.json`;
8028
8592
  const vfsConfig = config.sandboxConfig?.vfs;
8029
8593
  let workspaceProvider = new RealFSProvider(config.mountPath);
8030
8594
  if (vfsConfig?.shadow?.length) {
@@ -8059,11 +8623,11 @@ async function resumeVm(config) {
8059
8623
  '`);
8060
8624
  await vm.exec(`sh -c 'echo "nameserver 8.8.8.8
8061
8625
  nameserver 1.1.1.1" > /etc/resolv.conf'`);
8062
- const vmAgentDir = `/home/agent/.moltnet/${config.agentName}`;
8063
8626
  const vmSshDir = `${vmAgentDir}/ssh`;
8064
8627
  await vm.exec(`mkdir -p ${vmAgentDir}/ssh /home/agent/.pi/agent`);
8065
8628
  await vm.fs.writeFile("/home/agent/.pi/agent/auth.json", creds.piAuthJson, { mode: 384 });
8066
- await vm.fs.writeFile(`${vmAgentDir}/moltnet.json`, creds.moltnetJson, { mode: 384 });
8629
+ const vmMoltnetJson = rewriteMoltnetJsonPaths(creds.moltnetJson, vmAgentDir, vmSshDir, creds.githubAppPemFilename);
8630
+ await vm.fs.writeFile(`${vmAgentDir}/moltnet.json`, vmMoltnetJson, { mode: 384 });
8067
8631
  await vm.fs.writeFile(`${vmAgentDir}/env`, creds.agentEnvRaw, { mode: 384 });
8068
8632
  if (creds.gitconfig) {
8069
8633
  const vmSigningKey = `${vmSshDir}/id_ed25519`;
@@ -8074,6 +8638,7 @@ nameserver 1.1.1.1" > /etc/resolv.conf'`);
8074
8638
  if (creds.sshPrivateKey) await vm.fs.writeFile(`${vmSshDir}/id_ed25519`, creds.sshPrivateKey, { mode: 384 });
8075
8639
  if (creds.sshPublicKey) await vm.fs.writeFile(`${vmSshDir}/id_ed25519.pub`, creds.sshPublicKey, { mode: 420 });
8076
8640
  if (creds.allowedSigners) await vm.fs.writeFile(`${vmSshDir}/allowed_signers`, creds.allowedSigners, { mode: 420 });
8641
+ if (creds.githubAppPem && creds.githubAppPemFilename) await vm.fs.writeFile(`${vmAgentDir}/${creds.githubAppPemFilename}`, creds.githubAppPem, { mode: 384 });
8077
8642
  await vm.exec("chown -R agent:agent /home/agent/.pi /home/agent/.moltnet");
8078
8643
  return {
8079
8644
  vm,
@@ -8084,6 +8649,43 @@ nameserver 1.1.1.1" > /etc/resolv.conf'`);
8084
8649
  };
8085
8650
  }
8086
8651
  /**
8652
+ * Rewrite host-absolute paths inside moltnet.json to VM-local equivalents.
8653
+ *
8654
+ * Fields rewritten:
8655
+ * ssh.private_key_path → <vmSshDir>/<basename of original>
8656
+ * ssh.public_key_path → <vmSshDir>/<basename of original>
8657
+ * git.config_path → <vmAgentDir>/gitconfig
8658
+ * github.private_key_path → <vmAgentDir>/<pemFilename> (if present)
8659
+ *
8660
+ * All other fields are passed through unchanged.
8661
+ * Throws if moltnetJson is not valid JSON — callers must not inject a broken
8662
+ * moltnet.json into the guest.
8663
+ */
8664
+ function rewriteMoltnetJsonPaths(moltnetJson, vmAgentDir, vmSshDir, githubAppPemFilename) {
8665
+ const config = JSON.parse(moltnetJson);
8666
+ if (config.ssh && typeof config.ssh === "object") {
8667
+ const ssh = config.ssh;
8668
+ const origPrivate = typeof ssh.private_key_path === "string" ? ssh.private_key_path : null;
8669
+ const origPublic = typeof ssh.public_key_path === "string" ? ssh.public_key_path : null;
8670
+ config.ssh = {
8671
+ ...ssh,
8672
+ ...origPrivate !== null && { private_key_path: `${vmSshDir}/${path.basename(origPrivate)}` },
8673
+ ...origPublic !== null && { public_key_path: `${vmSshDir}/${path.basename(origPublic)}` }
8674
+ };
8675
+ }
8676
+ if (config.git && typeof config.git === "object") {
8677
+ const git = { ...config.git };
8678
+ git.config_path = `${vmAgentDir}/gitconfig`;
8679
+ config.git = git;
8680
+ }
8681
+ if (githubAppPemFilename && config.github && typeof config.github === "object") {
8682
+ const github = { ...config.github };
8683
+ github.private_key_path = `${vmAgentDir}/${githubAppPemFilename}`;
8684
+ config.github = github;
8685
+ }
8686
+ return JSON.stringify(config);
8687
+ }
8688
+ /**
8087
8689
  * Ensure `[worktree] useRelativePaths = true` is set in the given
8088
8690
  * gitconfig text. If the section exists, rewrite the key; otherwise
8089
8691
  * append a new section.
@@ -8121,9 +8723,9 @@ if (!FormatRegistry.Has("date-time")) FormatRegistry.Set("date-time", (v) => !Nu
8121
8723
  * own signed rows and CIDv1 lookup. The schema below is designed to
8122
8724
  * carry forward unchanged — only storage and addressing differ.
8123
8725
  *
8124
- * Until Phase 2 lands, `rubric_id` + `version` + `content_hash` are
8726
+ * Until Phase 2 lands, `rubricId` + `version` + `contentHash` are
8125
8727
  * informational fields the author fills in; no uniqueness is enforced.
8126
- * `content_hash` is optional in Phase 1 because the *task*'s input_cid
8728
+ * `contentHash` is optional in Phase 1 because the *task*'s input_cid
8127
8729
  * is the authoritative commitment.
8128
8730
  */
8129
8731
  /**
@@ -8159,12 +8761,12 @@ var RubricCriterion = Type$1.Object({
8159
8761
  * (stored row `body`); only the addressing mechanism differs.
8160
8762
  */
8161
8763
  var Rubric = Type$1.Object({
8162
- rubric_id: Type$1.String({ minLength: 1 }),
8764
+ rubricId: Type$1.String({ minLength: 1 }),
8163
8765
  version: Type$1.String({ minLength: 1 }),
8164
8766
  preamble: Type$1.Optional(Type$1.String()),
8165
8767
  criteria: Type$1.Array(RubricCriterion, { minItems: 1 }),
8166
8768
  scope: Type$1.Optional(Type$1.String()),
8167
- content_hash: Type$1.Optional(Type$1.String())
8769
+ contentHash: Type$1.Optional(Type$1.String())
8168
8770
  }, {
8169
8771
  $id: "Rubric",
8170
8772
  additionalProperties: false
@@ -8220,25 +8822,25 @@ var AssessBriefCriterion = Type$1.Object({
8220
8822
  additionalProperties: false
8221
8823
  });
8222
8824
  var AssessBriefInput = Type$1.Object({
8223
- target_task_id: Type$1.String({ format: "uuid" }),
8825
+ targetTaskId: Type$1.String({ format: "uuid" }),
8224
8826
  criteria: Type$1.Array(AssessBriefCriterion, { minItems: 1 }),
8225
- rubric_preamble: Type$1.Optional(Type$1.String())
8827
+ rubricPreamble: Type$1.Optional(Type$1.String())
8226
8828
  }, {
8227
8829
  $id: "AssessBriefInput",
8228
8830
  additionalProperties: false
8229
8831
  });
8230
8832
  /** One score line. */
8231
8833
  var AssessBriefScore = Type$1.Object({
8232
- criterion_id: Type$1.String({ minLength: 1 }),
8834
+ criterionId: Type$1.String({ minLength: 1 }),
8233
8835
  score: Type$1.Number({
8234
8836
  minimum: 0,
8235
8837
  maximum: 1
8236
8838
  }),
8237
8839
  rationale: Type$1.Optional(Type$1.String()),
8238
8840
  evidence: Type$1.Optional(Type$1.Object({
8239
- commits_verified: Type$1.Number(),
8240
- commits_total: Type$1.Number(),
8241
- signature_failures: Type$1.Array(Type$1.String())
8841
+ commitsVerified: Type$1.Number(),
8842
+ commitsTotal: Type$1.Number(),
8843
+ signatureFailures: Type$1.Array(Type$1.String())
8242
8844
  }, { additionalProperties: false }))
8243
8845
  }, {
8244
8846
  $id: "AssessBriefScore",
@@ -8251,7 +8853,7 @@ var AssessBriefOutput = Type$1.Object({
8251
8853
  maximum: 1
8252
8854
  }),
8253
8855
  verdict: Type$1.String({ minLength: 1 }),
8254
- judge_model: Type$1.Optional(Type$1.String())
8856
+ judgeModel: Type$1.Optional(Type$1.String())
8255
8857
  }, {
8256
8858
  $id: "AssessBriefOutput",
8257
8859
  additionalProperties: false
@@ -8282,15 +8884,15 @@ var EntryTypeFilter = Type$1.Union([
8282
8884
  Type$1.Literal("reflection")
8283
8885
  ]);
8284
8886
  var CuratePackInput = Type$1.Object({
8285
- diary_id: Type$1.String({ format: "uuid" }),
8286
- task_prompt: Type$1.String({ minLength: 1 }),
8287
- entry_types: Type$1.Optional(Type$1.Array(EntryTypeFilter, { minItems: 1 })),
8288
- tag_filters: Type$1.Optional(Type$1.Object({
8887
+ diaryId: Type$1.String({ format: "uuid" }),
8888
+ taskPrompt: Type$1.String({ minLength: 1 }),
8889
+ entryTypes: Type$1.Optional(Type$1.Array(EntryTypeFilter, { minItems: 1 })),
8890
+ tagFilters: Type$1.Optional(Type$1.Object({
8289
8891
  include: Type$1.Optional(Type$1.Array(Type$1.String())),
8290
8892
  exclude: Type$1.Optional(Type$1.Array(Type$1.String())),
8291
8893
  prefix: Type$1.Optional(Type$1.String())
8292
8894
  }, { additionalProperties: false })),
8293
- token_budget: Type$1.Optional(Type$1.Number({ minimum: 500 })),
8895
+ tokenBudget: Type$1.Optional(Type$1.Number({ minimum: 500 })),
8294
8896
  recipe: Type$1.Optional(Type$1.Union([Type$1.Literal("topic-focused-v1"), Type$1.Literal("scope-inventory-v1")]))
8295
8897
  }, {
8296
8898
  $id: "CuratePackInput",
@@ -8302,18 +8904,18 @@ var CuratePackInput = Type$1.Object({
8302
8904
  * is the receipt.
8303
8905
  */
8304
8906
  var CuratePackOutput = Type$1.Object({
8305
- pack_id: Type$1.String({ format: "uuid" }),
8306
- pack_cid: Type$1.String({ minLength: 1 }),
8907
+ packId: Type$1.String({ format: "uuid" }),
8908
+ packCid: Type$1.String({ minLength: 1 }),
8307
8909
  entries: Type$1.Array(Type$1.Object({
8308
- entry_id: Type$1.String({ format: "uuid" }),
8910
+ entryId: Type$1.String({ format: "uuid" }),
8309
8911
  rank: Type$1.Number({ minimum: 1 }),
8310
8912
  rationale: Type$1.String({ minLength: 1 })
8311
8913
  }, { additionalProperties: false }), { minItems: 1 }),
8312
- recipe_params: Type$1.Record(Type$1.String(), Type$1.Unknown()),
8914
+ recipeParams: Type$1.Record(Type$1.String(), Type$1.Unknown()),
8313
8915
  checkpoints: Type$1.Optional(Type$1.Array(Type$1.Object({
8314
8916
  phase: Type$1.String({ minLength: 1 }),
8315
- candidate_ids: Type$1.Array(Type$1.String({ format: "uuid" })),
8316
- dropped_ids: Type$1.Optional(Type$1.Array(Type$1.String({ format: "uuid" }))),
8917
+ candidateIds: Type$1.Array(Type$1.String({ format: "uuid" })),
8918
+ droppedIds: Type$1.Optional(Type$1.Array(Type$1.String({ format: "uuid" }))),
8317
8919
  notes: Type$1.String({ minLength: 1 })
8318
8920
  }, { additionalProperties: false }))),
8319
8921
  summary: Type$1.String({ minLength: 1 })
@@ -8334,9 +8936,9 @@ var FULFILL_BRIEF_TYPE = "fulfill_brief";
8334
8936
  var FulfillBriefInput = Type$1.Object({
8335
8937
  brief: Type$1.String({ minLength: 1 }),
8336
8938
  title: Type$1.Optional(Type$1.String()),
8337
- acceptance_criteria: Type$1.Optional(Type$1.Array(Type$1.String())),
8338
- seed_files: Type$1.Optional(Type$1.Array(Type$1.String())),
8339
- scope_hint: Type$1.Optional(Type$1.String())
8939
+ acceptanceCriteria: Type$1.Optional(Type$1.Array(Type$1.String())),
8940
+ seedFiles: Type$1.Optional(Type$1.Array(Type$1.String())),
8941
+ scopeHint: Type$1.Optional(Type$1.String())
8340
8942
  }, {
8341
8943
  $id: "FulfillBriefInput",
8342
8944
  additionalProperties: false
@@ -8350,10 +8952,10 @@ var FulfillBriefOutput = Type$1.Object({
8350
8952
  commits: Type$1.Array(Type$1.Object({
8351
8953
  sha: Type$1.String({ minLength: 7 }),
8352
8954
  message: Type$1.String(),
8353
- diary_entry_id: Type$1.Union([Type$1.String({ format: "uuid" }), Type$1.Null()])
8955
+ diaryEntryId: Type$1.Union([Type$1.String({ format: "uuid" }), Type$1.Null()])
8354
8956
  }, { additionalProperties: false })),
8355
- pull_request_url: Type$1.Union([Type$1.String(), Type$1.Null()]),
8356
- diary_entry_ids: Type$1.Array(Type$1.String({ format: "uuid" })),
8957
+ pullRequestUrl: Type$1.Union([Type$1.String(), Type$1.Null()]),
8958
+ diaryEntryIds: Type$1.Array(Type$1.String({ format: "uuid" })),
8357
8959
  summary: Type$1.String({ minLength: 1 })
8358
8960
  }, {
8359
8961
  $id: "FulfillBriefOutput",
@@ -8384,8 +8986,8 @@ var FulfillBriefOutput = Type$1.Object({
8384
8986
  */
8385
8987
  var JUDGE_PACK_TYPE = "judge_pack";
8386
8988
  var JudgePackInput = Type$1.Object({
8387
- rendered_pack_id: Type$1.String({ format: "uuid" }),
8388
- source_pack_id: Type$1.String({ format: "uuid" }),
8989
+ renderedPackId: Type$1.String({ format: "uuid" }),
8990
+ sourcePackId: Type$1.String({ format: "uuid" }),
8389
8991
  rubric: Rubric
8390
8992
  }, {
8391
8993
  $id: "JudgePackInput",
@@ -8393,7 +8995,7 @@ var JudgePackInput = Type$1.Object({
8393
8995
  });
8394
8996
  /** One scored criterion. Mirrors `AssessBriefScore`. */
8395
8997
  var JudgePackScore = Type$1.Object({
8396
- criterion_id: Type$1.String({ minLength: 1 }),
8998
+ criterionId: Type$1.String({ minLength: 1 }),
8397
8999
  score: Type$1.Number({
8398
9000
  minimum: 0,
8399
9001
  maximum: 1
@@ -8411,8 +9013,8 @@ var JudgePackOutput = Type$1.Object({
8411
9013
  maximum: 1
8412
9014
  }),
8413
9015
  verdict: Type$1.String({ minLength: 1 }),
8414
- judge_model: Type$1.Optional(Type$1.String()),
8415
- renderer_binary_cid: Type$1.Optional(Type$1.Union([Type$1.String(), Type$1.Null()]))
9016
+ judgeModel: Type$1.Optional(Type$1.String()),
9017
+ rendererBinaryCid: Type$1.Optional(Type$1.Union([Type$1.String(), Type$1.Null()]))
8416
9018
  }, {
8417
9019
  $id: "JudgePackOutput",
8418
9020
  additionalProperties: false
@@ -8436,7 +9038,7 @@ var JudgePackOutput = Type$1.Object({
8436
9038
  */
8437
9039
  var RENDER_PACK_TYPE = "render_pack";
8438
9040
  var RenderPackInput = Type$1.Object({
8439
- pack_id: Type$1.String({ format: "uuid" }),
9041
+ packId: Type$1.String({ format: "uuid" }),
8440
9042
  persist: Type$1.Optional(Type$1.Boolean()),
8441
9043
  pinned: Type$1.Optional(Type$1.Boolean())
8442
9044
  }, {
@@ -8444,11 +9046,11 @@ var RenderPackInput = Type$1.Object({
8444
9046
  additionalProperties: false
8445
9047
  });
8446
9048
  var RenderPackOutput = Type$1.Object({
8447
- rendered_pack_id: Type$1.Union([Type$1.String({ format: "uuid" }), Type$1.Null()]),
8448
- rendered_cid: Type$1.String({ minLength: 1 }),
8449
- render_method: Type$1.String({ minLength: 1 }),
8450
- byte_size: Type$1.Number({ minimum: 0 }),
8451
- entries_rendered: Type$1.Number({ minimum: 0 }),
9049
+ renderedPackId: Type$1.Union([Type$1.String({ format: "uuid" }), Type$1.Null()]),
9050
+ renderedCid: Type$1.String({ minLength: 1 }),
9051
+ renderMethod: Type$1.String({ minLength: 1 }),
9052
+ byteSize: Type$1.Number({ minimum: 0 }),
9053
+ entriesRendered: Type$1.Number({ minimum: 0 }),
8452
9054
  summary: Type$1.String({ minLength: 1 })
8453
9055
  }, {
8454
9056
  $id: "RenderPackOutput",
@@ -8509,6 +9111,41 @@ var BUILT_IN_TASK_TYPES = {
8509
9111
  }
8510
9112
  };
8511
9113
  //#endregion
9114
+ //#region ../tasks/src/task-type-registry.ts
9115
+ var schemaCids = null;
9116
+ function getTaskTypeRegistry() {
9117
+ if (!schemaCids) throw new Error("Task type registry not initialized. Call initTaskTypeRegistry() first.");
9118
+ return schemaCids;
9119
+ }
9120
+ new Proxy({}, { get(_, prop) {
9121
+ if (typeof prop !== "string") return void 0;
9122
+ return getTaskTypeRegistry().get(prop);
9123
+ } });
9124
+ //#endregion
9125
+ //#region ../tasks/src/validation.ts
9126
+ function getTaskTypeEntry(taskType) {
9127
+ const taskTypes = BUILT_IN_TASK_TYPES;
9128
+ if (!Object.prototype.hasOwnProperty.call(taskTypes, taskType)) return;
9129
+ return taskTypes[taskType];
9130
+ }
9131
+ function formatField(prefix, path) {
9132
+ return path ? `${prefix}${path}` : prefix;
9133
+ }
9134
+ function schemaErrors(prefix, schema, value) {
9135
+ return [...Value.Errors(schema, value)].map((error) => ({
9136
+ field: formatField(prefix, error.path),
9137
+ message: error.message
9138
+ }));
9139
+ }
9140
+ function validateTaskOutput(taskType, output) {
9141
+ const entry = getTaskTypeEntry(taskType);
9142
+ if (!entry) return [{
9143
+ field: "taskType",
9144
+ message: `Unknown task type: ${taskType}`
9145
+ }];
9146
+ return schemaErrors("output", entry.outputSchema, output);
9147
+ }
9148
+ //#endregion
8512
9149
  //#region ../tasks/src/wire.ts
8513
9150
  /**
8514
9151
  * Wire-format types for the MoltNet Task model.
@@ -8520,7 +9157,7 @@ var BUILT_IN_TASK_TYPES = {
8520
9157
  * - `TaskReporter` output records (PR 0)
8521
9158
  *
8522
9159
  * Invariant: every property on `Task` is type-neutral (applies to all
8523
- * `task_type`s). Type-specific payloads live inside `input` / `output`
9160
+ * `taskType`s). Type-specific payloads live inside `input` / `output`
8524
9161
  * JSONB, validated against schemas registered under `task_types`.
8525
9162
  *
8526
9163
  * Identity rule:
@@ -8563,8 +9200,8 @@ var IsoTimestamp = Type$1.String({ format: "date-time" });
8563
9200
  * Embedded in `tasks.references` JSONB array.
8564
9201
  */
8565
9202
  var TaskRef = Type$1.Object({
8566
- task_id: Type$1.Union([Uuid, Type$1.Null()]),
8567
- output_cid: Cid,
9203
+ taskId: Type$1.Union([Uuid, Type$1.Null()]),
9204
+ outputCid: Cid,
8568
9205
  role: Type$1.Union([
8569
9206
  Type$1.Literal("judged_work"),
8570
9207
  Type$1.Literal("reviewed_diff"),
@@ -8593,11 +9230,11 @@ var TaskRef = Type$1.Object({
8593
9230
  * `TaskOutput.usage` for convenience.
8594
9231
  */
8595
9232
  var TaskUsage = Type$1.Object({
8596
- input_tokens: Type$1.Integer({ minimum: 0 }),
8597
- output_tokens: Type$1.Integer({ minimum: 0 }),
8598
- cache_read_tokens: Type$1.Optional(Type$1.Integer({ minimum: 0 })),
8599
- cache_write_tokens: Type$1.Optional(Type$1.Integer({ minimum: 0 })),
8600
- tool_calls: Type$1.Optional(Type$1.Integer({ minimum: 0 })),
9233
+ inputTokens: Type$1.Integer({ minimum: 0 }),
9234
+ outputTokens: Type$1.Integer({ minimum: 0 }),
9235
+ cacheReadTokens: Type$1.Optional(Type$1.Integer({ minimum: 0 })),
9236
+ cacheWriteTokens: Type$1.Optional(Type$1.Integer({ minimum: 0 })),
9237
+ toolCalls: Type$1.Optional(Type$1.Integer({ minimum: 0 })),
8601
9238
  model: Type$1.Optional(Type$1.String()),
8602
9239
  provider: Type$1.Optional(Type$1.String())
8603
9240
  }, {
@@ -8617,62 +9254,65 @@ var TaskError = Type$1.Object({
8617
9254
  additionalProperties: false
8618
9255
  });
8619
9256
  Type$1.Object({
8620
- agent_id: Type$1.Union([Uuid, Type$1.Null()]),
8621
- human_id: Type$1.Union([Uuid, Type$1.Null()])
9257
+ agentId: Type$1.Union([Uuid, Type$1.Null()]),
9258
+ humanId: Type$1.Union([Uuid, Type$1.Null()])
8622
9259
  }, {
8623
9260
  $id: "ActorPair",
8624
9261
  additionalProperties: false
8625
9262
  });
8626
9263
  Type$1.Object({
8627
9264
  id: Uuid,
8628
- task_type: Type$1.String({ minLength: 1 }),
8629
- team_id: Uuid,
8630
- diary_id: Type$1.Union([Uuid, Type$1.Null()]),
8631
- output_kind: OutputKind,
9265
+ taskType: Type$1.String({ minLength: 1 }),
9266
+ teamId: Uuid,
9267
+ diaryId: Type$1.Union([Uuid, Type$1.Null()]),
9268
+ outputKind: OutputKind,
8632
9269
  input: Type$1.Record(Type$1.String(), Type$1.Unknown()),
8633
- input_schema_cid: Cid,
8634
- input_cid: Cid,
8635
- criteria_cid: Type$1.Union([Cid, Type$1.Null()]),
9270
+ inputSchemaCid: Cid,
9271
+ inputCid: Cid,
9272
+ criteriaCid: Type$1.Union([Cid, Type$1.Null()]),
8636
9273
  references: Type$1.Array(TaskRef),
8637
- correlation_id: Type$1.Union([Uuid, Type$1.Null()]),
8638
- imposed_by_agent_id: Type$1.Union([Uuid, Type$1.Null()]),
8639
- imposed_by_human_id: Type$1.Union([Uuid, Type$1.Null()]),
8640
- accepted_attempt_n: Type$1.Union([Type$1.Number(), Type$1.Null()]),
9274
+ correlationId: Type$1.Union([Uuid, Type$1.Null()]),
9275
+ imposedByAgentId: Type$1.Union([Uuid, Type$1.Null()]),
9276
+ imposedByHumanId: Type$1.Union([Uuid, Type$1.Null()]),
9277
+ acceptedAttemptN: Type$1.Union([Type$1.Number(), Type$1.Null()]),
8641
9278
  status: TaskStatus,
8642
- queued_at: IsoTimestamp,
8643
- completed_at: Type$1.Union([IsoTimestamp, Type$1.Null()]),
8644
- expires_at: Type$1.Union([IsoTimestamp, Type$1.Null()]),
8645
- cancelled_by_agent_id: Type$1.Union([Uuid, Type$1.Null()]),
8646
- cancelled_by_human_id: Type$1.Union([Uuid, Type$1.Null()]),
8647
- cancel_reason: Type$1.Union([Type$1.String(), Type$1.Null()]),
8648
- max_attempts: Type$1.Number({ minimum: 1 })
9279
+ queuedAt: IsoTimestamp,
9280
+ completedAt: Type$1.Union([IsoTimestamp, Type$1.Null()]),
9281
+ expiresAt: Type$1.Union([IsoTimestamp, Type$1.Null()]),
9282
+ cancelledByAgentId: Type$1.Union([Uuid, Type$1.Null()]),
9283
+ cancelledByHumanId: Type$1.Union([Uuid, Type$1.Null()]),
9284
+ cancelReason: Type$1.Union([Type$1.String(), Type$1.Null()]),
9285
+ maxAttempts: Type$1.Number({ minimum: 1 })
8649
9286
  }, {
8650
9287
  $id: "Task",
8651
9288
  additionalProperties: false
8652
9289
  });
8653
9290
  Type$1.Object({
8654
- task_id: Uuid,
8655
- attempt_n: Type$1.Number({ minimum: 1 }),
8656
- claimed_by_agent_id: Uuid,
8657
- runtime_id: Type$1.Union([Uuid, Type$1.Null()]),
8658
- claimed_at: IsoTimestamp,
8659
- started_at: Type$1.Union([IsoTimestamp, Type$1.Null()]),
8660
- completed_at: Type$1.Union([IsoTimestamp, Type$1.Null()]),
9291
+ taskId: Uuid,
9292
+ attemptN: Type$1.Number({ minimum: 1 }),
9293
+ claimedByAgentId: Uuid,
9294
+ runtimeId: Type$1.Union([Uuid, Type$1.Null()]),
9295
+ claimedAt: IsoTimestamp,
9296
+ startedAt: Type$1.Union([IsoTimestamp, Type$1.Null()]),
9297
+ completedAt: Type$1.Union([IsoTimestamp, Type$1.Null()]),
8661
9298
  status: TaskAttemptStatus,
8662
9299
  output: Type$1.Union([Type$1.Record(Type$1.String(), Type$1.Unknown()), Type$1.Null()]),
8663
- output_cid: Type$1.Union([Cid, Type$1.Null()]),
9300
+ outputCid: Type$1.Union([Cid, Type$1.Null()]),
8664
9301
  error: Type$1.Union([TaskError, Type$1.Null()]),
8665
9302
  usage: Type$1.Union([TaskUsage, Type$1.Null()]),
8666
- content_signature: Type$1.Union([Type$1.String(), Type$1.Null()]),
8667
- signed_at: Type$1.Union([IsoTimestamp, Type$1.Null()])
9303
+ contentSignature: Type$1.Union([Type$1.String(), Type$1.Null()]),
9304
+ signedAt: Type$1.Union([IsoTimestamp, Type$1.Null()])
8668
9305
  }, {
8669
9306
  $id: "TaskAttempt",
8670
9307
  additionalProperties: false
8671
9308
  });
8672
9309
  Type$1.Object({
8673
- task_id: Uuid,
8674
- attempt_n: Type$1.Number({ minimum: 1 }),
8675
- seq: Type$1.Number({ minimum: 0 }),
9310
+ taskId: Uuid,
9311
+ attemptN: Type$1.Number({ minimum: 1 }),
9312
+ seq: Type$1.Number({
9313
+ minimum: 0,
9314
+ description: "Monotonically increasing integer assigned by the server. Use as the afterSeq cursor on the list-messages endpoint to poll for new messages without re-fetching earlier ones."
9315
+ }),
8676
9316
  timestamp: IsoTimestamp,
8677
9317
  kind: TaskMessageKind,
8678
9318
  payload: Type$1.Record(Type$1.String(), Type$1.Unknown())
@@ -8681,34 +9321,34 @@ Type$1.Object({
8681
9321
  additionalProperties: false
8682
9322
  });
8683
9323
  Type$1.Object({
8684
- task_id: Uuid,
8685
- attempt_n: Type$1.Number({ minimum: 1 }),
9324
+ taskId: Uuid,
9325
+ attemptN: Type$1.Number({ minimum: 1 }),
8686
9326
  status: Type$1.Union([
8687
9327
  Type$1.Literal("completed"),
8688
9328
  Type$1.Literal("failed"),
8689
9329
  Type$1.Literal("cancelled")
8690
9330
  ]),
8691
9331
  output: Type$1.Union([Type$1.Record(Type$1.String(), Type$1.Unknown()), Type$1.Null()]),
8692
- output_cid: Type$1.Union([Cid, Type$1.Null()]),
9332
+ outputCid: Type$1.Union([Cid, Type$1.Null()]),
8693
9333
  usage: TaskUsage,
8694
- duration_ms: Type$1.Number({ minimum: 0 }),
9334
+ durationMs: Type$1.Number({ minimum: 0 }),
8695
9335
  error: Type$1.Optional(TaskError),
8696
- content_signature: Type$1.Optional(Type$1.String())
9336
+ contentSignature: Type$1.Optional(Type$1.String())
8697
9337
  }, {
8698
9338
  $id: "TaskOutput",
8699
9339
  additionalProperties: false
8700
9340
  });
8701
9341
  Type$1.Object({
8702
- runtime_id: Uuid,
8703
- agent_id: Uuid,
9342
+ runtimeId: Uuid,
9343
+ agentId: Uuid,
8704
9344
  timestamp: IsoTimestamp,
8705
9345
  status: Type$1.Union([
8706
9346
  Type$1.Literal("idle"),
8707
9347
  Type$1.Literal("busy"),
8708
9348
  Type$1.Literal("draining")
8709
9349
  ]),
8710
- active_task_ids: Type$1.Array(Uuid),
8711
- supported_task_types: Type$1.Array(Type$1.String())
9350
+ activeTaskIds: Type$1.Array(Uuid),
9351
+ supportedTaskTypes: Type$1.Array(Type$1.String())
8712
9352
  }, {
8713
9353
  $id: "RuntimeHeartbeat",
8714
9354
  additionalProperties: false
@@ -8729,10 +9369,10 @@ function buildAssessBriefPrompt(input, ctx) {
8729
9369
  ...ctx.target.diaryEntryIds.map((id) => `- ${id}`),
8730
9370
  ""
8731
9371
  ].join("\n") : "";
8732
- const preambleSection = input.rubric_preamble ? [
9372
+ const preambleSection = input.rubricPreamble ? [
8733
9373
  "### Rubric preamble",
8734
9374
  "",
8735
- input.rubric_preamble,
9375
+ input.rubricPreamble,
8736
9376
  ""
8737
9377
  ].join("\n") : "";
8738
9378
  return [
@@ -8763,12 +9403,12 @@ function buildAssessBriefPrompt(input, ctx) {
8763
9403
  "",
8764
9404
  "- `llm_judged`: score 0..1 continuous. `rationale` REQUIRED (2–4 sentences).",
8765
9405
  "- `boolean`: score exactly 0 or 1. `rationale` optional.",
8766
- "- `deterministic_signature_check`: run `moltnet entry verify` on every diary entry listed above AND `git verify-commit` on every commit. Score 1 iff ALL signatures are valid; otherwise 0. Populate `evidence.commits_verified`, `evidence.commits_total`, `evidence.signature_failures`.",
9406
+ "- `deterministic_signature_check`: run `moltnet entry verify` on every diary entry listed above AND `git verify-commit` on every commit. Score 1 iff ALL signatures are valid; otherwise 0. Populate `evidence.commitsVerified`, `evidence.commitsTotal`, `evidence.signatureFailures`.",
8767
9407
  "",
8768
9408
  "### Final output",
8769
9409
  "",
8770
9410
  "Emit a JSON object matching `AssessBriefOutput`:",
8771
- " { \"scores\": [{criterion_id, score, rationale?, evidence?}], \"composite\", \"verdict\", \"judge_model\"? }",
9411
+ " { \"scores\": [{criterionId, score, rationale?, evidence?}], \"composite\", \"verdict\", \"judgeModel\"? }",
8772
9412
  "`composite` = Σ(weight_i × score_i) recomputed. The runtime will reject a mismatch.",
8773
9413
  "Write a signed diary entry (tags: \"judgment\", \"assess_brief\") capturing the rationale before emitting the JSON."
8774
9414
  ].filter(Boolean).join("\n");
@@ -8790,7 +9430,7 @@ function buildAssessBriefPrompt(input, ctx) {
8790
9430
  * N isolated `createAgentSession` children (one per tag cluster or
8791
9431
  * entry_type axis the curator picks after recon), each with a narrow
8792
9432
  * tool subset and a turn cap, and returns compressed summaries. Parent
8793
- * curator keeps a warm context and only sees {candidate_ids, notes}
9433
+ * curator keeps a warm context and only sees {candidateIds, notes}
8794
9434
  * per probe — mirrors the fan-out pattern pi-mono SDK example #13
8795
9435
  * (session runtime) + #05 (custom tools) makes possible. Until that
8796
9436
  * lands, the `checkpoints[]` output field is the fallback: curator
@@ -8798,7 +9438,7 @@ function buildAssessBriefPrompt(input, ctx) {
8798
9438
  * resume without replaying the tool history.
8799
9439
  */
8800
9440
  function buildCuratePackPrompt(input, ctx) {
8801
- const { diary_id: diaryId, task_prompt: taskPrompt, entry_types: entryTypes, tag_filters: tagFilters, token_budget: tokenBudget, recipe } = input;
9441
+ const { diaryId, taskPrompt, entryTypes, tagFilters, tokenBudget, recipe } = input;
8802
9442
  const effectiveEntryTypes = entryTypes ?? [
8803
9443
  "semantic",
8804
9444
  "episodic",
@@ -8885,7 +9525,7 @@ function buildCuratePackPrompt(input, ctx) {
8885
9525
  "",
8886
9526
  "The tool returns a JSON payload whose top-level fields are `packId` and",
8887
9527
  "`packCid` (NOT `id`). Copy those exact UUID/CID strings verbatim into",
8888
- "`pack_id` and `pack_cid` in your final output — do not substitute an",
9528
+ "`packId` and `packCid` in your final output — do not substitute an",
8889
9529
  "entry id, do not reformat, do not fabricate a UUID.",
8890
9530
  "",
8891
9531
  "## Hard constraints",
@@ -8902,14 +9542,14 @@ function buildCuratePackPrompt(input, ctx) {
8902
9542
  "Write to stdout a JSON object matching `CuratePackOutput`:",
8903
9543
  "```",
8904
9544
  "{",
8905
- " \"pack_id\": \"<uuid>\",",
8906
- " \"pack_cid\": \"<cid>\",",
9545
+ " \"packId\": \"<uuid>\",",
9546
+ " \"packCid\": \"<cid>\",",
8907
9547
  " \"entries\": [",
8908
- " { \"entry_id\": \"<uuid>\", \"rank\": 1, \"rationale\": \"<why>\" }",
9548
+ " { \"entryId\": \"<uuid>\", \"rank\": 1, \"rationale\": \"<why>\" }",
8909
9549
  " ],",
8910
- " \"recipe_params\": { \"recipe\": \"...\", \"prompt\": \"...\", ... },",
9550
+ " \"recipeParams\": { \"recipe\": \"...\", \"prompt\": \"...\", ... },",
8911
9551
  " \"checkpoints\": [",
8912
- " { \"phase\": \"recon\", \"candidate_ids\": [...], \"dropped_ids\": [...], \"notes\": \"...\" }",
9552
+ " { \"phase\": \"recon\", \"candidateIds\": [...], \"droppedIds\": [...], \"notes\": \"...\" }",
8913
9553
  " ],",
8914
9554
  " \"summary\": \"<2-4 sentences: what you looked for, how you narrowed, what defines the final set>\"",
8915
9555
  "}",
@@ -8927,7 +9567,7 @@ function buildCuratePackPrompt(input, ctx) {
8927
9567
  * is told to inspect them itself.
8928
9568
  */
8929
9569
  function buildFulfillBriefPrompt(input, ctx) {
8930
- const { brief, title, acceptance_criteria: acceptanceCriteria, seed_files: seedFiles, scope_hint: scopeHint } = input;
9570
+ const { brief, title, acceptanceCriteria, seedFiles, scopeHint } = input;
8931
9571
  const criteriaSection = acceptanceCriteria?.length ? [
8932
9572
  "### Acceptance criteria",
8933
9573
  "",
@@ -8976,14 +9616,14 @@ function buildFulfillBriefPrompt(input, ctx) {
8976
9616
  "### Final output",
8977
9617
  "",
8978
9618
  "When done, write to stdout a JSON object with shape matching `FulfillBriefOutput`:",
8979
- " { \"branch\", \"commits\": [{sha, message, diary_entry_id}], \"pull_request_url\", \"diary_entry_ids\", \"summary\" }",
9619
+ " { \"branch\", \"commits\": [{sha, message, diaryEntryId}], \"pullRequestUrl\", \"diaryEntryIds\", \"summary\" }",
8980
9620
  "The runtime parses this as the structured task output. Failing to emit it is a failure."
8981
9621
  ].filter(Boolean).join("\n");
8982
9622
  }
8983
9623
  //#endregion
8984
9624
  //#region ../agent-runtime/src/prompts/judge-pack.ts
8985
9625
  function buildJudgePackPrompt(input, ctx) {
8986
- const { rendered_pack_id: renderedPackId, source_pack_id: sourcePackId, rubric } = input;
9626
+ const { renderedPackId, sourcePackId, rubric } = input;
8987
9627
  const criteriaList = rubric.criteria.map((c, i) => `${i + 1}. **${c.id}** (weight ${c.weight}, scoring: \`${c.scoring}\`) — ${c.description}`).join("\n");
8988
9628
  const preambleSection = rubric.preamble ? [
8989
9629
  "### Rubric preamble",
@@ -9006,7 +9646,7 @@ function buildJudgePackPrompt(input, ctx) {
9006
9646
  "",
9007
9647
  `- **Rendered pack**: \`${renderedPackId}\``,
9008
9648
  `- **Source pack**: \`${sourcePackId}\``,
9009
- `- **Rubric**: \`${rubric.rubric_id}\` v${rubric.version}`,
9649
+ `- **Rubric**: \`${rubric.rubricId}\` v${rubric.version}`,
9010
9650
  "",
9011
9651
  preambleSection,
9012
9652
  "## Workflow",
@@ -9045,12 +9685,12 @@ function buildJudgePackPrompt(input, ctx) {
9045
9685
  " non-empty `contentSignature`. If `required_signed_total` is 0,",
9046
9686
  " score = 1. Populate `evidence` with `{ entries_verified,",
9047
9687
  " entries_total, required_signed_total, required_signed_ok,",
9048
- " signature_failures: [entry_ids] }` where `signature_failures` lists",
9688
+ " signatureFailures: [entryIds] }` where `signatureFailures` lists",
9049
9689
  " ONLY the REQUIRED-SIGNED entries that lack a signature.",
9050
9690
  "- `deterministic_coverage_check`: for every source entry, check",
9051
- " whether its `entry_id` (or a stable reference like title + CID",
9691
+ " whether its `entryId` (or a stable reference like title + CID",
9052
9692
  " prefix) appears in the rendered `content`. Score 1 iff coverage is",
9053
- " complete; otherwise 0. Populate `evidence` with `{ covered, total, missing: [entry_ids] }`.",
9693
+ " complete; otherwise 0. Populate `evidence` with `{ covered, total, missing: [entryIds] }`.",
9054
9694
  "",
9055
9695
  "## Constraints",
9056
9696
  "",
@@ -9064,17 +9704,17 @@ function buildJudgePackPrompt(input, ctx) {
9064
9704
  "Write to stdout a JSON object matching `JudgePackOutput`:",
9065
9705
  "```",
9066
9706
  "{",
9067
- " \"scores\": [{\"criterion_id\": \"...\", \"score\": 0.0, \"rationale\": \"...\", \"evidence\": {...}}],",
9707
+ " \"scores\": [{\"criterionId\": \"...\", \"score\": 0.0, \"rationale\": \"...\", \"evidence\": {...}}],",
9068
9708
  " \"composite\": <sum-of-weighted-scores>,",
9069
9709
  " \"verdict\": \"<1-3 sentence overall>\",",
9070
- " \"judge_model\": \"<provider:model>\",",
9071
- " \"renderer_binary_cid\": \"<cid-string-only-if-available>\"",
9710
+ " \"judgeModel\": \"<provider:model>\",",
9711
+ " \"rendererBinaryCid\": \"<cid-string-only-if-available>\"",
9072
9712
  "}",
9073
9713
  "```",
9074
- "Omit `renderer_binary_cid` entirely when no binary CID is exposed by",
9714
+ "Omit `rendererBinaryCid` entirely when no binary CID is exposed by",
9075
9715
  "`moltnet_rendered_pack_get`. Do NOT emit `null` — the field is optional",
9076
9716
  "and absence is the correct representation when unavailable.",
9077
- `Write a signed diary entry (tags: \`judgment\`, \`judge_pack\`, \`rubric:${rubric.rubric_id}\`) capturing the rationale before`,
9717
+ `Write a signed diary entry (tags: \`judgment\`, \`judge_pack\`, \`rubric:${rubric.rubricId}\`) capturing the rationale before`,
9078
9718
  "emitting the JSON."
9079
9719
  ].filter((l) => l !== null).join("\n");
9080
9720
  }
@@ -9085,7 +9725,7 @@ function buildJudgePackPrompt(input, ctx) {
9085
9725
  * wraps `moltnet_pack_render` and emits the receipt.
9086
9726
  */
9087
9727
  function buildRenderPackPrompt(input, ctx) {
9088
- const { pack_id: packId, persist = true, pinned = false } = input;
9728
+ const { packId, persist = true, pinned = false } = input;
9089
9729
  return [
9090
9730
  "# Render Pack Agent",
9091
9731
  "",
@@ -9125,11 +9765,11 @@ function buildRenderPackPrompt(input, ctx) {
9125
9765
  "Write to stdout a JSON object matching `RenderPackOutput`:",
9126
9766
  "```",
9127
9767
  "{",
9128
- " \"rendered_pack_id\": \"<uuid-or-null>\",",
9129
- " \"rendered_cid\": \"<cid>\",",
9130
- " \"render_method\": \"<label>\",",
9131
- " \"byte_size\": <int>,",
9132
- " \"entries_rendered\": <int>,",
9768
+ " \"renderedPackId\": \"<uuid-or-null>\",",
9769
+ " \"renderedCid\": \"<cid>\",",
9770
+ " \"renderMethod\": \"<label>\",",
9771
+ " \"byteSize\": <int>,",
9772
+ " \"entriesRendered\": <int>,",
9133
9773
  " \"summary\": \"<1-3 sentence recap>\"",
9134
9774
  "}",
9135
9775
  "```",
@@ -9139,11 +9779,11 @@ function buildRenderPackPrompt(input, ctx) {
9139
9779
  //#endregion
9140
9780
  //#region ../agent-runtime/src/prompts/index.ts
9141
9781
  /**
9142
- * Resolve the correct prompt builder for `task.task_type` and invoke it.
9782
+ * Resolve the correct prompt builder for `task.taskType` and invoke it.
9143
9783
  * Throws if the type is unknown or the input fails TypeBox validation.
9144
9784
  */
9145
9785
  function buildPromptForTask(task, ctx) {
9146
- switch (task.task_type) {
9786
+ switch (task.taskType) {
9147
9787
  case FULFILL_BRIEF_TYPE:
9148
9788
  if (!Value.Check(FulfillBriefInput, task.input)) {
9149
9789
  const errors = [...Value.Errors(FulfillBriefInput, task.input)];
@@ -9193,10 +9833,102 @@ function buildPromptForTask(task, ctx) {
9193
9833
  diaryId: ctx.diaryId,
9194
9834
  taskId: ctx.taskId
9195
9835
  });
9196
- default: throw new Error(`No prompt builder registered for task_type="${task.task_type}"`);
9836
+ default: throw new Error(`No prompt builder registered for taskType="${task.taskType}"`);
9197
9837
  }
9198
9838
  }
9199
9839
  //#endregion
9840
+ //#region src/runtime/task-output.ts
9841
+ async function parseStructuredTaskOutput(assistantText, taskType) {
9842
+ const extracted = extractJsonObject(assistantText);
9843
+ if (!extracted) return {
9844
+ output: null,
9845
+ outputCid: null,
9846
+ error: {
9847
+ code: "output_missing",
9848
+ message: "Agent did not emit a parseable JSON object as its final message."
9849
+ }
9850
+ };
9851
+ const errors = validateTaskOutput(taskType, extracted);
9852
+ if (errors.length > 0) {
9853
+ const details = errors.slice(0, 3).map((error) => `${error.field}: ${error.message}`);
9854
+ const [firstError] = errors;
9855
+ return {
9856
+ output: null,
9857
+ outputCid: null,
9858
+ error: {
9859
+ code: firstError?.field === "taskType" ? "unknown_task_type" : "output_validation_failed",
9860
+ message: `Output failed schema validation: ${details.join("; ")}`
9861
+ }
9862
+ };
9863
+ }
9864
+ try {
9865
+ return {
9866
+ output: extracted,
9867
+ outputCid: await computeJsonCid(extracted),
9868
+ error: null
9869
+ };
9870
+ } catch (error) {
9871
+ return {
9872
+ output: null,
9873
+ outputCid: null,
9874
+ error: {
9875
+ code: "output_cid_compute_failed",
9876
+ message: `Validated output could not be canonicalized: ${error instanceof Error ? error.message : String(error)}`
9877
+ }
9878
+ };
9879
+ }
9880
+ }
9881
+ /**
9882
+ * Find the last balanced top-level JSON object in `text` and parse it.
9883
+ * Tolerates markdown fences and leading prose. Returns null if parsing fails.
9884
+ */
9885
+ function extractJsonObject(text) {
9886
+ if (!text) return null;
9887
+ const fenceMatch = /```(?:json)?\s*([\s\S]*?)```/gi;
9888
+ const candidates = [];
9889
+ for (const m of text.matchAll(fenceMatch)) candidates.push(m[1]);
9890
+ const scanForObject = (s) => {
9891
+ let depth = 0;
9892
+ let start = -1;
9893
+ let lastComplete = null;
9894
+ let inString = false;
9895
+ let escape = false;
9896
+ for (let i = 0; i < s.length; i++) {
9897
+ const ch = s[i];
9898
+ if (inString) {
9899
+ if (escape) escape = false;
9900
+ else if (ch === "\\") escape = true;
9901
+ else if (ch === "\"") inString = false;
9902
+ continue;
9903
+ }
9904
+ if (ch === "\"") {
9905
+ inString = true;
9906
+ continue;
9907
+ }
9908
+ if (ch === "{") {
9909
+ if (depth === 0) start = i;
9910
+ depth++;
9911
+ } else if (ch === "}") {
9912
+ depth--;
9913
+ if (depth === 0 && start !== -1) {
9914
+ lastComplete = s.slice(start, i + 1);
9915
+ start = -1;
9916
+ }
9917
+ }
9918
+ }
9919
+ return lastComplete;
9920
+ };
9921
+ candidates.push(text);
9922
+ for (let i = candidates.length - 1; i >= 0; i--) {
9923
+ const obj = scanForObject(candidates[i]);
9924
+ if (!obj) continue;
9925
+ try {
9926
+ return JSON.parse(obj);
9927
+ } catch {}
9928
+ }
9929
+ return null;
9930
+ }
9931
+ //#endregion
9200
9932
  //#region src/runtime/execute-pi-task.ts
9201
9933
  /**
9202
9934
  * executePiTask — run a single Task attempt using pi-coding-agent inside a
@@ -9221,14 +9953,14 @@ function buildPromptForTask(task, ctx) {
9221
9953
  */
9222
9954
  function createPiTaskExecutor(opts) {
9223
9955
  let cachedCheckpoint = opts.checkpointPath ?? null;
9224
- return async (task, reporter) => {
9956
+ return async (claimedTask, reporter) => {
9225
9957
  if (!cachedCheckpoint) cachedCheckpoint = await ensureSnapshot({
9226
9958
  config: opts.sandboxConfig?.snapshot,
9227
9959
  onProgress: opts.onSnapshotProgress ?? ((m) => {
9228
9960
  process.stderr.write(`[snapshot] ${m}\n`);
9229
9961
  })
9230
9962
  });
9231
- return executePiTask(task, reporter, {
9963
+ return executePiTask(claimedTask, reporter, {
9232
9964
  ...opts,
9233
9965
  checkpointPath: cachedCheckpoint
9234
9966
  });
@@ -9240,8 +9972,9 @@ function createPiTaskExecutor(opts) {
9240
9972
  * a `TaskOutput` (failures surface as `status: 'failed'`); throws only on
9241
9973
  * unrecoverable setup errors.
9242
9974
  */
9243
- async function executePiTask(task, reporter, opts) {
9244
- const attemptN = opts.attemptN ?? 1;
9975
+ async function executePiTask(claimedTask, reporter, opts) {
9976
+ const task = claimedTask.task;
9977
+ const attemptN = claimedTask.attemptN;
9245
9978
  const startTime = Date.now();
9246
9979
  const mountPath = opts.mountPath ?? process.cwd();
9247
9980
  const checkpointPath = opts.checkpointPath ?? await ensureSnapshot({
@@ -9267,17 +10000,18 @@ async function executePiTask(task, reporter, opts) {
9267
10000
  extraAllowedHosts: opts.extraAllowedHosts,
9268
10001
  sandboxConfig: opts.sandboxConfig
9269
10002
  });
9270
- const diaryId = task.diary_id ?? "";
10003
+ const diaryId = task.diaryId ?? "";
9271
10004
  let reporterOpen = false;
9272
10005
  let session = null;
9273
- const makeFailedOutput = (code, message, usage = emptyUsage(opts.provider, opts.model)) => ({
9274
- task_id: task.id,
9275
- attempt_n: attemptN,
10006
+ const finalUsage = emptyUsage(opts.provider, opts.model);
10007
+ const makeFailedOutput = (code, message, usage = finalUsage) => ({
10008
+ taskId: task.id,
10009
+ attemptN,
9276
10010
  status: "failed",
9277
10011
  output: null,
9278
- output_cid: null,
10012
+ outputCid: null,
9279
10013
  usage,
9280
- duration_ms: Date.now() - startTime,
10014
+ durationMs: Date.now() - startTime,
9281
10015
  error: {
9282
10016
  code,
9283
10017
  message,
@@ -9298,8 +10032,8 @@ async function executePiTask(task, reporter, opts) {
9298
10032
  });
9299
10033
  await emit("info", {
9300
10034
  event: "execute_start",
9301
- task_type: task.task_type,
9302
- team_id: task.team_id,
10035
+ taskType: task.taskType,
10036
+ teamId: task.teamId,
9303
10037
  provider: opts.provider,
9304
10038
  model: opts.model
9305
10039
  });
@@ -9330,7 +10064,9 @@ async function executePiTask(task, reporter, opts) {
9330
10064
  getAgent: () => moltnetAgent,
9331
10065
  getDiaryId: () => diaryId,
9332
10066
  getSessionErrors: () => [],
9333
- clearSessionErrors: () => {}
10067
+ clearSessionErrors: () => {},
10068
+ getHostCwd: () => mountPath,
10069
+ hostExecBaseEnv: new Set([...HOST_EXEC_DEFAULT_BASE_ENV, ...Object.keys(managed.credentials.agentEnv)])
9334
10070
  });
9335
10071
  const piAuthDir = join(homedir(), ".pi", "agent");
9336
10072
  const modelHandle = getModel(opts.provider, opts.model);
@@ -9358,7 +10094,7 @@ async function executePiTask(task, reporter, opts) {
9358
10094
  let llmAbort = false;
9359
10095
  let assistantText = "";
9360
10096
  let reporterError = null;
9361
- const usage = emptyUsage(opts.provider, opts.model);
10097
+ const usage = finalUsage;
9362
10098
  const recordingPromise = [];
9363
10099
  const track = (p) => {
9364
10100
  recordingPromise.push(p.catch((err) => {
@@ -9388,12 +10124,12 @@ async function executePiTask(task, reporter, opts) {
9388
10124
  else if (event.type === "turn_end") {
9389
10125
  const msg = event.message;
9390
10126
  if (msg?.role === "assistant" && msg.usage) {
9391
- usage.input_tokens += Math.max(0, msg.usage.input ?? 0);
9392
- usage.output_tokens += Math.max(0, msg.usage.output ?? 0);
10127
+ usage.inputTokens += Math.max(0, msg.usage.input ?? 0);
10128
+ usage.outputTokens += Math.max(0, msg.usage.output ?? 0);
9393
10129
  const cr = Math.max(0, msg.usage.cacheRead ?? 0);
9394
10130
  const cw = Math.max(0, msg.usage.cacheWrite ?? 0);
9395
- if (cr) usage.cache_read_tokens = (usage.cache_read_tokens ?? 0) + cr;
9396
- if (cw) usage.cache_write_tokens = (usage.cache_write_tokens ?? 0) + cw;
10131
+ if (cr) usage.cacheReadTokens = (usage.cacheReadTokens ?? 0) + cr;
10132
+ if (cw) usage.cacheWriteTokens = (usage.cacheWriteTokens ?? 0) + cw;
9397
10133
  }
9398
10134
  track(emit("turn_end", { stop_reason: msg?.stopReason ?? "end_turn" }));
9399
10135
  llmAbort = msg?.stopReason === "error";
@@ -9415,28 +10151,13 @@ async function executePiTask(task, reporter, opts) {
9415
10151
  }
9416
10152
  await Promise.all(recordingPromise);
9417
10153
  let parsedOutput = null;
10154
+ let parsedOutputCid = null;
9418
10155
  let parseError = null;
9419
10156
  if (!runError && !llmAbort) {
9420
- const extracted = extractJsonObject(assistantText);
9421
- if (!extracted) parseError = {
9422
- code: "output_missing",
9423
- message: "Agent did not emit a parseable JSON object as its final message."
9424
- };
9425
- else {
9426
- const entry = BUILT_IN_TASK_TYPES[task.task_type];
9427
- if (!entry) parseError = {
9428
- code: "unknown_task_type",
9429
- message: `No output schema registered for task_type=${task.task_type}`
9430
- };
9431
- else {
9432
- const check = TypeCompiler.Compile(entry.outputSchema);
9433
- if (check.Check(extracted)) parsedOutput = extracted;
9434
- else parseError = {
9435
- code: "output_validation_failed",
9436
- message: `Output failed schema validation: ${[...check.Errors(extracted)].slice(0, 3).map((e) => `${e.path}: ${e.message}`).join("; ")}`
9437
- };
9438
- }
9439
- }
10157
+ const parsed = await parseStructuredTaskOutput(assistantText, task.taskType);
10158
+ parsedOutput = parsed.output;
10159
+ parsedOutputCid = parsed.outputCid;
10160
+ parseError = parsed.error;
9440
10161
  if (parseError) await emit("error", {
9441
10162
  message: parseError.message,
9442
10163
  phase: "output_validation"
@@ -9446,13 +10167,13 @@ async function executePiTask(task, reporter, opts) {
9446
10167
  const errorCode = runError?.code ?? parseError?.code ?? reporterError?.code ?? (llmAbort ? "llm_api_error" : void 0);
9447
10168
  const errorMessage = runError?.message ?? parseError?.message ?? reporterError?.message ?? (llmAbort ? "LLM API error during turn" : void 0);
9448
10169
  return {
9449
- task_id: task.id,
9450
- attempt_n: attemptN,
10170
+ taskId: task.id,
10171
+ attemptN,
9451
10172
  status,
9452
10173
  output: parsedOutput,
9453
- output_cid: null,
10174
+ outputCid: parsedOutputCid,
9454
10175
  usage,
9455
- duration_ms: Date.now() - startTime,
10176
+ durationMs: Date.now() - startTime,
9456
10177
  ...errorCode && errorMessage ? { error: {
9457
10178
  code: errorCode,
9458
10179
  message: errorMessage,
@@ -9467,7 +10188,7 @@ async function executePiTask(task, reporter, opts) {
9467
10188
  } catch {}
9468
10189
  if (reporterOpen) {
9469
10190
  try {
9470
- await reporter.finalize(emptyUsage(opts.provider, opts.model));
10191
+ await reporter.finalize(finalUsage);
9471
10192
  } catch {}
9472
10193
  try {
9473
10194
  await reporter.close();
@@ -9478,8 +10199,8 @@ async function executePiTask(task, reporter, opts) {
9478
10199
  }
9479
10200
  function emptyUsage(provider, model) {
9480
10201
  return {
9481
- input_tokens: 0,
9482
- output_tokens: 0,
10202
+ inputTokens: 0,
10203
+ outputTokens: 0,
9483
10204
  provider,
9484
10205
  model
9485
10206
  };
@@ -9513,79 +10234,16 @@ function truncateForWire(value) {
9513
10234
  };
9514
10235
  }
9515
10236
  }
9516
- /**
9517
- * Find the last balanced top-level JSON object in `text` and parse it.
9518
- * Tolerates markdown fences and leading prose. Returns null if parsing fails.
9519
- */
9520
- function extractJsonObject(text) {
9521
- if (!text) return null;
9522
- const fenceMatch = /```(?:json)?\s*([\s\S]*?)```/gi;
9523
- const candidates = [];
9524
- for (const m of text.matchAll(fenceMatch)) candidates.push(m[1]);
9525
- const scanForObject = (s) => {
9526
- let depth = 0;
9527
- let start = -1;
9528
- let lastComplete = null;
9529
- let inString = false;
9530
- let escape = false;
9531
- for (let i = 0; i < s.length; i++) {
9532
- const ch = s[i];
9533
- if (inString) {
9534
- if (escape) escape = false;
9535
- else if (ch === "\\") escape = true;
9536
- else if (ch === "\"") inString = false;
9537
- continue;
9538
- }
9539
- if (ch === "\"") {
9540
- inString = true;
9541
- continue;
9542
- }
9543
- if (ch === "{") {
9544
- if (depth === 0) start = i;
9545
- depth++;
9546
- } else if (ch === "}") {
9547
- depth--;
9548
- if (depth === 0 && start !== -1) {
9549
- lastComplete = s.slice(start, i + 1);
9550
- start = -1;
9551
- }
9552
- }
9553
- }
9554
- return lastComplete;
9555
- };
9556
- candidates.push(text);
9557
- for (let i = candidates.length - 1; i >= 0; i--) {
9558
- const obj = scanForObject(candidates[i]);
9559
- if (!obj) continue;
9560
- try {
9561
- return JSON.parse(obj);
9562
- } catch {}
9563
- }
9564
- return null;
9565
- }
9566
10237
  //#endregion
9567
10238
  //#region src/index.ts
9568
10239
  /**
9569
10240
  * @themoltnet/pi-extension — MoltNet pi extension
9570
10241
  *
9571
- * Sandboxes tool execution in a Gondolin VM with:
9572
- * - Auto-built and cached VM snapshots
9573
- * - Credential injection (pi OAuth + MoltNet identity)
9574
- * - Egress policy (only LLM provider + MoltNet API)
9575
- * - Tool redirection (read/write/edit/bash → VM)
9576
- * - MoltNet custom tools (diary entries — run on host via SDK)
9577
- * - Optional git worktree per session
10242
+ * Runs pi coding-agent sessions inside a Gondolin VM with the agent's
10243
+ * MoltNet identity fully available inside the sandbox.
9578
10244
  *
9579
- * Usage:
9580
- * pi -e @themoltnet/pi-extension
9581
- * pi -e @themoltnet/pi-extension --agent legreffier
9582
- * pi -e @themoltnet/pi-extension --worktree-branch feat/my-task
9583
- * pi -e @themoltnet/pi-extension --sandbox-config ./sandbox.json
9584
- *
9585
- * Sandbox config resolution (first match):
9586
- * 1. --sandbox-config flag (explicit path to JSON)
9587
- * 2. sandbox.json in cwd (convention)
9588
- * 3. Base only (git, gh, moltnet CLI, agent user)
10245
+ * See README.md for credential injection flow, tool split, sandbox.json
10246
+ * reference, and headless/programmatic usage.
9589
10247
  */
9590
10248
  var GUEST_WORKSPACE = "/workspace";
9591
10249
  function moltnetExtension(pi) {
@@ -9623,6 +10281,7 @@ function moltnetExtension(pi) {
9623
10281
  let worktreePath = null;
9624
10282
  let moltnetAgent = null;
9625
10283
  let diaryId = null;
10284
+ let hostExecBaseEnv = HOST_EXEC_DEFAULT_BASE_ENV;
9626
10285
  async function ensureVm(ctx) {
9627
10286
  if (vm) return vm;
9628
10287
  if (vmStarting) return vmStarting;
@@ -9676,6 +10335,7 @@ function moltnetExtension(pi) {
9676
10335
  activateAgentEnv(managed.credentials.agentEnv, mainRepo);
9677
10336
  moltnetAgent = await connect({ configDir: managed.agentDir });
9678
10337
  diaryId = managed.credentials.agentEnv.MOLTNET_DIARY_ID ?? null;
10338
+ hostExecBaseEnv = new Set([...HOST_EXEC_DEFAULT_BASE_ENV, ...Object.keys(managed.credentials.agentEnv)]);
9679
10339
  vm = managed.vm;
9680
10340
  const label = worktreePath ? `${mountPath} → ${GUEST_WORKSPACE}` : `${localCwd} → ${GUEST_WORKSPACE}`;
9681
10341
  ctx?.ui.setStatus("sandbox", ctx.ui.theme.fg("accent", `Sandbox: running (${label})`));
@@ -9738,7 +10398,9 @@ function moltnetExtension(pi) {
9738
10398
  getSessionErrors: () => sessionErrors,
9739
10399
  clearSessionErrors: () => {
9740
10400
  sessionErrors.length = 0;
9741
- }
10401
+ },
10402
+ getHostCwd: () => worktreePath ?? localCwd,
10403
+ hostExecBaseEnv
9742
10404
  });
9743
10405
  for (const tool of moltnetTools) pi.registerTool(tool);
9744
10406
  const sessionStartTime = Date.now();
@@ -9844,4 +10506,4 @@ function moltnetExtension(pi) {
9844
10506
  registerMoltnetReflectCommand(pi, state);
9845
10507
  }
9846
10508
  //#endregion
9847
- export { activateAgentEnv, buildPiJudgeRecipeManifest, computePiJudgeRecipeCid, createGondolinBashOps, createGondolinEditOps, createGondolinReadOps, createGondolinWriteOps, createMoltNetTools, createPiTaskExecutor, moltnetExtension as default, ensureSnapshot, executePiTask, findMainWorktree, loadCredentials, resolvePiJudgeRecipeVersions, resumeVm, toGuestPath };
10509
+ export { HOST_EXEC_DEFAULT_BASE_ENV, activateAgentEnv, buildPiJudgeRecipeManifest, computePiJudgeRecipeCid, createGondolinBashOps, createGondolinEditOps, createGondolinReadOps, createGondolinWriteOps, createMoltNetTools, createPiTaskExecutor, moltnetExtension as default, ensureSnapshot, executePiTask, findMainWorktree, loadCredentials, resolvePiJudgeRecipeVersions, resumeVm, toGuestPath };