@themoltnet/pi-extension 0.4.0 → 0.6.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.
Files changed (4) hide show
  1. package/README.md +204 -41
  2. package/dist/index.d.ts +188 -7
  3. package/dist/index.js +1600 -1098
  4. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -3,17 +3,16 @@ import { execFileSync } from "node:child_process";
3
3
  import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync } from "node:fs";
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
- import { createHash, randomUUID } from "node:crypto";
6
+ import { createHash } from "node:crypto";
7
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";
11
- import { fileURLToPath } from "node:url";
12
11
  import { RealFSProvider, ShadowProvider, VM, VmCheckpoint, createHttpHooks, createShadowPathPredicate, ensureImageSelector, loadGuestAssets } from "@earendil-works/gondolin";
13
12
  import { parseEnv } from "node:util";
13
+ import { fileURLToPath } from "node:url";
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({
@@ -1416,80 +1415,6 @@ var updateRenderedPack = (options) => (options.client ?? client).patch({
1416
1415
  }
1417
1416
  });
1418
1417
  /**
1419
- * Trigger fidelity verification for an agent-rendered pack.
1420
- */
1421
- var verifyRenderedPack = (options) => (options.client ?? client).post({
1422
- security: [
1423
- {
1424
- scheme: "bearer",
1425
- type: "http"
1426
- },
1427
- {
1428
- name: "X-Moltnet-Session-Token",
1429
- type: "apiKey"
1430
- },
1431
- {
1432
- in: "cookie",
1433
- name: "ory_kratos_session",
1434
- type: "apiKey"
1435
- }
1436
- ],
1437
- url: "/rendered-packs/{id}/verify",
1438
- ...options,
1439
- headers: {
1440
- "Content-Type": "application/json",
1441
- ...options.headers
1442
- }
1443
- });
1444
- /**
1445
- * Judge claims verification payload (source entries, rendered content, and rubric).
1446
- */
1447
- var claimVerification = (options) => (options.client ?? client).post({
1448
- security: [
1449
- {
1450
- scheme: "bearer",
1451
- type: "http"
1452
- },
1453
- {
1454
- name: "X-Moltnet-Session-Token",
1455
- type: "apiKey"
1456
- },
1457
- {
1458
- in: "cookie",
1459
- name: "ory_kratos_session",
1460
- type: "apiKey"
1461
- }
1462
- ],
1463
- url: "/rendered-packs/{id}/verify/claim",
1464
- ...options
1465
- });
1466
- /**
1467
- * Judge submits fidelity scores and transcript.
1468
- */
1469
- var submitVerification = (options) => (options.client ?? client).post({
1470
- security: [
1471
- {
1472
- scheme: "bearer",
1473
- type: "http"
1474
- },
1475
- {
1476
- name: "X-Moltnet-Session-Token",
1477
- type: "apiKey"
1478
- },
1479
- {
1480
- in: "cookie",
1481
- name: "ory_kratos_session",
1482
- type: "apiKey"
1483
- }
1484
- ],
1485
- url: "/rendered-packs/{id}/verify/submit",
1486
- ...options,
1487
- headers: {
1488
- "Content-Type": "application/json",
1489
- ...options.headers
1490
- }
1491
- });
1492
- /**
1493
1418
  * Get an agent's public profile by key fingerprint (A1B2-C3D4-E5F6-G7H8).
1494
1419
  */
1495
1420
  var getAgentProfile = (options) => (options.client ?? client).get({
@@ -2039,109 +1964,379 @@ var getLegreffierOnboardingStatus = (options) => (options.client ?? client).get(
2039
1964
  ...options
2040
1965
  });
2041
1966
  /**
2042
- * List all problem types used in API error responses (RFC 9457).
1967
+ * List tasks for a team with optional filters.
2043
1968
  */
2044
- var listProblemTypes = (options) => (options?.client ?? client).get({
2045
- url: "/problems",
1969
+ var listTasks = (options) => (options.client ?? client).get({
1970
+ security: [
1971
+ {
1972
+ scheme: "bearer",
1973
+ type: "http"
1974
+ },
1975
+ {
1976
+ name: "X-Moltnet-Session-Token",
1977
+ type: "apiKey"
1978
+ },
1979
+ {
1980
+ in: "cookie",
1981
+ name: "ory_kratos_session",
1982
+ type: "apiKey"
1983
+ }
1984
+ ],
1985
+ url: "/tasks",
2046
1986
  ...options
2047
1987
  });
2048
1988
  /**
2049
- * Get details about a specific problem type (RFC 9457).
1989
+ * Create and enqueue a new task.
2050
1990
  */
2051
- var getProblemType = (options) => (options.client ?? client).get({
2052
- url: "/problems/{type}",
1991
+ var createTask = (options) => (options.client ?? client).post({
1992
+ security: [
1993
+ {
1994
+ scheme: "bearer",
1995
+ type: "http"
1996
+ },
1997
+ {
1998
+ name: "X-Moltnet-Session-Token",
1999
+ type: "apiKey"
2000
+ },
2001
+ {
2002
+ in: "cookie",
2003
+ name: "ory_kratos_session",
2004
+ type: "apiKey"
2005
+ }
2006
+ ],
2007
+ url: "/tasks",
2008
+ ...options,
2009
+ headers: {
2010
+ "Content-Type": "application/json",
2011
+ ...options.headers
2012
+ }
2013
+ });
2014
+ /**
2015
+ * Get a task by ID.
2016
+ */
2017
+ var getTask = (options) => (options.client ?? client).get({
2018
+ security: [
2019
+ {
2020
+ scheme: "bearer",
2021
+ type: "http"
2022
+ },
2023
+ {
2024
+ name: "X-Moltnet-Session-Token",
2025
+ type: "apiKey"
2026
+ },
2027
+ {
2028
+ in: "cookie",
2029
+ name: "ory_kratos_session",
2030
+ type: "apiKey"
2031
+ }
2032
+ ],
2033
+ url: "/tasks/{id}",
2053
2034
  ...options
2054
2035
  });
2055
- //#endregion
2056
- //#region ../api-client/src/retry-fetch.ts
2057
- var DEFAULT_RETRY_STATUSES = [
2058
- 408,
2059
- 429,
2060
- 500,
2061
- 502,
2062
- 503,
2063
- 504
2064
- ];
2065
- var DEFAULT_RETRY_METHODS = [
2066
- "GET",
2067
- "HEAD",
2068
- "OPTIONS",
2069
- "PUT"
2070
- ];
2071
- function createRetryFetch$1(options) {
2072
- const { maxRetries = 3, baseDelay = 500, maxDelay = 1e4, retryStatuses = DEFAULT_RETRY_STATUSES, retryMethods = DEFAULT_RETRY_METHODS, retryOnNetworkError = true, baseFetch = globalThis.fetch, jitter = true, onRetry } = options ?? {};
2073
- const retryMethodSet = new Set(retryMethods.map((m) => m.toUpperCase()));
2074
- return async function retryFetch(input, init) {
2075
- const method = (input instanceof Request ? input.method : init?.method ?? "GET").toUpperCase();
2076
- let lastError;
2077
- let lastResponse;
2078
- for (let attempt = 0; attempt <= maxRetries; attempt++) try {
2079
- const response = await baseFetch(input instanceof Request ? input.clone() : input, init);
2080
- const isRateLimited = response.status === 429;
2081
- if (!(retryStatuses.includes(response.status) && (isRateLimited || retryMethodSet.has(method))) || attempt === maxRetries) return response;
2082
- lastResponse = response;
2083
- await response.body?.cancel().catch(() => {});
2084
- const delay = computeDelay(attempt, baseDelay, maxDelay, jitter, response);
2085
- onRetry?.(attempt, delay, `status ${response.status}`);
2086
- await sleep(delay);
2087
- } catch (err) {
2088
- lastError = err;
2089
- if (!retryOnNetworkError || !retryMethodSet.has(method) || attempt === maxRetries) throw err;
2090
- const delay = computeDelay(attempt, baseDelay, maxDelay, jitter);
2091
- onRetry?.(attempt, delay, "network error");
2092
- await sleep(delay);
2036
+ /**
2037
+ * Claim a queued task and start an attempt.
2038
+ */
2039
+ var claimTask = (options) => (options.client ?? client).post({
2040
+ security: [
2041
+ {
2042
+ scheme: "bearer",
2043
+ type: "http"
2044
+ },
2045
+ {
2046
+ name: "X-Moltnet-Session-Token",
2047
+ type: "apiKey"
2048
+ },
2049
+ {
2050
+ in: "cookie",
2051
+ name: "ory_kratos_session",
2052
+ type: "apiKey"
2093
2053
  }
2094
- if (lastResponse) return lastResponse;
2095
- throw lastError;
2096
- };
2097
- }
2098
- function computeDelay(attempt, baseDelay, maxDelay, jitter, response) {
2099
- const retryAfter = response?.headers.get("Retry-After");
2100
- if (retryAfter) {
2101
- const seconds = Number(retryAfter);
2102
- if (!Number.isNaN(seconds)) return Math.min(seconds * 1e3, maxDelay);
2103
- const date = Date.parse(retryAfter);
2104
- if (!Number.isNaN(date)) return Math.min(Math.max(date - Date.now(), 0), maxDelay);
2054
+ ],
2055
+ url: "/tasks/{id}/claim",
2056
+ ...options,
2057
+ headers: {
2058
+ "Content-Type": "application/json",
2059
+ ...options.headers
2105
2060
  }
2106
- const exponential = baseDelay * 2 ** attempt;
2107
- const jitterMs = jitter ? Math.random() * baseDelay : 0;
2108
- return Math.min(exponential + jitterMs, maxDelay);
2109
- }
2110
- function sleep(ms) {
2111
- return new Promise((resolve) => {
2112
- setTimeout(resolve, ms);
2113
- });
2114
- }
2115
- function createRateLimitFetch(options) {
2116
- return createRetryFetch$1({
2117
- maxRetries: options?.maxRetries ?? 3,
2118
- baseDelay: options?.baseDelayMs ?? 1e3,
2119
- maxDelay: options?.maxDelayMs ?? 3e4,
2120
- retryStatuses: [429],
2121
- retryMethods: [
2122
- "GET",
2123
- "HEAD",
2124
- "OPTIONS",
2125
- "PUT",
2126
- "POST",
2127
- "PATCH",
2128
- "DELETE"
2129
- ],
2130
- retryOnNetworkError: false
2131
- });
2132
- }
2133
- //#endregion
2134
- //#region ../sdk/src/errors.ts
2135
- var MoltNetError = class extends Error {
2136
- code;
2137
- statusCode;
2138
- detail;
2139
- constructor(message, options) {
2140
- super(message);
2141
- this.name = "MoltNetError";
2142
- this.code = options.code;
2143
- this.statusCode = options.statusCode;
2144
- this.detail = options.detail;
2061
+ });
2062
+ /**
2063
+ * Send a heartbeat to keep the attempt lease alive.
2064
+ */
2065
+ var taskHeartbeat = (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/{id}/attempts/{n}/heartbeat",
2082
+ ...options,
2083
+ headers: {
2084
+ "Content-Type": "application/json",
2085
+ ...options.headers
2086
+ }
2087
+ });
2088
+ /**
2089
+ * Mark an attempt as completed with output.
2090
+ */
2091
+ var completeTask = (options) => (options.client ?? client).post({
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}/attempts/{n}/complete",
2108
+ ...options,
2109
+ headers: {
2110
+ "Content-Type": "application/json",
2111
+ ...options.headers
2112
+ }
2113
+ });
2114
+ /**
2115
+ * Mark an attempt as failed with error details.
2116
+ */
2117
+ var failTask = (options) => (options.client ?? client).post({
2118
+ security: [
2119
+ {
2120
+ scheme: "bearer",
2121
+ type: "http"
2122
+ },
2123
+ {
2124
+ name: "X-Moltnet-Session-Token",
2125
+ type: "apiKey"
2126
+ },
2127
+ {
2128
+ in: "cookie",
2129
+ name: "ory_kratos_session",
2130
+ type: "apiKey"
2131
+ }
2132
+ ],
2133
+ url: "/tasks/{id}/attempts/{n}/fail",
2134
+ ...options,
2135
+ headers: {
2136
+ "Content-Type": "application/json",
2137
+ ...options.headers
2138
+ }
2139
+ });
2140
+ /**
2141
+ * Cancel a task.
2142
+ */
2143
+ var cancelTask = (options) => (options.client ?? client).post({
2144
+ security: [
2145
+ {
2146
+ scheme: "bearer",
2147
+ type: "http"
2148
+ },
2149
+ {
2150
+ name: "X-Moltnet-Session-Token",
2151
+ type: "apiKey"
2152
+ },
2153
+ {
2154
+ in: "cookie",
2155
+ name: "ory_kratos_session",
2156
+ type: "apiKey"
2157
+ }
2158
+ ],
2159
+ url: "/tasks/{id}/cancel",
2160
+ ...options,
2161
+ headers: {
2162
+ "Content-Type": "application/json",
2163
+ ...options.headers
2164
+ }
2165
+ });
2166
+ /**
2167
+ * List all attempts for a task.
2168
+ */
2169
+ var listTaskAttempts = (options) => (options.client ?? client).get({
2170
+ security: [
2171
+ {
2172
+ scheme: "bearer",
2173
+ type: "http"
2174
+ },
2175
+ {
2176
+ name: "X-Moltnet-Session-Token",
2177
+ type: "apiKey"
2178
+ },
2179
+ {
2180
+ in: "cookie",
2181
+ name: "ory_kratos_session",
2182
+ type: "apiKey"
2183
+ }
2184
+ ],
2185
+ url: "/tasks/{id}/attempts",
2186
+ ...options
2187
+ });
2188
+ /**
2189
+ * List messages for a task attempt.
2190
+ */
2191
+ var listTaskMessages = (options) => (options.client ?? client).get({
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}/messages",
2208
+ ...options
2209
+ });
2210
+ /**
2211
+ * Append messages to a task attempt.
2212
+ */
2213
+ var appendTaskMessages = (options) => (options.client ?? client).post({
2214
+ security: [
2215
+ {
2216
+ scheme: "bearer",
2217
+ type: "http"
2218
+ },
2219
+ {
2220
+ name: "X-Moltnet-Session-Token",
2221
+ type: "apiKey"
2222
+ },
2223
+ {
2224
+ in: "cookie",
2225
+ name: "ory_kratos_session",
2226
+ type: "apiKey"
2227
+ }
2228
+ ],
2229
+ url: "/tasks/{id}/attempts/{n}/messages",
2230
+ ...options,
2231
+ headers: {
2232
+ "Content-Type": "application/json",
2233
+ ...options.headers
2234
+ }
2235
+ });
2236
+ /**
2237
+ * List all problem types used in API error responses (RFC 9457).
2238
+ */
2239
+ var listProblemTypes = (options) => (options?.client ?? client).get({
2240
+ url: "/problems",
2241
+ ...options
2242
+ });
2243
+ /**
2244
+ * Get details about a specific problem type (RFC 9457).
2245
+ */
2246
+ var getProblemType = (options) => (options.client ?? client).get({
2247
+ url: "/problems/{type}",
2248
+ ...options
2249
+ });
2250
+ //#endregion
2251
+ //#region ../api-client/src/retry-fetch.ts
2252
+ var DEFAULT_RETRY_STATUSES = [
2253
+ 408,
2254
+ 429,
2255
+ 500,
2256
+ 502,
2257
+ 503,
2258
+ 504
2259
+ ];
2260
+ var DEFAULT_RETRY_METHODS = [
2261
+ "GET",
2262
+ "HEAD",
2263
+ "OPTIONS",
2264
+ "PUT"
2265
+ ];
2266
+ function createRetryFetch$1(options) {
2267
+ const { maxRetries = 3, baseDelay = 500, maxDelay = 1e4, retryStatuses = DEFAULT_RETRY_STATUSES, retryMethods = DEFAULT_RETRY_METHODS, retryOnNetworkError = true, baseFetch = globalThis.fetch, jitter = true, onRetry } = options ?? {};
2268
+ const retryMethodSet = new Set(retryMethods.map((m) => m.toUpperCase()));
2269
+ return async function retryFetch(input, init) {
2270
+ const method = (input instanceof Request ? input.method : init?.method ?? "GET").toUpperCase();
2271
+ let lastError;
2272
+ let lastResponse;
2273
+ for (let attempt = 0; attempt <= maxRetries; attempt++) try {
2274
+ const response = await baseFetch(input instanceof Request ? input.clone() : input, init);
2275
+ const isRateLimited = response.status === 429;
2276
+ if (!(retryStatuses.includes(response.status) && (isRateLimited || retryMethodSet.has(method))) || attempt === maxRetries) return response;
2277
+ lastResponse = response;
2278
+ await response.body?.cancel().catch(() => {});
2279
+ const delay = computeDelay(attempt, baseDelay, maxDelay, jitter, response);
2280
+ onRetry?.(attempt, delay, `status ${response.status}`);
2281
+ await sleep(delay);
2282
+ } catch (err) {
2283
+ lastError = err;
2284
+ if (!retryOnNetworkError || !retryMethodSet.has(method) || attempt === maxRetries) throw err;
2285
+ const delay = computeDelay(attempt, baseDelay, maxDelay, jitter);
2286
+ onRetry?.(attempt, delay, "network error");
2287
+ await sleep(delay);
2288
+ }
2289
+ if (lastResponse) return lastResponse;
2290
+ throw lastError;
2291
+ };
2292
+ }
2293
+ function computeDelay(attempt, baseDelay, maxDelay, jitter, response) {
2294
+ const retryAfter = response?.headers.get("Retry-After");
2295
+ if (retryAfter) {
2296
+ const seconds = Number(retryAfter);
2297
+ if (!Number.isNaN(seconds)) return Math.min(seconds * 1e3, maxDelay);
2298
+ const date = Date.parse(retryAfter);
2299
+ if (!Number.isNaN(date)) return Math.min(Math.max(date - Date.now(), 0), maxDelay);
2300
+ }
2301
+ const exponential = baseDelay * 2 ** attempt;
2302
+ const jitterMs = jitter ? Math.random() * baseDelay : 0;
2303
+ return Math.min(exponential + jitterMs, maxDelay);
2304
+ }
2305
+ function sleep(ms) {
2306
+ return new Promise((resolve) => {
2307
+ setTimeout(resolve, ms);
2308
+ });
2309
+ }
2310
+ function createRateLimitFetch(options) {
2311
+ return createRetryFetch$1({
2312
+ maxRetries: options?.maxRetries ?? 3,
2313
+ baseDelay: options?.baseDelayMs ?? 1e3,
2314
+ maxDelay: options?.maxDelayMs ?? 3e4,
2315
+ retryStatuses: [429],
2316
+ retryMethods: [
2317
+ "GET",
2318
+ "HEAD",
2319
+ "OPTIONS",
2320
+ "PUT",
2321
+ "POST",
2322
+ "PATCH",
2323
+ "DELETE"
2324
+ ],
2325
+ retryOnNetworkError: false
2326
+ });
2327
+ }
2328
+ //#endregion
2329
+ //#region ../sdk/src/errors.ts
2330
+ var MoltNetError = class extends Error {
2331
+ code;
2332
+ statusCode;
2333
+ detail;
2334
+ constructor(message, options) {
2335
+ super(message);
2336
+ this.name = "MoltNetError";
2337
+ this.code = options.code;
2338
+ this.statusCode = options.statusCode;
2339
+ this.detail = options.detail;
2145
2340
  }
2146
2341
  };
2147
2342
  var NetworkError = class extends MoltNetError {
@@ -3059,7 +3254,7 @@ function decode$3(string, alphabetIdx, bitsPerChar, name) {
3059
3254
  if (bits >= bitsPerChar || (255 & buffer << 8 - bits) !== 0) throw new SyntaxError("Unexpected end of data");
3060
3255
  return out;
3061
3256
  }
3062
- function encode$1(data, alphabet, bitsPerChar) {
3257
+ function encode$2(data, alphabet, bitsPerChar) {
3063
3258
  const pad = alphabet[alphabet.length - 1] === "=";
3064
3259
  const mask = (1 << bitsPerChar) - 1;
3065
3260
  let out = "";
@@ -3091,7 +3286,7 @@ function rfc4648({ name, prefix, bitsPerChar, alphabet }) {
3091
3286
  prefix,
3092
3287
  name,
3093
3288
  encode(input) {
3094
- return encode$1(input, alphabet, bitsPerChar);
3289
+ return encode$2(input, alphabet, bitsPerChar);
3095
3290
  },
3096
3291
  decode(input) {
3097
3292
  return decode$3(input, alphabetIdx, bitsPerChar, name);
@@ -3180,14 +3375,14 @@ baseX({
3180
3375
  });
3181
3376
  //#endregion
3182
3377
  //#region ../../node_modules/.pnpm/multiformats@13.4.2/node_modules/multiformats/dist/src/vendor/varint.js
3183
- var encode_1 = encode;
3378
+ var encode_1 = encode$1;
3184
3379
  var MSB = 128, MSBALL = -128, INT = Math.pow(2, 31);
3185
3380
  /**
3186
3381
  * @param {number} num
3187
3382
  * @param {number[]} out
3188
3383
  * @param {number} offset
3189
3384
  */
3190
- function encode(num, out, offset) {
3385
+ function encode$1(num, out, offset) {
3191
3386
  out = out || [];
3192
3387
  offset = offset || 0;
3193
3388
  var oldOffset = offset;
@@ -3200,7 +3395,7 @@ function encode(num, out, offset) {
3200
3395
  num >>>= 7;
3201
3396
  }
3202
3397
  out[offset] = num | 0;
3203
- encode.bytes = offset - oldOffset + 1;
3398
+ encode$1.bytes = offset - oldOffset + 1;
3204
3399
  return out;
3205
3400
  }
3206
3401
  var decode$2 = read;
@@ -4069,8 +4264,13 @@ etc.sha512Sync = (...m) => {
4069
4264
  m.forEach((msg) => hash.update(msg));
4070
4265
  return hash.digest();
4071
4266
  };
4072
- new TextEncoder();
4267
+ //#endregion
4268
+ //#region ../../node_modules/.pnpm/multiformats@13.4.2/node_modules/multiformats/dist/src/codecs/json.js
4269
+ var textEncoder$1 = new TextEncoder();
4073
4270
  new TextDecoder();
4271
+ function encode(node) {
4272
+ return textEncoder$1.encode(JSON.stringify(node));
4273
+ }
4074
4274
  //#endregion
4075
4275
  //#region ../../node_modules/.pnpm/multiformats@13.4.2/node_modules/multiformats/dist/src/hashes/hasher.js
4076
4276
  var DEFAULT_MIN_DIGEST_LENGTH = 20;
@@ -4117,7 +4317,9 @@ function createDigest(digest, code, truncate) {
4117
4317
  }
4118
4318
  return create(code, digest);
4119
4319
  }
4120
- from({
4320
+ //#endregion
4321
+ //#region ../../node_modules/.pnpm/multiformats@13.4.2/node_modules/multiformats/dist/src/hashes/sha2.js
4322
+ var sha256 = from({
4121
4323
  name: "sha2-256",
4122
4324
  code: 18,
4123
4325
  encode: (input) => coerce(crypto.createHash("sha256").update(input).digest())
@@ -4128,6 +4330,20 @@ from({
4128
4330
  encode: (input) => coerce(crypto.createHash("sha512").update(input).digest())
4129
4331
  });
4130
4332
  //#endregion
4333
+ //#region ../crypto-service/src/json-cid.ts
4334
+ /**
4335
+ * Generic JSON CID — CIDv1 for arbitrary JSON-serialisable values.
4336
+ *
4337
+ * Uses the dag-json codec and sha2-256, producing a base32lower CIDv1.
4338
+ * Suitable for content-addressing task inputs, schema objects, and other
4339
+ * JSON payloads that don't need diary-entry canonical normalisation.
4340
+ */
4341
+ async function computeJsonCid(value) {
4342
+ const bytes = encode(value);
4343
+ const hash = await sha256.digest(bytes);
4344
+ return CID.create(1, 512, hash).toString();
4345
+ }
4346
+ //#endregion
4131
4347
  //#region ../../node_modules/.pnpm/cborg@4.5.8/node_modules/cborg/lib/is.js
4132
4348
  var objectTypeNames = [
4133
4349
  "Object",
@@ -6155,43 +6371,20 @@ function createPacksNamespace(context) {
6155
6371
  body
6156
6372
  }));
6157
6373
  },
6158
- async verifyRendered(id, body) {
6159
- return unwrapResult(await verifyRenderedPack({
6374
+ async create(diaryId, body) {
6375
+ return unwrapResult(await createDiaryCustomPack({
6160
6376
  client,
6161
6377
  auth,
6162
- path: { id },
6378
+ path: { id: diaryId },
6163
6379
  body
6164
6380
  }));
6165
6381
  },
6166
- async claimVerification(id) {
6167
- return unwrapResult(await claimVerification({
6382
+ async preview(diaryId, body) {
6383
+ return unwrapResult(await previewDiaryCustomPack({
6168
6384
  client,
6169
6385
  auth,
6170
- path: { id }
6171
- }));
6172
- },
6173
- async submitVerification(id, body) {
6174
- return unwrapResult(await submitVerification({
6175
- client,
6176
- auth,
6177
- path: { id },
6178
- body
6179
- }));
6180
- },
6181
- async create(diaryId, body) {
6182
- return unwrapResult(await createDiaryCustomPack({
6183
- client,
6184
- auth,
6185
- path: { id: diaryId },
6186
- body
6187
- }));
6188
- },
6189
- async preview(diaryId, body) {
6190
- return unwrapResult(await previewDiaryCustomPack({
6191
- client,
6192
- auth,
6193
- path: { id: diaryId },
6194
- body
6386
+ path: { id: diaryId },
6387
+ body
6195
6388
  }));
6196
6389
  }
6197
6390
  };
@@ -6302,6 +6495,124 @@ function createSigningRequestsNamespace(context) {
6302
6495
  };
6303
6496
  }
6304
6497
  //#endregion
6498
+ //#region ../sdk/src/namespaces/tasks.ts
6499
+ function createTasksNamespace(context) {
6500
+ const { client, auth } = context;
6501
+ return {
6502
+ async list(query) {
6503
+ return unwrapResult(await listTasks({
6504
+ client,
6505
+ auth,
6506
+ query
6507
+ }));
6508
+ },
6509
+ async create(body) {
6510
+ return unwrapResult(await createTask({
6511
+ client,
6512
+ auth,
6513
+ body
6514
+ }));
6515
+ },
6516
+ async get(id) {
6517
+ return unwrapResult(await getTask({
6518
+ client,
6519
+ auth,
6520
+ path: { id }
6521
+ }));
6522
+ },
6523
+ async claim(id, body) {
6524
+ const result = await claimTask({
6525
+ client,
6526
+ auth,
6527
+ path: { id },
6528
+ body
6529
+ });
6530
+ const data = unwrapResult(result);
6531
+ const traceHeaders = {};
6532
+ const traceparent = result.response.headers.get("traceparent");
6533
+ if (traceparent) {
6534
+ traceHeaders["traceparent"] = traceparent;
6535
+ const tracestate = result.response.headers.get("tracestate");
6536
+ if (tracestate) traceHeaders["tracestate"] = tracestate;
6537
+ }
6538
+ return {
6539
+ ...data,
6540
+ traceHeaders
6541
+ };
6542
+ },
6543
+ async heartbeat(id, n, body) {
6544
+ return unwrapResult(await taskHeartbeat({
6545
+ client,
6546
+ auth,
6547
+ path: {
6548
+ id,
6549
+ n
6550
+ },
6551
+ body
6552
+ }));
6553
+ },
6554
+ async complete(id, n, body) {
6555
+ return unwrapResult(await completeTask({
6556
+ client,
6557
+ auth,
6558
+ path: {
6559
+ id,
6560
+ n
6561
+ },
6562
+ body
6563
+ }));
6564
+ },
6565
+ async fail(id, n, body) {
6566
+ return unwrapResult(await failTask({
6567
+ client,
6568
+ auth,
6569
+ path: {
6570
+ id,
6571
+ n
6572
+ },
6573
+ body
6574
+ }));
6575
+ },
6576
+ async cancel(id, body) {
6577
+ return unwrapResult(await cancelTask({
6578
+ client,
6579
+ auth,
6580
+ path: { id },
6581
+ body
6582
+ }));
6583
+ },
6584
+ async listAttempts(id) {
6585
+ return unwrapResult(await listTaskAttempts({
6586
+ client,
6587
+ auth,
6588
+ path: { id }
6589
+ }));
6590
+ },
6591
+ async listMessages(id, n, query) {
6592
+ return unwrapResult(await listTaskMessages({
6593
+ client,
6594
+ auth,
6595
+ path: {
6596
+ id,
6597
+ n
6598
+ },
6599
+ query
6600
+ }));
6601
+ },
6602
+ async appendMessages(id, n, body) {
6603
+ return unwrapResult(await appendTaskMessages({
6604
+ client,
6605
+ auth,
6606
+ path: {
6607
+ id,
6608
+ n
6609
+ },
6610
+ body
6611
+ }));
6612
+ }
6613
+ };
6614
+ }
6615
+ //#endregion
6305
6616
  //#region ../sdk/src/namespaces/teams.ts
6306
6617
  function createTeamsNamespace(context) {
6307
6618
  const { client, auth } = context;
@@ -6433,6 +6744,7 @@ function createAgent(options) {
6433
6744
  legreffier: createLegreffierNamespace(context),
6434
6745
  problems: createProblemsNamespace(context),
6435
6746
  teams: createTeamsNamespace(context),
6747
+ tasks: createTasksNamespace(context),
6436
6748
  client,
6437
6749
  getToken: () => tokenManager.getToken()
6438
6750
  };
@@ -6758,21 +7070,6 @@ var registerSandboxCommand = (pi, state) => {
6758
7070
  };
6759
7071
  //#endregion
6760
7072
  //#region src/moltnet/judge/assets.ts
6761
- /**
6762
- * Judge assets — single source of truth.
6763
- *
6764
- * `DEFAULT_RUBRIC` and `JUDGE_SYSTEM_PROMPT` below ARE the assets. There are
6765
- * no companion `.md` files; tsc does not copy non-TS files into `dist/`, and
6766
- * keeping a parallel markdown copy invited drift between source-of-truth
6767
- * versions, which is what previously happened.
6768
- *
6769
- * The asset path constants are opaque identifiers used in the judge-recipe
6770
- * CID manifest so verifiers can trace which asset set a given Pi extension
6771
- * version emitted. They are NOT filesystem paths and are never read.
6772
- * Bump the version suffix when you change the corresponding constant.
6773
- */
6774
- var RUBRIC_ASSET_PATH = "pi-extension/judge/rubric@v1";
6775
- var JUDGE_PROMPT_ASSET_PATH = "pi-extension/judge/system-prompt@v1";
6776
7073
  /** Default fidelity rubric — kept verbatim from the Go judge. */
6777
7074
  var DEFAULT_RUBRIC = `Evaluate the rendered content against the source entries on three axes:
6778
7075
 
@@ -6925,135 +7222,6 @@ function buildSourceEntriesMarkdown(entries) {
6925
7222
  return parts.join("\n");
6926
7223
  }
6927
7224
  //#endregion
6928
- //#region src/moltnet/judge-recipe-cid.ts
6929
- var require = createRequire(import.meta.url);
6930
- var SELF_PACKAGE_NAME = "@themoltnet/pi-extension";
6931
- var PI_PACKAGE_NAME = "@mariozechner/pi-coding-agent";
6932
- var SDK_PACKAGE_NAME = "@themoltnet/sdk";
6933
- var CID_VERSION = 1;
6934
- var RAW_CODEC = 85;
6935
- var SHA2_256_CODE = 18;
6936
- var BASE32_ALPHABET = "abcdefghijklmnopqrstuvwxyz234567";
6937
- function findSelfPackageDir() {
6938
- const start = path.dirname(fileURLToPath(import.meta.url));
6939
- let dir = start;
6940
- while (true) {
6941
- const candidate = path.join(dir, "package.json");
6942
- if (existsSync(candidate)) {
6943
- if (JSON.parse(readFileSync(candidate, "utf8")).name === SELF_PACKAGE_NAME) return dir;
6944
- }
6945
- const parent = path.dirname(dir);
6946
- if (parent === dir) return start;
6947
- dir = parent;
6948
- }
6949
- }
6950
- var PACKAGE_DIR = findSelfPackageDir();
6951
- function sha256Hex(value) {
6952
- return createHash("sha256").update(value, "utf8").digest("hex");
6953
- }
6954
- function encodeVarint(value) {
6955
- const bytes = [];
6956
- let current = value >>> 0;
6957
- while (current >= 128) {
6958
- bytes.push(current & 127 | 128);
6959
- current >>>= 7;
6960
- }
6961
- bytes.push(current);
6962
- return bytes;
6963
- }
6964
- function base32Lower(bytes) {
6965
- let bits = 0;
6966
- let value = 0;
6967
- let output = "";
6968
- for (const byte of bytes) {
6969
- value = value << 8 | byte;
6970
- bits += 8;
6971
- while (bits >= 5) {
6972
- output += BASE32_ALPHABET[value >>> bits - 5 & 31];
6973
- bits -= 5;
6974
- }
6975
- }
6976
- if (bits > 0) output += BASE32_ALPHABET[value << 5 - bits & 31];
6977
- return `b${output}`;
6978
- }
6979
- function stableStringify(value) {
6980
- if (value === null || typeof value !== "object") return JSON.stringify(value);
6981
- if (Array.isArray(value)) return `[${value.map((item) => stableStringify(item)).join(",")}]`;
6982
- return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${stableStringify(item)}`).join(",")}}`;
6983
- }
6984
- function readPackageVersion(pkgPath, expectedName) {
6985
- if (!existsSync(pkgPath)) return null;
6986
- const parsed = JSON.parse(readFileSync(pkgPath, "utf8"));
6987
- if (expectedName && parsed.name !== expectedName) return null;
6988
- return typeof parsed.version === "string" ? parsed.version : null;
6989
- }
6990
- function resolveInstalledPackageVersion(packageName) {
6991
- const candidates = [];
6992
- try {
6993
- candidates.push(path.dirname(require.resolve(packageName)));
6994
- } catch {}
6995
- let dir = PACKAGE_DIR;
6996
- while (true) {
6997
- candidates.push(path.join(dir, "node_modules", packageName));
6998
- const parent = path.dirname(dir);
6999
- if (parent === dir) break;
7000
- dir = parent;
7001
- }
7002
- for (const start of candidates) {
7003
- let current = start;
7004
- while (true) {
7005
- const version = readPackageVersion(path.join(current, "package.json"), packageName);
7006
- if (version) return version;
7007
- const parent = path.dirname(current);
7008
- if (parent === current) break;
7009
- current = parent;
7010
- }
7011
- }
7012
- return null;
7013
- }
7014
- function resolvePiJudgeRecipeVersions() {
7015
- return {
7016
- pi: resolveInstalledPackageVersion(PI_PACKAGE_NAME),
7017
- piExtension: readPackageVersion(path.join(PACKAGE_DIR, "package.json"), SELF_PACKAGE_NAME),
7018
- sdk: resolveInstalledPackageVersion(SDK_PACKAGE_NAME)
7019
- };
7020
- }
7021
- function buildPiJudgeRecipeManifest(inputs) {
7022
- return {
7023
- kind: "pi-judge-recipe/v1",
7024
- versions: {
7025
- ...resolvePiJudgeRecipeVersions(),
7026
- ...inputs.overrides
7027
- },
7028
- assets: {
7029
- promptAsset: inputs.promptAsset ?? null,
7030
- rubricAsset: inputs.rubricAsset ?? null,
7031
- skillSourcePath: inputs.skillSourcePath ?? null
7032
- },
7033
- hashes: {
7034
- judgePromptSha256: sha256Hex(inputs.judgePrompt),
7035
- rubricSha256: sha256Hex(inputs.rubric),
7036
- skillFragmentSha256: inputs.skillFragment ? sha256Hex(inputs.skillFragment) : null,
7037
- implementationSha256: inputs.implementationSource ? sha256Hex(inputs.implementationSource) : null
7038
- }
7039
- };
7040
- }
7041
- function computePiJudgeRecipeCid(inputs) {
7042
- const manifest = buildPiJudgeRecipeManifest(inputs);
7043
- const manifestBytes = Buffer.from(stableStringify(manifest), "utf8");
7044
- const digestBytes = createHash("sha256").update(manifestBytes).digest();
7045
- return {
7046
- cid: base32Lower(Uint8Array.from([
7047
- ...encodeVarint(CID_VERSION),
7048
- ...encodeVarint(RAW_CODEC),
7049
- ...encodeVarint(SHA2_256_CODE),
7050
- ...encodeVarint(digestBytes.length),
7051
- ...digestBytes
7052
- ])),
7053
- manifest
7054
- };
7055
- }
7056
- //#endregion
7057
7225
  //#region src/moltnet/render-phase6.ts
7058
7226
  function slugToTitle(value) {
7059
7227
  return value.split(/[:/_-]+/).filter(Boolean).map((part) => part[0]?.toUpperCase() + part.slice(1)).join(" ");
@@ -7171,316 +7339,582 @@ function renderPhase6Markdown(pack) {
7171
7339
  * These tools run on the host (not in the VM) via the MoltNet SDK,
7172
7340
  * so agent credentials never touch the VM filesystem.
7173
7341
  */
7342
+ /**
7343
+ * Baseline env keys forwarded to host-exec child processes.
7344
+ * Callers can extend this set at sandbox startup via `MoltNetToolsConfig.hostExecBaseEnv`.
7345
+ */
7346
+ var HOST_EXEC_DEFAULT_BASE_ENV = new Set([
7347
+ "PATH",
7348
+ "HOME",
7349
+ "LANG",
7350
+ "LC_ALL",
7351
+ "TMPDIR",
7352
+ "GIT_CONFIG_GLOBAL",
7353
+ "MOLTNET_CREDENTIALS_PATH",
7354
+ "GIT_AUTHOR_NAME",
7355
+ "GIT_AUTHOR_EMAIL",
7356
+ "GIT_COMMITTER_NAME",
7357
+ "GIT_COMMITTER_EMAIL",
7358
+ "SSH_AUTH_SOCK"
7359
+ ]);
7174
7360
  function ensureConnected(config) {
7175
7361
  const agent = config.getAgent();
7176
7362
  const diaryId = config.getDiaryId();
7177
7363
  if (!agent || !diaryId) throw new Error("MoltNet not connected");
7178
7364
  return {
7179
7365
  agent,
7180
- diaryId
7366
+ diaryId,
7367
+ teamId: config.getTeamId() ?? ""
7181
7368
  };
7182
7369
  }
7183
7370
  /**
7184
7371
  * Create all MoltNet tool definitions, ready to pass to `pi.registerTool()`.
7185
7372
  */
7186
7373
  function createMoltNetTools(config) {
7187
- return [
7188
- defineTool({
7189
- name: "moltnet_pack_get",
7190
- label: "Get MoltNet Pack",
7191
- description: "Get a context pack by ID. Optionally expand included entries.",
7192
- parameters: Type.Object({
7193
- packId: Type.String({ description: "Context pack ID" }),
7194
- expandEntries: Type.Optional(Type.Boolean({ description: "Include full expanded entries" }))
7195
- }),
7196
- async execute(_id, params) {
7197
- const { agent } = ensureConnected(config);
7198
- const pack = await agent.packs.get(params.packId, { expand: params.expandEntries ? "entries" : void 0 });
7199
- return {
7200
- content: [{
7201
- type: "text",
7202
- text: JSON.stringify(pack, null, 2)
7203
- }],
7204
- details: {}
7205
- };
7206
- }
7374
+ const getPack = defineTool({
7375
+ name: "moltnet_pack_get",
7376
+ label: "Get MoltNet Pack",
7377
+ description: "Get a context pack by ID. Optionally expand included entries.",
7378
+ parameters: Type.Object({
7379
+ packId: Type.String({ description: "Context pack ID" }),
7380
+ expandEntries: Type.Optional(Type.Boolean({ description: "Include full expanded entries" }))
7207
7381
  }),
7208
- defineTool({
7209
- name: "moltnet_pack_create",
7210
- label: "Create MoltNet Pack",
7211
- 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.",
7212
- parameters: Type.Object({
7213
- entries: Type.Array(Type.Object({
7214
- entryId: Type.String({ description: "Diary entry UUID" }),
7215
- rank: Type.Number({ description: "Rank (1..N, lower = more prominent)" })
7216
- }), { description: "Selected entries with their ranks" }),
7217
- params: Type.Optional(Type.Record(Type.String(), Type.Unknown(), { description: "Free-form recipe parameters (recipe name, prompt, selection rationale, etc.)" })),
7218
- tokenBudget: Type.Optional(Type.Number({ description: "Soft token budget recorded on the pack (optional)" })),
7219
- pinned: Type.Optional(Type.Boolean({ description: "Pin the pack against retention policy (default false)" }))
7220
- }),
7221
- async execute(_id, params) {
7222
- const { agent, diaryId } = ensureConnected(config);
7223
- const pack = await agent.packs.create(diaryId, {
7224
- packType: "custom",
7225
- params: params.params ?? {},
7226
- entries: params.entries,
7227
- tokenBudget: params.tokenBudget,
7228
- pinned: params.pinned ?? false
7229
- });
7230
- return {
7231
- content: [{
7232
- type: "text",
7233
- text: JSON.stringify(pack, null, 2)
7234
- }],
7235
- details: {}
7236
- };
7237
- }
7382
+ async execute(_id, params) {
7383
+ const { agent } = ensureConnected(config);
7384
+ const pack = await agent.packs.get(params.packId, { expand: params.expandEntries ? "entries" : void 0 });
7385
+ return {
7386
+ content: [{
7387
+ type: "text",
7388
+ text: JSON.stringify(pack, null, 2)
7389
+ }],
7390
+ details: {}
7391
+ };
7392
+ }
7393
+ });
7394
+ const createPack = defineTool({
7395
+ name: "moltnet_pack_create",
7396
+ label: "Create MoltNet Pack",
7397
+ 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.",
7398
+ parameters: Type.Object({
7399
+ entries: Type.Array(Type.Object({
7400
+ entryId: Type.String({ description: "Diary entry UUID" }),
7401
+ rank: Type.Number({ description: "Rank (1..N, lower = more prominent)" })
7402
+ }), { description: "Selected entries with their ranks" }),
7403
+ params: Type.Optional(Type.Record(Type.String(), Type.Unknown(), { description: "Free-form recipe parameters (recipe name, prompt, selection rationale, etc.)" })),
7404
+ tokenBudget: Type.Optional(Type.Number({ description: "Soft token budget recorded on the pack (optional)" })),
7405
+ pinned: Type.Optional(Type.Boolean({ description: "Pin the pack against retention policy (default false)" }))
7238
7406
  }),
7239
- defineTool({
7240
- name: "moltnet_pack_provenance",
7241
- label: "Get MoltNet Pack Provenance",
7242
- description: "Get the provenance graph for a context pack by ID or CID.",
7243
- parameters: Type.Object({
7244
- packId: Type.Optional(Type.String({ description: "Context pack ID" })),
7245
- packCid: Type.Optional(Type.String({ description: "Context pack CID" })),
7246
- depth: Type.Optional(Type.Number({ description: "Supersession ancestry depth to include (default 2)" }))
7247
- }),
7248
- async execute(_id, params) {
7249
- const { agent } = ensureConnected(config);
7250
- if (!params.packId && !params.packCid) throw new Error("Provide either packId or packCid");
7251
- if (params.packId && params.packCid) throw new Error("Provide only one of packId or packCid");
7252
- const graph = params.packId ? await agent.packs.getProvenance(params.packId, { depth: params.depth ?? 2 }) : await agent.packs.getProvenanceByCid(params.packCid, { depth: params.depth ?? 2 });
7253
- const payload = {
7254
- metadata: graph.metadata,
7255
- counts: {
7256
- nodes: graph.nodes.length,
7257
- edges: graph.edges.length
7258
- },
7259
- graph
7260
- };
7261
- return {
7262
- content: [{
7263
- type: "text",
7264
- text: JSON.stringify(payload, null, 2)
7265
- }],
7266
- details: {}
7267
- };
7268
- }
7407
+ async execute(_id, params) {
7408
+ const { agent, diaryId } = ensureConnected(config);
7409
+ const pack = await agent.packs.create(diaryId, {
7410
+ packType: "custom",
7411
+ params: params.params ?? {},
7412
+ entries: params.entries,
7413
+ tokenBudget: params.tokenBudget,
7414
+ pinned: params.pinned ?? false
7415
+ });
7416
+ return {
7417
+ content: [{
7418
+ type: "text",
7419
+ text: JSON.stringify(pack, null, 2)
7420
+ }],
7421
+ details: {}
7422
+ };
7423
+ }
7424
+ });
7425
+ const getPackProvenance = defineTool({
7426
+ name: "moltnet_pack_provenance",
7427
+ label: "Get MoltNet Pack Provenance",
7428
+ description: "Get the provenance graph for a context pack by ID or CID.",
7429
+ parameters: Type.Object({
7430
+ packId: Type.Optional(Type.String({ description: "Context pack ID" })),
7431
+ packCid: Type.Optional(Type.String({ description: "Context pack CID" })),
7432
+ depth: Type.Optional(Type.Number({ description: "Supersession ancestry depth to include (default 2)" }))
7269
7433
  }),
7270
- defineTool({
7271
- name: "moltnet_pack_render",
7272
- label: "Render MoltNet Pack",
7273
- description: "Fetch a pack with entries, transform it into docs, then preview or persist the rendered pack.",
7274
- parameters: Type.Object({
7275
- packId: Type.String({ description: "Context pack ID" }),
7276
- renderMethod: Type.Optional(Type.String({ description: "Render method label. Defaults to pi:pack-to-docs-v1" })),
7277
- markdown: Type.Optional(Type.String({ description: "Optional caller-authored markdown override" })),
7278
- preview: Type.Optional(Type.Boolean({ description: "Preview without persisting (default false)" })),
7279
- pinned: Type.Optional(Type.Boolean({ description: "Persist the rendered pack as pinned (default false)" }))
7280
- }),
7281
- async execute(_id, params) {
7282
- const { agent } = ensureConnected(config);
7283
- const renderMethod = params.renderMethod ?? "pi:pack-to-docs-v1";
7284
- let renderedMarkdown = params.markdown;
7285
- if (!renderedMarkdown && !renderMethod.startsWith("server:")) renderedMarkdown = renderPhase6Markdown(await agent.packs.get(params.packId, { expand: "entries" }));
7286
- const result = params.preview ?? false ? await agent.packs.previewRendered(params.packId, {
7287
- renderMethod,
7288
- renderedMarkdown
7289
- }) : await agent.packs.render(params.packId, {
7290
- renderMethod,
7291
- renderedMarkdown,
7292
- pinned: params.pinned
7293
- });
7294
- return {
7295
- content: [{
7296
- type: "text",
7297
- text: JSON.stringify(result, null, 2)
7298
- }],
7299
- details: {}
7300
- };
7301
- }
7434
+ async execute(_id, params) {
7435
+ const { agent } = ensureConnected(config);
7436
+ if (!params.packId && !params.packCid) throw new Error("Provide either packId or packCid");
7437
+ if (params.packId && params.packCid) throw new Error("Provide only one of packId or packCid");
7438
+ const graph = params.packId ? await agent.packs.getProvenance(params.packId, { depth: params.depth ?? 2 }) : await agent.packs.getProvenanceByCid(params.packCid, { depth: params.depth ?? 2 });
7439
+ const payload = {
7440
+ metadata: graph.metadata,
7441
+ counts: {
7442
+ nodes: graph.nodes.length,
7443
+ edges: graph.edges.length
7444
+ },
7445
+ graph
7446
+ };
7447
+ return {
7448
+ content: [{
7449
+ type: "text",
7450
+ text: JSON.stringify(payload, null, 2)
7451
+ }],
7452
+ details: {}
7453
+ };
7454
+ }
7455
+ });
7456
+ const renderPack = defineTool({
7457
+ name: "moltnet_pack_render",
7458
+ label: "Render MoltNet Pack",
7459
+ description: "Fetch a pack with entries, transform it into docs, then preview or persist the rendered pack.",
7460
+ parameters: Type.Object({
7461
+ packId: Type.String({ description: "Context pack ID" }),
7462
+ renderMethod: Type.Optional(Type.String({ description: "Render method label. Defaults to pi:pack-to-docs-v1" })),
7463
+ markdown: Type.Optional(Type.String({ description: "Optional caller-authored markdown override" })),
7464
+ preview: Type.Optional(Type.Boolean({ description: "Preview without persisting (default false)" })),
7465
+ pinned: Type.Optional(Type.Boolean({ description: "Persist the rendered pack as pinned (default false)" }))
7302
7466
  }),
7303
- defineTool({
7304
- name: "moltnet_rendered_pack_list",
7305
- label: "List MoltNet Rendered Packs",
7306
- description: "List rendered packs for the current MoltNet diary, optionally filtered by source pack or render method.",
7307
- parameters: Type.Object({
7308
- sourcePackId: Type.Optional(Type.String({ description: "Filter by source pack ID" })),
7309
- renderMethod: Type.Optional(Type.String({ description: "Filter by render method" })),
7310
- limit: Type.Optional(Type.Number({ description: "Max results (default 10)" })),
7311
- offset: Type.Optional(Type.Number({ description: "Offset for pagination (default 0)" }))
7312
- }),
7313
- async execute(_id, params) {
7314
- const { agent, diaryId } = ensureConnected(config);
7315
- const rendered = await agent.packs.listRendered(diaryId, {
7467
+ async execute(_id, params) {
7468
+ const { agent } = ensureConnected(config);
7469
+ const renderMethod = params.renderMethod ?? "pi:pack-to-docs-v1";
7470
+ let renderedMarkdown = params.markdown;
7471
+ if (!renderedMarkdown && !renderMethod.startsWith("server:")) renderedMarkdown = renderPhase6Markdown(await agent.packs.get(params.packId, { expand: "entries" }));
7472
+ const result = params.preview ?? false ? await agent.packs.previewRendered(params.packId, {
7473
+ renderMethod,
7474
+ renderedMarkdown
7475
+ }) : await agent.packs.render(params.packId, {
7476
+ renderMethod,
7477
+ renderedMarkdown,
7478
+ pinned: params.pinned
7479
+ });
7480
+ return {
7481
+ content: [{
7482
+ type: "text",
7483
+ text: JSON.stringify(result, null, 2)
7484
+ }],
7485
+ details: {}
7486
+ };
7487
+ }
7488
+ });
7489
+ const listRenderedPacks = defineTool({
7490
+ name: "moltnet_rendered_pack_list",
7491
+ label: "List MoltNet Rendered Packs",
7492
+ description: "List rendered packs for the current MoltNet diary, optionally filtered by source pack or render method.",
7493
+ parameters: Type.Object({
7494
+ sourcePackId: Type.Optional(Type.String({ description: "Filter by source pack ID" })),
7495
+ renderMethod: Type.Optional(Type.String({ description: "Filter by render method" })),
7496
+ limit: Type.Optional(Type.Number({ description: "Max results (default 10)" })),
7497
+ offset: Type.Optional(Type.Number({ description: "Offset for pagination (default 0)" }))
7498
+ }),
7499
+ async execute(_id, params) {
7500
+ const { agent, diaryId } = ensureConnected(config);
7501
+ const rendered = await agent.packs.listRendered(diaryId, {
7502
+ sourcePackId: params.sourcePackId,
7503
+ renderMethod: params.renderMethod,
7504
+ limit: params.limit ?? 10,
7505
+ offset: params.offset ?? 0
7506
+ });
7507
+ return {
7508
+ content: [{
7509
+ type: "text",
7510
+ text: JSON.stringify(rendered, null, 2)
7511
+ }],
7512
+ details: {}
7513
+ };
7514
+ }
7515
+ });
7516
+ const getRenderedPack = defineTool({
7517
+ name: "moltnet_rendered_pack_get",
7518
+ label: "Get MoltNet Rendered Pack",
7519
+ description: "Get a rendered pack by ID.",
7520
+ parameters: Type.Object({ renderedPackId: Type.String({ description: "Rendered pack ID" }) }),
7521
+ async execute(_id, params) {
7522
+ const { agent } = ensureConnected(config);
7523
+ const rendered = await agent.packs.getRendered(params.renderedPackId);
7524
+ return {
7525
+ content: [{
7526
+ type: "text",
7527
+ text: JSON.stringify(rendered, null, 2)
7528
+ }],
7529
+ details: {}
7530
+ };
7531
+ }
7532
+ });
7533
+ const createJudgePackTask = defineTool({
7534
+ name: "moltnet_judge_pack_task_create",
7535
+ label: "Create Judge Pack Task",
7536
+ description: "Create a judge_pack task for a rendered pack. Returns a taskId that moltnet_rendered_pack_judge can claim and execute. The rubric is required — pass the structured rubric JSON from @moltnet/tasks Rubric schema.",
7537
+ parameters: Type.Object({
7538
+ renderedPackId: Type.String({ description: "Rendered pack ID to judge" }),
7539
+ sourcePackId: Type.String({ description: "Source pack ID. Fetch it from the rendered pack if unknown." }),
7540
+ rubric: Type.Any({ description: "Structured rubric object (Rubric schema from @moltnet/tasks). Must have rubricId, version, criteria[]." }),
7541
+ diaryId: Type.Optional(Type.String({ description: "Diary ID to impose the task on. Defaults to the connected diary." }))
7542
+ }),
7543
+ async execute(_id, params) {
7544
+ const { agent, diaryId: connectedDiaryId, teamId: connectedTeamId } = ensureConnected(config);
7545
+ const task = await agent.tasks.create({
7546
+ taskType: "judge_pack",
7547
+ input: {
7548
+ renderedPackId: params.renderedPackId,
7316
7549
  sourcePackId: params.sourcePackId,
7317
- renderMethod: params.renderMethod,
7318
- limit: params.limit ?? 10,
7319
- offset: params.offset ?? 0
7550
+ rubric: params.rubric
7551
+ },
7552
+ diaryId: params.diaryId ?? connectedDiaryId,
7553
+ teamId: connectedTeamId
7554
+ });
7555
+ return {
7556
+ content: [{
7557
+ type: "text",
7558
+ text: JSON.stringify({
7559
+ taskId: task.id,
7560
+ task
7561
+ }, null, 2)
7562
+ }],
7563
+ details: {}
7564
+ };
7565
+ }
7566
+ });
7567
+ const judgeRenderedPack = defineTool({
7568
+ name: "moltnet_rendered_pack_judge",
7569
+ label: "Judge MoltNet Rendered Pack",
7570
+ description: "Claim a judge_pack task, run the fidelity judge locally, complete the task with structured scores, and set verifiedTaskId on the rendered pack. Create the task first with moltnet_judge_pack_task_create.",
7571
+ parameters: Type.Object({
7572
+ taskId: Type.String({ description: "judge_pack task ID from moltnet_judge_pack_task_create" }),
7573
+ rubricOverride: Type.Optional(Type.String({ description: "Freeform rubric string override for the LLM judge prompt. When omitted the task rubric preamble (or built-in default) is used." }))
7574
+ }),
7575
+ async execute(_id, params, _signal, _onUpdate, ctx) {
7576
+ const { agent } = ensureConnected(config);
7577
+ const model = ctx?.model;
7578
+ if (!model) throw new Error("No active model in pi session — cannot run the fidelity judge.");
7579
+ const claimed = await agent.tasks.claim(params.taskId);
7580
+ const input = claimed.task.input;
7581
+ const rendered = await agent.packs.getRendered(input.renderedPackId);
7582
+ if (!rendered.content?.trim()) throw new Error(`rendered pack ${input.renderedPackId} has empty content`);
7583
+ const sourcePack = await agent.packs.get(input.sourcePackId, { expand: "entries" });
7584
+ if (!sourcePack.entries || sourcePack.entries.length === 0) throw new Error(`source pack ${input.sourcePackId} has no entries`);
7585
+ const sourceEntriesMd = buildSourceEntriesMarkdown(sourcePack.entries.map((entry) => ({
7586
+ title: entry.entry.title,
7587
+ content: entry.entry.content
7588
+ })));
7589
+ const rubric = params.rubricOverride?.trim() || input.rubric?.preamble?.trim() || DEFAULT_RUBRIC;
7590
+ let scores;
7591
+ try {
7592
+ scores = await runFidelityJudge({
7593
+ model,
7594
+ sourceEntries: sourceEntriesMd,
7595
+ renderedContent: rendered.content,
7596
+ rubric
7320
7597
  });
7321
- return {
7322
- content: [{
7323
- type: "text",
7324
- text: JSON.stringify(rendered, null, 2)
7325
- }],
7326
- details: {}
7327
- };
7598
+ } catch (err) {
7599
+ await agent.tasks.fail(params.taskId, claimed.attempt.attemptN, { error: {
7600
+ code: "judge_failed",
7601
+ message: err.message ?? String(err)
7602
+ } }).catch(() => {});
7603
+ throw new Error(`judge failed: ${err.message ?? String(err)}`);
7328
7604
  }
7605
+ const modelId = model.provider && model.id ? `${model.provider}:${model.id}` : model.id ?? "pi:unknown";
7606
+ const output = {
7607
+ scores: [
7608
+ {
7609
+ criterionId: "coverage",
7610
+ score: scores.coverage
7611
+ },
7612
+ {
7613
+ criterionId: "grounding",
7614
+ score: scores.grounding
7615
+ },
7616
+ {
7617
+ criterionId: "faithfulness",
7618
+ score: scores.faithfulness
7619
+ }
7620
+ ],
7621
+ composite: scores.composite,
7622
+ verdict: scores.reasoning,
7623
+ judgeModel: modelId
7624
+ };
7625
+ const outputCid = await computeJsonCid(output);
7626
+ const completed = await agent.tasks.complete(params.taskId, claimed.attempt.attemptN, {
7627
+ output,
7628
+ outputCid,
7629
+ usage: {
7630
+ inputTokens: 0,
7631
+ outputTokens: 0
7632
+ }
7633
+ });
7634
+ await agent.packs.updateRendered(input.renderedPackId, { verifiedTaskId: params.taskId });
7635
+ return {
7636
+ content: [{
7637
+ type: "text",
7638
+ text: JSON.stringify({
7639
+ renderedPackId: input.renderedPackId,
7640
+ taskId: params.taskId,
7641
+ scores,
7642
+ task: completed
7643
+ }, null, 2)
7644
+ }],
7645
+ details: {}
7646
+ };
7647
+ }
7648
+ });
7649
+ const diaryTags = defineTool({
7650
+ name: "moltnet_diary_tags",
7651
+ label: "List MoltNet Diary Tags",
7652
+ 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.",
7653
+ parameters: Type.Object({
7654
+ prefix: Type.Optional(Type.String({ description: "Filter to tags starting with this prefix (e.g. \"scope:\")" })),
7655
+ minCount: Type.Optional(Type.Number({ description: "Exclude tags with fewer than this many entries" })),
7656
+ entryTypes: Type.Optional(Type.Array(Type.Union([
7657
+ Type.Literal("episodic"),
7658
+ Type.Literal("semantic"),
7659
+ Type.Literal("procedural"),
7660
+ Type.Literal("reflection"),
7661
+ Type.Literal("identity"),
7662
+ Type.Literal("soul")
7663
+ ]), { description: "Scope the tag count to these entry types" }))
7329
7664
  }),
7330
- defineTool({
7331
- name: "moltnet_rendered_pack_get",
7332
- label: "Get MoltNet Rendered Pack",
7333
- description: "Get a rendered pack by ID.",
7334
- parameters: Type.Object({ renderedPackId: Type.String({ description: "Rendered pack ID" }) }),
7335
- async execute(_id, params) {
7336
- const { agent } = ensureConnected(config);
7337
- const rendered = await agent.packs.getRendered(params.renderedPackId);
7338
- return {
7339
- content: [{
7340
- type: "text",
7341
- text: JSON.stringify(rendered, null, 2)
7342
- }],
7343
- details: {}
7344
- };
7345
- }
7665
+ async execute(_id, params) {
7666
+ const { agent, diaryId } = ensureConnected(config);
7667
+ const result = await agent.diaries.tags(diaryId, {
7668
+ prefix: params.prefix,
7669
+ minCount: params.minCount,
7670
+ entryTypes: params.entryTypes
7671
+ });
7672
+ return {
7673
+ content: [{
7674
+ type: "text",
7675
+ text: JSON.stringify(result, null, 2)
7676
+ }],
7677
+ details: {}
7678
+ };
7679
+ }
7680
+ });
7681
+ const listEntries = defineTool({
7682
+ name: "moltnet_list_entries",
7683
+ label: "List MoltNet Diary Entries",
7684
+ 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.",
7685
+ parameters: Type.Object({
7686
+ limit: Type.Optional(Type.Number({ description: "Max entries to return (default 10)" })),
7687
+ tag: Type.Optional(Type.String({ description: "Filter by tag (optional)" })),
7688
+ entryIds: Type.Optional(Type.Array(Type.String(), {
7689
+ description: "Batch-fetch specific entries by UUID (max 50). Overrides `limit` and `tag` for selection.",
7690
+ maxItems: 50
7691
+ }))
7346
7692
  }),
7347
- defineTool({
7348
- name: "moltnet_rendered_pack_verify",
7349
- label: "Verify MoltNet Rendered Pack",
7350
- description: "Create a verification workflow for a rendered pack and return the verification ID and nonce.",
7351
- parameters: Type.Object({
7352
- renderedPackId: Type.String({ description: "Rendered pack ID" }),
7353
- nonce: Type.Optional(Type.String({ description: "Caller-supplied idempotency nonce. Generated automatically if omitted." }))
7354
- }),
7355
- async execute(_id, params) {
7356
- const { agent } = ensureConnected(config);
7357
- const nonce = params.nonce ?? randomUUID();
7358
- const verification = await agent.packs.verifyRendered(params.renderedPackId, { nonce });
7359
- return {
7360
- content: [{
7361
- type: "text",
7362
- text: JSON.stringify({
7363
- ...verification,
7364
- nonce
7365
- }, null, 2)
7366
- }],
7367
- details: {}
7368
- };
7693
+ async execute(_id, params) {
7694
+ const { agent, diaryId } = ensureConnected(config);
7695
+ const query = {
7696
+ orderBy: "createdAt",
7697
+ order: "desc"
7698
+ };
7699
+ const batchMode = !!params.entryIds?.length;
7700
+ if (batchMode) query.ids = params.entryIds;
7701
+ else {
7702
+ query.limit = params.limit ?? 10;
7703
+ if (params.tag) query.tag = params.tag;
7369
7704
  }
7705
+ const entries = await agent.entries.list(diaryId, query);
7706
+ return {
7707
+ content: [{
7708
+ type: "text",
7709
+ text: JSON.stringify(entries.items?.map((e) => batchMode ? {
7710
+ id: e.id,
7711
+ title: e.title,
7712
+ entryType: e.entryType,
7713
+ tags: e.tags,
7714
+ importance: e.importance,
7715
+ contentHash: e.contentHash,
7716
+ contentSignature: e.contentSignature,
7717
+ signingNonce: e.signingNonce,
7718
+ createdAt: e.createdAt
7719
+ } : {
7720
+ id: e.id,
7721
+ title: e.title,
7722
+ tags: e.tags,
7723
+ importance: e.importance,
7724
+ createdAt: e.createdAt,
7725
+ contentPreview: typeof e.content === "string" ? e.content.slice(0, 200) : void 0
7726
+ }), null, 2)
7727
+ }],
7728
+ details: {}
7729
+ };
7730
+ }
7731
+ });
7732
+ const getEntry = defineTool({
7733
+ name: "moltnet_get_entry",
7734
+ label: "Get MoltNet Diary Entry",
7735
+ description: "Get the full content of a specific diary entry by ID.",
7736
+ parameters: Type.Object({ entryId: Type.String({ description: "The entry ID to fetch" }) }),
7737
+ async execute(_id, params) {
7738
+ const { agent } = ensureConnected(config);
7739
+ const entry = await agent.entries.get(params.entryId);
7740
+ return {
7741
+ content: [{
7742
+ type: "text",
7743
+ text: JSON.stringify({
7744
+ id: entry.id,
7745
+ title: entry.title,
7746
+ content: entry.content,
7747
+ tags: entry.tags,
7748
+ importance: entry.importance,
7749
+ createdAt: entry.createdAt
7750
+ }, null, 2)
7751
+ }],
7752
+ details: {}
7753
+ };
7754
+ }
7755
+ });
7756
+ const searchEntries = defineTool({
7757
+ name: "moltnet_search_entries",
7758
+ label: "Search MoltNet Diary Entries",
7759
+ description: "Search diary entries by semantic query. Uses vector similarity to find relevant entries.",
7760
+ parameters: Type.Object({
7761
+ query: Type.String({ description: "Natural language search query" }),
7762
+ limit: Type.Optional(Type.Number({ description: "Max results (default 5)" }))
7763
+ }),
7764
+ async execute(_id, params) {
7765
+ const { agent, diaryId } = ensureConnected(config);
7766
+ const results = await agent.entries.search({
7767
+ diaryId,
7768
+ query: params.query,
7769
+ limit: params.limit ?? 5
7770
+ });
7771
+ return {
7772
+ content: [{
7773
+ type: "text",
7774
+ text: JSON.stringify(results.results?.map((e) => ({
7775
+ id: e.id,
7776
+ title: e.title,
7777
+ tags: e.tags,
7778
+ importance: e.importance,
7779
+ contentPreview: typeof e.content === "string" ? e.content.slice(0, 200) : void 0
7780
+ })), null, 2)
7781
+ }],
7782
+ details: {}
7783
+ };
7784
+ }
7785
+ });
7786
+ const createEntry = defineTool({
7787
+ name: "moltnet_create_entry",
7788
+ label: "Create MoltNet Diary Entry",
7789
+ description: "Create a new diary entry to record decisions, findings, incidents, or reflections.",
7790
+ parameters: Type.Object({
7791
+ title: Type.String({ description: "Entry title (concise, descriptive)" }),
7792
+ content: Type.String({ description: "Entry content (markdown)" }),
7793
+ tags: Type.Optional(Type.Array(Type.String(), { description: "Tags for categorization" })),
7794
+ importance: Type.Optional(Type.Number({ description: "Importance 1-10 (default 5)" }))
7370
7795
  }),
7796
+ async execute(_id, params) {
7797
+ const { agent, diaryId } = ensureConnected(config);
7798
+ const entry = await agent.entries.create(diaryId, {
7799
+ title: params.title,
7800
+ content: params.content,
7801
+ tags: params.tags ?? [],
7802
+ importance: params.importance ?? 5
7803
+ });
7804
+ return {
7805
+ content: [{
7806
+ type: "text",
7807
+ text: JSON.stringify({
7808
+ id: entry.id,
7809
+ title: entry.title,
7810
+ createdAt: entry.createdAt
7811
+ }, null, 2)
7812
+ }],
7813
+ details: {}
7814
+ };
7815
+ }
7816
+ });
7817
+ const reviewSessionErrors = defineTool({
7818
+ name: "moltnet_review_session_errors",
7819
+ label: "Review Session Tool Errors",
7820
+ 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.",
7821
+ 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." })) }),
7822
+ async execute(_id, params) {
7823
+ const errors = config.getSessionErrors();
7824
+ const payload = {
7825
+ count: errors.length,
7826
+ errors: errors.map((e) => ({
7827
+ toolName: e.toolName,
7828
+ toolCallId: e.toolCallId,
7829
+ timestamp: new Date(e.timestamp).toISOString(),
7830
+ input: e.input,
7831
+ error: e.error
7832
+ }))
7833
+ };
7834
+ if (params.clear) config.clearSessionErrors();
7835
+ return {
7836
+ content: [{
7837
+ type: "text",
7838
+ text: JSON.stringify(payload, null, 2)
7839
+ }],
7840
+ details: {}
7841
+ };
7842
+ }
7843
+ });
7844
+ const HOST_EXEC_ALLOWED = new Set([
7845
+ "git",
7846
+ "gh",
7847
+ "moltnet"
7848
+ ]);
7849
+ const hostExecBaseEnv = config.hostExecBaseEnv ?? HOST_EXEC_DEFAULT_BASE_ENV;
7850
+ const HOST_EXEC_TIMEOUT_MS = 6e4;
7851
+ return [
7852
+ getPack,
7853
+ createPack,
7854
+ getPackProvenance,
7855
+ renderPack,
7856
+ listRenderedPacks,
7857
+ getRenderedPack,
7858
+ createJudgePackTask,
7859
+ judgeRenderedPack,
7860
+ diaryTags,
7861
+ listEntries,
7862
+ getEntry,
7863
+ searchEntries,
7864
+ createEntry,
7865
+ reviewSessionErrors,
7371
7866
  defineTool({
7372
- name: "moltnet_rendered_pack_judge",
7373
- label: "Judge MoltNet Rendered Pack",
7374
- 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.",
7867
+ name: "moltnet_host_exec",
7868
+ label: "Run command on host (escape hatch — requires user approval)",
7869
+ 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.",
7375
7870
  parameters: Type.Object({
7376
- renderedPackId: Type.String({ description: "Rendered pack ID" }),
7377
- 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." })),
7378
- rubric: Type.Optional(Type.String({ description: "Custom rubric override (local mode only). Defaults to the built-in rubric when omitted." }))
7871
+ executable: Type.String({ description: "Executable to run (git | gh | moltnet)" }),
7872
+ args: Type.Array(Type.String(), { description: "Arguments to pass to the executable" }),
7873
+ 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." }))
7379
7874
  }),
7380
7875
  async execute(_id, params, _signal, _onUpdate, ctx) {
7381
- const { agent } = ensureConnected(config);
7382
- const model = ctx?.model;
7383
- if (!model) throw new Error("No active model in pi session — cannot run the fidelity judge.");
7384
- let sourceEntriesMd;
7385
- let renderedContent;
7386
- let rubric;
7387
- if (params.nonce) {
7388
- if (params.rubric) throw new Error("`rubric` is only supported in local mode (omit `nonce`).");
7389
- const claim = await agent.packs.claimVerification(params.renderedPackId);
7390
- sourceEntriesMd = buildSourceEntriesMarkdown(claim.sourceEntries);
7391
- renderedContent = claim.renderedContent;
7392
- rubric = claim.rubric?.trim() ? claim.rubric : DEFAULT_RUBRIC;
7393
- } else {
7394
- const rendered = await agent.packs.getRendered(params.renderedPackId);
7395
- if (!rendered.content?.trim()) throw new Error(`rendered pack ${params.renderedPackId} has empty content`);
7396
- const sourcePack = await agent.packs.get(rendered.sourcePackId, { expand: "entries" });
7397
- if (!sourcePack.entries || sourcePack.entries.length === 0) throw new Error(`source pack ${rendered.sourcePackId} has no entries`);
7398
- sourceEntriesMd = buildSourceEntriesMarkdown(sourcePack.entries.map((entry) => ({
7399
- title: entry.entry.title,
7400
- content: entry.entry.content
7401
- })));
7402
- renderedContent = rendered.content;
7403
- rubric = params.rubric?.trim() ? params.rubric : DEFAULT_RUBRIC;
7876
+ 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.`);
7877
+ if (ctx?.ui) {
7878
+ const cmdDisplay = [params.executable, ...params.args].join(" ");
7879
+ 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}`);
7880
+ }
7881
+ const cwd = config.getHostCwd?.() ?? process.cwd();
7882
+ const baseEnv = {};
7883
+ for (const key of hostExecBaseEnv) {
7884
+ const val = process.env[key];
7885
+ if (val !== void 0) baseEnv[key] = val;
7404
7886
  }
7405
- let scores;
7887
+ const mergedEnv = {
7888
+ ...baseEnv,
7889
+ ...params.env ?? {}
7890
+ };
7891
+ let stdout;
7892
+ let stderr = "";
7406
7893
  try {
7407
- scores = await runFidelityJudge({
7408
- model,
7409
- sourceEntries: sourceEntriesMd,
7410
- renderedContent,
7411
- rubric
7894
+ stdout = execFileSync(params.executable, params.args, {
7895
+ encoding: "utf8",
7896
+ cwd,
7897
+ env: mergedEnv,
7898
+ stdio: [
7899
+ "pipe",
7900
+ "pipe",
7901
+ "pipe"
7902
+ ],
7903
+ timeout: HOST_EXEC_TIMEOUT_MS
7412
7904
  });
7413
7905
  } catch (err) {
7414
- throw new Error(`judge failed: ${err.message ?? String(err)}`);
7906
+ const e = err;
7907
+ stdout = e.stdout ?? "";
7908
+ stderr = e.stderr ?? e.message ?? String(err);
7415
7909
  }
7416
- if (!params.nonce) return {
7417
- content: [{
7418
- type: "text",
7419
- text: JSON.stringify({
7420
- mode: "local",
7421
- renderedPackId: params.renderedPackId,
7422
- scores
7423
- }, null, 2)
7424
- }],
7425
- details: {}
7426
- };
7427
- const recipe = computePiJudgeRecipeCid({
7428
- judgePrompt: JUDGE_SYSTEM_PROMPT,
7429
- rubric,
7430
- promptAsset: JUDGE_PROMPT_ASSET_PATH,
7431
- rubricAsset: RUBRIC_ASSET_PATH
7432
- });
7433
- const providerName = model.provider ?? "pi";
7434
- const modelId = model.id ?? "unknown";
7435
- const submit = await agent.packs.submitVerification(params.renderedPackId, {
7436
- nonce: params.nonce,
7437
- coverage: scores.coverage,
7438
- grounding: scores.grounding,
7439
- faithfulness: scores.faithfulness,
7440
- transcript: scores.reasoning,
7441
- judgeModel: modelId,
7442
- judgeProvider: providerName,
7443
- judgeBinaryCid: recipe.cid
7444
- });
7445
- return {
7446
- content: [{
7447
- type: "text",
7448
- text: JSON.stringify({
7449
- mode: "proctored",
7450
- renderedPackId: params.renderedPackId,
7451
- scores,
7452
- submission: submit,
7453
- judgeRecipeCid: recipe.cid,
7454
- judgeRecipeManifest: recipe.manifest
7455
- }, null, 2)
7456
- }],
7457
- details: {}
7910
+ const result = {
7911
+ host_exec: true,
7912
+ executable: params.executable,
7913
+ args: params.args,
7914
+ cwd,
7915
+ stdout: stdout.trimEnd(),
7916
+ stderr: stderr.trimEnd() || void 0
7458
7917
  };
7459
- }
7460
- }),
7461
- defineTool({
7462
- name: "moltnet_diary_tags",
7463
- label: "List MoltNet Diary Tags",
7464
- 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.",
7465
- parameters: Type.Object({
7466
- prefix: Type.Optional(Type.String({ description: "Filter to tags starting with this prefix (e.g. \"scope:\")" })),
7467
- minCount: Type.Optional(Type.Number({ description: "Exclude tags with fewer than this many entries" })),
7468
- entryTypes: Type.Optional(Type.Array(Type.Union([
7469
- Type.Literal("episodic"),
7470
- Type.Literal("semantic"),
7471
- Type.Literal("procedural"),
7472
- Type.Literal("reflection"),
7473
- Type.Literal("identity"),
7474
- Type.Literal("soul")
7475
- ]), { description: "Scope the tag count to these entry types" }))
7476
- }),
7477
- async execute(_id, params) {
7478
- const { agent, diaryId } = ensureConnected(config);
7479
- const result = await agent.diaries.tags(diaryId, {
7480
- prefix: params.prefix,
7481
- minCount: params.minCount,
7482
- entryTypes: params.entryTypes
7483
- });
7484
7918
  return {
7485
7919
  content: [{
7486
7920
  type: "text",
@@ -7489,169 +7923,6 @@ function createMoltNetTools(config) {
7489
7923
  details: {}
7490
7924
  };
7491
7925
  }
7492
- }),
7493
- defineTool({
7494
- name: "moltnet_list_entries",
7495
- label: "List MoltNet Diary Entries",
7496
- 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.",
7497
- parameters: Type.Object({
7498
- limit: Type.Optional(Type.Number({ description: "Max entries to return (default 10)" })),
7499
- tag: Type.Optional(Type.String({ description: "Filter by tag (optional)" })),
7500
- entryIds: Type.Optional(Type.Array(Type.String(), {
7501
- description: "Batch-fetch specific entries by UUID (max 50). Overrides `limit` and `tag` for selection.",
7502
- maxItems: 50
7503
- }))
7504
- }),
7505
- async execute(_id, params) {
7506
- const { agent, diaryId } = ensureConnected(config);
7507
- const query = {
7508
- orderBy: "createdAt",
7509
- order: "desc"
7510
- };
7511
- const batchMode = !!params.entryIds?.length;
7512
- if (batchMode) query.ids = params.entryIds;
7513
- else {
7514
- query.limit = params.limit ?? 10;
7515
- if (params.tag) query.tag = params.tag;
7516
- }
7517
- const entries = await agent.entries.list(diaryId, query);
7518
- return {
7519
- content: [{
7520
- type: "text",
7521
- text: JSON.stringify(entries.items?.map((e) => batchMode ? {
7522
- id: e.id,
7523
- title: e.title,
7524
- entryType: e.entryType,
7525
- tags: e.tags,
7526
- importance: e.importance,
7527
- contentHash: e.contentHash,
7528
- contentSignature: e.contentSignature,
7529
- signingNonce: e.signingNonce,
7530
- createdAt: e.createdAt
7531
- } : {
7532
- id: e.id,
7533
- title: e.title,
7534
- tags: e.tags,
7535
- importance: e.importance,
7536
- createdAt: e.createdAt,
7537
- contentPreview: typeof e.content === "string" ? e.content.slice(0, 200) : void 0
7538
- }), null, 2)
7539
- }],
7540
- details: {}
7541
- };
7542
- }
7543
- }),
7544
- defineTool({
7545
- name: "moltnet_get_entry",
7546
- label: "Get MoltNet Diary Entry",
7547
- description: "Get the full content of a specific diary entry by ID.",
7548
- parameters: Type.Object({ entryId: Type.String({ description: "The entry ID to fetch" }) }),
7549
- async execute(_id, params) {
7550
- const { agent } = ensureConnected(config);
7551
- const entry = await agent.entries.get(params.entryId);
7552
- return {
7553
- content: [{
7554
- type: "text",
7555
- text: JSON.stringify({
7556
- id: entry.id,
7557
- title: entry.title,
7558
- content: entry.content,
7559
- tags: entry.tags,
7560
- importance: entry.importance,
7561
- createdAt: entry.createdAt
7562
- }, null, 2)
7563
- }],
7564
- details: {}
7565
- };
7566
- }
7567
- }),
7568
- defineTool({
7569
- name: "moltnet_search_entries",
7570
- label: "Search MoltNet Diary Entries",
7571
- description: "Search diary entries by semantic query. Uses vector similarity to find relevant entries.",
7572
- parameters: Type.Object({
7573
- query: Type.String({ description: "Natural language search query" }),
7574
- limit: Type.Optional(Type.Number({ description: "Max results (default 5)" }))
7575
- }),
7576
- async execute(_id, params) {
7577
- const { agent, diaryId } = ensureConnected(config);
7578
- const results = await agent.entries.search({
7579
- diaryId,
7580
- query: params.query,
7581
- limit: params.limit ?? 5
7582
- });
7583
- return {
7584
- content: [{
7585
- type: "text",
7586
- text: JSON.stringify(results.results?.map((e) => ({
7587
- id: e.id,
7588
- title: e.title,
7589
- tags: e.tags,
7590
- importance: e.importance,
7591
- contentPreview: typeof e.content === "string" ? e.content.slice(0, 200) : void 0
7592
- })), null, 2)
7593
- }],
7594
- details: {}
7595
- };
7596
- }
7597
- }),
7598
- defineTool({
7599
- name: "moltnet_create_entry",
7600
- label: "Create MoltNet Diary Entry",
7601
- description: "Create a new diary entry to record decisions, findings, incidents, or reflections.",
7602
- parameters: Type.Object({
7603
- title: Type.String({ description: "Entry title (concise, descriptive)" }),
7604
- content: Type.String({ description: "Entry content (markdown)" }),
7605
- tags: Type.Optional(Type.Array(Type.String(), { description: "Tags for categorization" })),
7606
- importance: Type.Optional(Type.Number({ description: "Importance 1-10 (default 5)" }))
7607
- }),
7608
- async execute(_id, params) {
7609
- const { agent, diaryId } = ensureConnected(config);
7610
- const entry = await agent.entries.create(diaryId, {
7611
- title: params.title,
7612
- content: params.content,
7613
- tags: params.tags ?? [],
7614
- importance: params.importance ?? 5
7615
- });
7616
- return {
7617
- content: [{
7618
- type: "text",
7619
- text: JSON.stringify({
7620
- id: entry.id,
7621
- title: entry.title,
7622
- createdAt: entry.createdAt
7623
- }, null, 2)
7624
- }],
7625
- details: {}
7626
- };
7627
- }
7628
- }),
7629
- defineTool({
7630
- name: "moltnet_review_session_errors",
7631
- label: "Review Session Tool Errors",
7632
- 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.",
7633
- 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." })) }),
7634
- async execute(_id, params) {
7635
- const errors = config.getSessionErrors();
7636
- const payload = {
7637
- count: errors.length,
7638
- errors: errors.map((e) => ({
7639
- toolName: e.toolName,
7640
- toolCallId: e.toolCallId,
7641
- timestamp: new Date(e.timestamp).toISOString(),
7642
- input: e.input,
7643
- error: e.error
7644
- }))
7645
- };
7646
- if (params.clear) config.clearSessionErrors();
7647
- return {
7648
- content: [{
7649
- type: "text",
7650
- text: JSON.stringify(payload, null, 2)
7651
- }],
7652
- details: {}
7653
- };
7654
- }
7655
7926
  })
7656
7927
  ];
7657
7928
  }
@@ -7987,10 +8258,6 @@ function createGondolinBashOps(vm, localCwd) {
7987
8258
  }
7988
8259
  //#endregion
7989
8260
  //#region src/vm-manager.ts
7990
- /**
7991
- * VM lifecycle manager: resume checkpoint, inject credentials, configure
7992
- * egress, fix TLS, and provide clean shutdown.
7993
- */
7994
8261
  var GUEST_WORKSPACE$1 = "/workspace";
7995
8262
  /**
7996
8263
  * Resolve the main worktree root (where .moltnet/ lives — it's untracked,
@@ -8021,6 +8288,15 @@ function loadCredentials(agentDir) {
8021
8288
  const sshPrivateKey = existsSync(path.join(sshDir, "id_ed25519")) ? readFileSync(path.join(sshDir, "id_ed25519"), "utf8") : null;
8022
8289
  const sshPublicKey = existsSync(path.join(sshDir, "id_ed25519.pub")) ? readFileSync(path.join(sshDir, "id_ed25519.pub"), "utf8") : null;
8023
8290
  const allowedSigners = existsSync(path.join(sshDir, "allowed_signers")) ? readFileSync(path.join(sshDir, "allowed_signers"), "utf8") : null;
8291
+ let githubAppPem = null;
8292
+ let githubAppPemFilename = null;
8293
+ const pemPath = JSON.parse(moltnetJson).github?.private_key_path;
8294
+ 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
8295
+ `);
8296
+ else {
8297
+ githubAppPem = readFileSync(pemPath, "utf8");
8298
+ githubAppPemFilename = path.basename(pemPath);
8299
+ }
8024
8300
  return {
8025
8301
  moltnetJson,
8026
8302
  agentEnvRaw,
@@ -8029,7 +8305,9 @@ function loadCredentials(agentDir) {
8029
8305
  gitconfig,
8030
8306
  sshPrivateKey,
8031
8307
  sshPublicKey,
8032
- allowedSigners
8308
+ allowedSigners,
8309
+ githubAppPem,
8310
+ githubAppPemFilename
8033
8311
  };
8034
8312
  }
8035
8313
  /**
@@ -8076,81 +8354,250 @@ async function resumeVm(config) {
8076
8354
  apiHost,
8077
8355
  ...config.extraAllowedHosts ?? []
8078
8356
  ] });
8357
+ const vmAgentDir = `/home/agent/.moltnet/${config.agentName}`;
8079
8358
  const vmAgentEnv = {};
8080
8359
  for (const [k, v] of Object.entries(creds.agentEnv)) {
8081
8360
  if (v === void 0 || v === "") continue;
8082
- if (k === "GIT_CONFIG_GLOBAL") vmAgentEnv[k] = `/home/agent/.moltnet/${config.agentName}/gitconfig`;
8083
- else if (k.endsWith("_PRIVATE_KEY_PATH")) vmAgentEnv[k] = `/home/agent/.moltnet/${config.agentName}/${path.basename(v)}`;
8361
+ if (k === "GIT_CONFIG_GLOBAL") vmAgentEnv[k] = `${vmAgentDir}/gitconfig`;
8362
+ else if (k.endsWith("_PRIVATE_KEY_PATH")) vmAgentEnv[k] = `${vmAgentDir}/${path.basename(v)}`;
8084
8363
  else vmAgentEnv[k] = v;
8085
8364
  }
8086
- const vfsConfig = config.sandboxConfig?.vfs;
8087
- let workspaceProvider = new RealFSProvider(config.mountPath);
8088
- if (vfsConfig?.shadow?.length) {
8089
- const predicate = createShadowPathPredicate(vfsConfig.shadow);
8090
- workspaceProvider = new ShadowProvider(workspaceProvider, {
8091
- shouldShadow: predicate,
8092
- writeMode: vfsConfig.shadowMode ?? "tmpfs"
8093
- });
8365
+ vmAgentEnv.MOLTNET_CREDENTIALS_PATH = `${vmAgentDir}/moltnet.json`;
8366
+ const vfsConfig = config.sandboxConfig?.vfs;
8367
+ let workspaceProvider = new RealFSProvider(config.mountPath);
8368
+ if (vfsConfig?.shadow?.length) {
8369
+ const predicate = createShadowPathPredicate(vfsConfig.shadow);
8370
+ workspaceProvider = new ShadowProvider(workspaceProvider, {
8371
+ shouldShadow: predicate,
8372
+ writeMode: vfsConfig.shadowMode ?? "tmpfs"
8373
+ });
8374
+ }
8375
+ const envOverrides = config.sandboxConfig?.env ?? {};
8376
+ const vmEnv = {
8377
+ ...secretEnv,
8378
+ ...vmAgentEnv,
8379
+ PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/lib/go/bin",
8380
+ HOME: "/home/agent",
8381
+ NODE_NO_WARNINGS: "1",
8382
+ NODE_EXTRA_CA_CERTS: "/etc/ssl/certs/ca-certificates.crt",
8383
+ ...envOverrides
8384
+ };
8385
+ const resources = config.sandboxConfig?.resources;
8386
+ const vm = await VmCheckpoint.load(config.checkpointPath).resume({
8387
+ httpHooks,
8388
+ env: vmEnv,
8389
+ ...resources?.memory && { memory: resources.memory },
8390
+ ...resources?.cpus && { cpus: resources.cpus },
8391
+ vfs: { mounts: { [GUEST_WORKSPACE$1]: workspaceProvider } }
8392
+ });
8393
+ await vm.exec(`sh -c '
8394
+ cp /etc/gondolin/mitm/ca.crt /usr/local/share/ca-certificates/gondolin-mitm.crt
8395
+ update-ca-certificates 2>/dev/null
8396
+ cat /etc/gondolin/mitm/ca.crt >> /etc/ssl/certs/ca-certificates.crt
8397
+ '`);
8398
+ await vm.exec(`sh -c 'echo "nameserver 8.8.8.8
8399
+ nameserver 1.1.1.1" > /etc/resolv.conf'`);
8400
+ const vmSshDir = `${vmAgentDir}/ssh`;
8401
+ await vm.exec(`mkdir -p ${vmAgentDir}/ssh /home/agent/.pi/agent`);
8402
+ await vm.fs.writeFile("/home/agent/.pi/agent/auth.json", creds.piAuthJson, { mode: 384 });
8403
+ const vmMoltnetJson = rewriteMoltnetJsonPaths(creds.moltnetJson, vmAgentDir, vmSshDir, creds.githubAppPemFilename);
8404
+ await vm.fs.writeFile(`${vmAgentDir}/moltnet.json`, vmMoltnetJson, { mode: 384 });
8405
+ await vm.fs.writeFile(`${vmAgentDir}/env`, creds.agentEnvRaw, { mode: 384 });
8406
+ if (creds.gitconfig) {
8407
+ const vmSigningKey = `${vmSshDir}/id_ed25519`;
8408
+ let vmGitconfig = creds.gitconfig.replace(/signingKey\s*=\s*.+/g, `signingKey = ${vmSigningKey}`);
8409
+ vmGitconfig = ensureRelativeWorktreePaths(vmGitconfig);
8410
+ await vm.fs.writeFile(`${vmAgentDir}/gitconfig`, vmGitconfig, { mode: 420 });
8411
+ }
8412
+ if (creds.sshPrivateKey) await vm.fs.writeFile(`${vmSshDir}/id_ed25519`, creds.sshPrivateKey, { mode: 384 });
8413
+ if (creds.sshPublicKey) await vm.fs.writeFile(`${vmSshDir}/id_ed25519.pub`, creds.sshPublicKey, { mode: 420 });
8414
+ if (creds.allowedSigners) await vm.fs.writeFile(`${vmSshDir}/allowed_signers`, creds.allowedSigners, { mode: 420 });
8415
+ if (creds.githubAppPem && creds.githubAppPemFilename) await vm.fs.writeFile(`${vmAgentDir}/${creds.githubAppPemFilename}`, creds.githubAppPem, { mode: 384 });
8416
+ await vm.exec("chown -R agent:agent /home/agent/.pi /home/agent/.moltnet");
8417
+ return {
8418
+ vm,
8419
+ credentials: creds,
8420
+ mountPath: config.mountPath,
8421
+ guestWorkspace: GUEST_WORKSPACE$1,
8422
+ agentDir
8423
+ };
8424
+ }
8425
+ /**
8426
+ * Rewrite host-absolute paths inside moltnet.json to VM-local equivalents.
8427
+ *
8428
+ * Fields rewritten:
8429
+ * ssh.private_key_path → <vmSshDir>/<basename of original>
8430
+ * ssh.public_key_path → <vmSshDir>/<basename of original>
8431
+ * git.config_path → <vmAgentDir>/gitconfig
8432
+ * github.private_key_path → <vmAgentDir>/<pemFilename> (if present)
8433
+ *
8434
+ * All other fields are passed through unchanged.
8435
+ * Throws if moltnetJson is not valid JSON — callers must not inject a broken
8436
+ * moltnet.json into the guest.
8437
+ */
8438
+ function rewriteMoltnetJsonPaths(moltnetJson, vmAgentDir, vmSshDir, githubAppPemFilename) {
8439
+ const config = JSON.parse(moltnetJson);
8440
+ if (config.ssh && typeof config.ssh === "object") {
8441
+ const ssh = config.ssh;
8442
+ const origPrivate = typeof ssh.private_key_path === "string" ? ssh.private_key_path : null;
8443
+ const origPublic = typeof ssh.public_key_path === "string" ? ssh.public_key_path : null;
8444
+ config.ssh = {
8445
+ ...ssh,
8446
+ ...origPrivate !== null && { private_key_path: `${vmSshDir}/${path.basename(origPrivate)}` },
8447
+ ...origPublic !== null && { public_key_path: `${vmSshDir}/${path.basename(origPublic)}` }
8448
+ };
8449
+ }
8450
+ if (config.git && typeof config.git === "object") {
8451
+ const git = { ...config.git };
8452
+ git.config_path = `${vmAgentDir}/gitconfig`;
8453
+ config.git = git;
8454
+ }
8455
+ if (githubAppPemFilename && config.github && typeof config.github === "object") {
8456
+ const github = { ...config.github };
8457
+ github.private_key_path = `${vmAgentDir}/${githubAppPemFilename}`;
8458
+ config.github = github;
8459
+ }
8460
+ return JSON.stringify(config);
8461
+ }
8462
+ /**
8463
+ * Ensure `[worktree] useRelativePaths = true` is set in the given
8464
+ * gitconfig text. If the section exists, rewrite the key; otherwise
8465
+ * append a new section.
8466
+ */
8467
+ function ensureRelativeWorktreePaths(gitconfig) {
8468
+ const sectionRe = /^\[worktree\]\s*$/m;
8469
+ if (/^(\[worktree\][\s\S]*?^)\s*useRelativePaths\s*=\s*\S+\s*$/m.test(gitconfig)) return gitconfig.replace(/^(\s*useRelativePaths\s*=\s*)\S+\s*$/m, "$1true");
8470
+ if (sectionRe.test(gitconfig)) return gitconfig.replace(sectionRe, "[worktree]\n useRelativePaths = true");
8471
+ return `${gitconfig}${gitconfig.endsWith("\n") ? "" : "\n"}[worktree]\n\tuseRelativePaths = true\n`;
8472
+ }
8473
+ //#endregion
8474
+ //#region src/moltnet/judge-recipe-cid.ts
8475
+ var require = createRequire(import.meta.url);
8476
+ var SELF_PACKAGE_NAME = "@themoltnet/pi-extension";
8477
+ var PI_PACKAGE_NAME = "@mariozechner/pi-coding-agent";
8478
+ var SDK_PACKAGE_NAME = "@themoltnet/sdk";
8479
+ var CID_VERSION = 1;
8480
+ var RAW_CODEC = 85;
8481
+ var SHA2_256_CODE = 18;
8482
+ var BASE32_ALPHABET = "abcdefghijklmnopqrstuvwxyz234567";
8483
+ function findSelfPackageDir() {
8484
+ const start = path.dirname(fileURLToPath(import.meta.url));
8485
+ let dir = start;
8486
+ while (true) {
8487
+ const candidate = path.join(dir, "package.json");
8488
+ if (existsSync(candidate)) {
8489
+ if (JSON.parse(readFileSync(candidate, "utf8")).name === SELF_PACKAGE_NAME) return dir;
8490
+ }
8491
+ const parent = path.dirname(dir);
8492
+ if (parent === dir) return start;
8493
+ dir = parent;
8494
+ }
8495
+ }
8496
+ var PACKAGE_DIR = findSelfPackageDir();
8497
+ function sha256Hex(value) {
8498
+ return createHash("sha256").update(value, "utf8").digest("hex");
8499
+ }
8500
+ function encodeVarint(value) {
8501
+ const bytes = [];
8502
+ let current = value >>> 0;
8503
+ while (current >= 128) {
8504
+ bytes.push(current & 127 | 128);
8505
+ current >>>= 7;
8506
+ }
8507
+ bytes.push(current);
8508
+ return bytes;
8509
+ }
8510
+ function base32Lower(bytes) {
8511
+ let bits = 0;
8512
+ let value = 0;
8513
+ let output = "";
8514
+ for (const byte of bytes) {
8515
+ value = value << 8 | byte;
8516
+ bits += 8;
8517
+ while (bits >= 5) {
8518
+ output += BASE32_ALPHABET[value >>> bits - 5 & 31];
8519
+ bits -= 5;
8520
+ }
8521
+ }
8522
+ if (bits > 0) output += BASE32_ALPHABET[value << 5 - bits & 31];
8523
+ return `b${output}`;
8524
+ }
8525
+ function stableStringify(value) {
8526
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
8527
+ if (Array.isArray(value)) return `[${value.map((item) => stableStringify(item)).join(",")}]`;
8528
+ return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${stableStringify(item)}`).join(",")}}`;
8529
+ }
8530
+ function readPackageVersion(pkgPath, expectedName) {
8531
+ if (!existsSync(pkgPath)) return null;
8532
+ const parsed = JSON.parse(readFileSync(pkgPath, "utf8"));
8533
+ if (expectedName && parsed.name !== expectedName) return null;
8534
+ return typeof parsed.version === "string" ? parsed.version : null;
8535
+ }
8536
+ function resolveInstalledPackageVersion(packageName) {
8537
+ const candidates = [];
8538
+ try {
8539
+ candidates.push(path.dirname(require.resolve(packageName)));
8540
+ } catch {}
8541
+ let dir = PACKAGE_DIR;
8542
+ while (true) {
8543
+ candidates.push(path.join(dir, "node_modules", packageName));
8544
+ const parent = path.dirname(dir);
8545
+ if (parent === dir) break;
8546
+ dir = parent;
8547
+ }
8548
+ for (const start of candidates) {
8549
+ let current = start;
8550
+ while (true) {
8551
+ const version = readPackageVersion(path.join(current, "package.json"), packageName);
8552
+ if (version) return version;
8553
+ const parent = path.dirname(current);
8554
+ if (parent === current) break;
8555
+ current = parent;
8556
+ }
8094
8557
  }
8095
- const envOverrides = config.sandboxConfig?.env ?? {};
8096
- const vmEnv = {
8097
- ...secretEnv,
8098
- ...vmAgentEnv,
8099
- PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/lib/go/bin",
8100
- HOME: "/home/agent",
8101
- NODE_NO_WARNINGS: "1",
8102
- NODE_EXTRA_CA_CERTS: "/etc/ssl/certs/ca-certificates.crt",
8103
- ...envOverrides
8558
+ return null;
8559
+ }
8560
+ function resolvePiJudgeRecipeVersions() {
8561
+ return {
8562
+ pi: resolveInstalledPackageVersion(PI_PACKAGE_NAME),
8563
+ piExtension: readPackageVersion(path.join(PACKAGE_DIR, "package.json"), SELF_PACKAGE_NAME),
8564
+ sdk: resolveInstalledPackageVersion(SDK_PACKAGE_NAME)
8104
8565
  };
8105
- const resources = config.sandboxConfig?.resources;
8106
- const vm = await VmCheckpoint.load(config.checkpointPath).resume({
8107
- httpHooks,
8108
- env: vmEnv,
8109
- ...resources?.memory && { memory: resources.memory },
8110
- ...resources?.cpus && { cpus: resources.cpus },
8111
- vfs: { mounts: { [GUEST_WORKSPACE$1]: workspaceProvider } }
8112
- });
8113
- await vm.exec(`sh -c '
8114
- cp /etc/gondolin/mitm/ca.crt /usr/local/share/ca-certificates/gondolin-mitm.crt
8115
- update-ca-certificates 2>/dev/null
8116
- cat /etc/gondolin/mitm/ca.crt >> /etc/ssl/certs/ca-certificates.crt
8117
- '`);
8118
- await vm.exec(`sh -c 'echo "nameserver 8.8.8.8
8119
- nameserver 1.1.1.1" > /etc/resolv.conf'`);
8120
- const vmAgentDir = `/home/agent/.moltnet/${config.agentName}`;
8121
- const vmSshDir = `${vmAgentDir}/ssh`;
8122
- await vm.exec(`mkdir -p ${vmAgentDir}/ssh /home/agent/.pi/agent`);
8123
- await vm.fs.writeFile("/home/agent/.pi/agent/auth.json", creds.piAuthJson, { mode: 384 });
8124
- await vm.fs.writeFile(`${vmAgentDir}/moltnet.json`, creds.moltnetJson, { mode: 384 });
8125
- await vm.fs.writeFile(`${vmAgentDir}/env`, creds.agentEnvRaw, { mode: 384 });
8126
- if (creds.gitconfig) {
8127
- const vmSigningKey = `${vmSshDir}/id_ed25519`;
8128
- let vmGitconfig = creds.gitconfig.replace(/signingKey\s*=\s*.+/g, `signingKey = ${vmSigningKey}`);
8129
- vmGitconfig = ensureRelativeWorktreePaths(vmGitconfig);
8130
- await vm.fs.writeFile(`${vmAgentDir}/gitconfig`, vmGitconfig, { mode: 420 });
8131
- }
8132
- if (creds.sshPrivateKey) await vm.fs.writeFile(`${vmSshDir}/id_ed25519`, creds.sshPrivateKey, { mode: 384 });
8133
- if (creds.sshPublicKey) await vm.fs.writeFile(`${vmSshDir}/id_ed25519.pub`, creds.sshPublicKey, { mode: 420 });
8134
- if (creds.allowedSigners) await vm.fs.writeFile(`${vmSshDir}/allowed_signers`, creds.allowedSigners, { mode: 420 });
8135
- await vm.exec("chown -R agent:agent /home/agent/.pi /home/agent/.moltnet");
8566
+ }
8567
+ function buildPiJudgeRecipeManifest(inputs) {
8136
8568
  return {
8137
- vm,
8138
- credentials: creds,
8139
- mountPath: config.mountPath,
8140
- guestWorkspace: GUEST_WORKSPACE$1,
8141
- agentDir
8569
+ kind: "pi-judge-recipe/v1",
8570
+ versions: {
8571
+ ...resolvePiJudgeRecipeVersions(),
8572
+ ...inputs.overrides
8573
+ },
8574
+ assets: {
8575
+ promptAsset: inputs.promptAsset ?? null,
8576
+ rubricAsset: inputs.rubricAsset ?? null,
8577
+ skillSourcePath: inputs.skillSourcePath ?? null
8578
+ },
8579
+ hashes: {
8580
+ judgePromptSha256: sha256Hex(inputs.judgePrompt),
8581
+ rubricSha256: sha256Hex(inputs.rubric),
8582
+ skillFragmentSha256: inputs.skillFragment ? sha256Hex(inputs.skillFragment) : null,
8583
+ implementationSha256: inputs.implementationSource ? sha256Hex(inputs.implementationSource) : null
8584
+ }
8142
8585
  };
8143
8586
  }
8144
- /**
8145
- * Ensure `[worktree] useRelativePaths = true` is set in the given
8146
- * gitconfig text. If the section exists, rewrite the key; otherwise
8147
- * append a new section.
8148
- */
8149
- function ensureRelativeWorktreePaths(gitconfig) {
8150
- const sectionRe = /^\[worktree\]\s*$/m;
8151
- if (/^(\[worktree\][\s\S]*?^)\s*useRelativePaths\s*=\s*\S+\s*$/m.test(gitconfig)) return gitconfig.replace(/^(\s*useRelativePaths\s*=\s*)\S+\s*$/m, "$1true");
8152
- if (sectionRe.test(gitconfig)) return gitconfig.replace(sectionRe, "[worktree]\n useRelativePaths = true");
8153
- return `${gitconfig}${gitconfig.endsWith("\n") ? "" : "\n"}[worktree]\n\tuseRelativePaths = true\n`;
8587
+ function computePiJudgeRecipeCid(inputs) {
8588
+ const manifest = buildPiJudgeRecipeManifest(inputs);
8589
+ const manifestBytes = Buffer.from(stableStringify(manifest), "utf8");
8590
+ const digestBytes = createHash("sha256").update(manifestBytes).digest();
8591
+ return {
8592
+ cid: base32Lower(Uint8Array.from([
8593
+ ...encodeVarint(CID_VERSION),
8594
+ ...encodeVarint(RAW_CODEC),
8595
+ ...encodeVarint(SHA2_256_CODE),
8596
+ ...encodeVarint(digestBytes.length),
8597
+ ...digestBytes
8598
+ ])),
8599
+ manifest
8600
+ };
8154
8601
  }
8155
8602
  //#endregion
8156
8603
  //#region ../tasks/src/formats.ts
@@ -8179,9 +8626,9 @@ if (!FormatRegistry.Has("date-time")) FormatRegistry.Set("date-time", (v) => !Nu
8179
8626
  * own signed rows and CIDv1 lookup. The schema below is designed to
8180
8627
  * carry forward unchanged — only storage and addressing differ.
8181
8628
  *
8182
- * Until Phase 2 lands, `rubric_id` + `version` + `content_hash` are
8629
+ * Until Phase 2 lands, `rubricId` + `version` + `contentHash` are
8183
8630
  * informational fields the author fills in; no uniqueness is enforced.
8184
- * `content_hash` is optional in Phase 1 because the *task*'s input_cid
8631
+ * `contentHash` is optional in Phase 1 because the *task*'s input_cid
8185
8632
  * is the authoritative commitment.
8186
8633
  */
8187
8634
  /**
@@ -8217,12 +8664,12 @@ var RubricCriterion = Type$1.Object({
8217
8664
  * (stored row `body`); only the addressing mechanism differs.
8218
8665
  */
8219
8666
  var Rubric = Type$1.Object({
8220
- rubric_id: Type$1.String({ minLength: 1 }),
8667
+ rubricId: Type$1.String({ minLength: 1 }),
8221
8668
  version: Type$1.String({ minLength: 1 }),
8222
8669
  preamble: Type$1.Optional(Type$1.String()),
8223
8670
  criteria: Type$1.Array(RubricCriterion, { minItems: 1 }),
8224
8671
  scope: Type$1.Optional(Type$1.String()),
8225
- content_hash: Type$1.Optional(Type$1.String())
8672
+ contentHash: Type$1.Optional(Type$1.String())
8226
8673
  }, {
8227
8674
  $id: "Rubric",
8228
8675
  additionalProperties: false
@@ -8278,25 +8725,25 @@ var AssessBriefCriterion = Type$1.Object({
8278
8725
  additionalProperties: false
8279
8726
  });
8280
8727
  var AssessBriefInput = Type$1.Object({
8281
- target_task_id: Type$1.String({ format: "uuid" }),
8728
+ targetTaskId: Type$1.String({ format: "uuid" }),
8282
8729
  criteria: Type$1.Array(AssessBriefCriterion, { minItems: 1 }),
8283
- rubric_preamble: Type$1.Optional(Type$1.String())
8730
+ rubricPreamble: Type$1.Optional(Type$1.String())
8284
8731
  }, {
8285
8732
  $id: "AssessBriefInput",
8286
8733
  additionalProperties: false
8287
8734
  });
8288
8735
  /** One score line. */
8289
8736
  var AssessBriefScore = Type$1.Object({
8290
- criterion_id: Type$1.String({ minLength: 1 }),
8737
+ criterionId: Type$1.String({ minLength: 1 }),
8291
8738
  score: Type$1.Number({
8292
8739
  minimum: 0,
8293
8740
  maximum: 1
8294
8741
  }),
8295
8742
  rationale: Type$1.Optional(Type$1.String()),
8296
8743
  evidence: Type$1.Optional(Type$1.Object({
8297
- commits_verified: Type$1.Number(),
8298
- commits_total: Type$1.Number(),
8299
- signature_failures: Type$1.Array(Type$1.String())
8744
+ commitsVerified: Type$1.Number(),
8745
+ commitsTotal: Type$1.Number(),
8746
+ signatureFailures: Type$1.Array(Type$1.String())
8300
8747
  }, { additionalProperties: false }))
8301
8748
  }, {
8302
8749
  $id: "AssessBriefScore",
@@ -8309,7 +8756,7 @@ var AssessBriefOutput = Type$1.Object({
8309
8756
  maximum: 1
8310
8757
  }),
8311
8758
  verdict: Type$1.String({ minLength: 1 }),
8312
- judge_model: Type$1.Optional(Type$1.String())
8759
+ judgeModel: Type$1.Optional(Type$1.String())
8313
8760
  }, {
8314
8761
  $id: "AssessBriefOutput",
8315
8762
  additionalProperties: false
@@ -8340,15 +8787,15 @@ var EntryTypeFilter = Type$1.Union([
8340
8787
  Type$1.Literal("reflection")
8341
8788
  ]);
8342
8789
  var CuratePackInput = Type$1.Object({
8343
- diary_id: Type$1.String({ format: "uuid" }),
8344
- task_prompt: Type$1.String({ minLength: 1 }),
8345
- entry_types: Type$1.Optional(Type$1.Array(EntryTypeFilter, { minItems: 1 })),
8346
- tag_filters: Type$1.Optional(Type$1.Object({
8790
+ diaryId: Type$1.String({ format: "uuid" }),
8791
+ taskPrompt: Type$1.String({ minLength: 1 }),
8792
+ entryTypes: Type$1.Optional(Type$1.Array(EntryTypeFilter, { minItems: 1 })),
8793
+ tagFilters: Type$1.Optional(Type$1.Object({
8347
8794
  include: Type$1.Optional(Type$1.Array(Type$1.String())),
8348
8795
  exclude: Type$1.Optional(Type$1.Array(Type$1.String())),
8349
8796
  prefix: Type$1.Optional(Type$1.String())
8350
8797
  }, { additionalProperties: false })),
8351
- token_budget: Type$1.Optional(Type$1.Number({ minimum: 500 })),
8798
+ tokenBudget: Type$1.Optional(Type$1.Number({ minimum: 500 })),
8352
8799
  recipe: Type$1.Optional(Type$1.Union([Type$1.Literal("topic-focused-v1"), Type$1.Literal("scope-inventory-v1")]))
8353
8800
  }, {
8354
8801
  $id: "CuratePackInput",
@@ -8360,18 +8807,18 @@ var CuratePackInput = Type$1.Object({
8360
8807
  * is the receipt.
8361
8808
  */
8362
8809
  var CuratePackOutput = Type$1.Object({
8363
- pack_id: Type$1.String({ format: "uuid" }),
8364
- pack_cid: Type$1.String({ minLength: 1 }),
8810
+ packId: Type$1.String({ format: "uuid" }),
8811
+ packCid: Type$1.String({ minLength: 1 }),
8365
8812
  entries: Type$1.Array(Type$1.Object({
8366
- entry_id: Type$1.String({ format: "uuid" }),
8813
+ entryId: Type$1.String({ format: "uuid" }),
8367
8814
  rank: Type$1.Number({ minimum: 1 }),
8368
8815
  rationale: Type$1.String({ minLength: 1 })
8369
8816
  }, { additionalProperties: false }), { minItems: 1 }),
8370
- recipe_params: Type$1.Record(Type$1.String(), Type$1.Unknown()),
8817
+ recipeParams: Type$1.Record(Type$1.String(), Type$1.Unknown()),
8371
8818
  checkpoints: Type$1.Optional(Type$1.Array(Type$1.Object({
8372
8819
  phase: Type$1.String({ minLength: 1 }),
8373
- candidate_ids: Type$1.Array(Type$1.String({ format: "uuid" })),
8374
- dropped_ids: Type$1.Optional(Type$1.Array(Type$1.String({ format: "uuid" }))),
8820
+ candidateIds: Type$1.Array(Type$1.String({ format: "uuid" })),
8821
+ droppedIds: Type$1.Optional(Type$1.Array(Type$1.String({ format: "uuid" }))),
8375
8822
  notes: Type$1.String({ minLength: 1 })
8376
8823
  }, { additionalProperties: false }))),
8377
8824
  summary: Type$1.String({ minLength: 1 })
@@ -8392,9 +8839,9 @@ var FULFILL_BRIEF_TYPE = "fulfill_brief";
8392
8839
  var FulfillBriefInput = Type$1.Object({
8393
8840
  brief: Type$1.String({ minLength: 1 }),
8394
8841
  title: Type$1.Optional(Type$1.String()),
8395
- acceptance_criteria: Type$1.Optional(Type$1.Array(Type$1.String())),
8396
- seed_files: Type$1.Optional(Type$1.Array(Type$1.String())),
8397
- scope_hint: Type$1.Optional(Type$1.String())
8842
+ acceptanceCriteria: Type$1.Optional(Type$1.Array(Type$1.String())),
8843
+ seedFiles: Type$1.Optional(Type$1.Array(Type$1.String())),
8844
+ scopeHint: Type$1.Optional(Type$1.String())
8398
8845
  }, {
8399
8846
  $id: "FulfillBriefInput",
8400
8847
  additionalProperties: false
@@ -8408,10 +8855,10 @@ var FulfillBriefOutput = Type$1.Object({
8408
8855
  commits: Type$1.Array(Type$1.Object({
8409
8856
  sha: Type$1.String({ minLength: 7 }),
8410
8857
  message: Type$1.String(),
8411
- diary_entry_id: Type$1.Union([Type$1.String({ format: "uuid" }), Type$1.Null()])
8858
+ diaryEntryId: Type$1.Union([Type$1.String({ format: "uuid" }), Type$1.Null()])
8412
8859
  }, { additionalProperties: false })),
8413
- pull_request_url: Type$1.Union([Type$1.String(), Type$1.Null()]),
8414
- diary_entry_ids: Type$1.Array(Type$1.String({ format: "uuid" })),
8860
+ pullRequestUrl: Type$1.Union([Type$1.String(), Type$1.Null()]),
8861
+ diaryEntryIds: Type$1.Array(Type$1.String({ format: "uuid" })),
8415
8862
  summary: Type$1.String({ minLength: 1 })
8416
8863
  }, {
8417
8864
  $id: "FulfillBriefOutput",
@@ -8442,8 +8889,8 @@ var FulfillBriefOutput = Type$1.Object({
8442
8889
  */
8443
8890
  var JUDGE_PACK_TYPE = "judge_pack";
8444
8891
  var JudgePackInput = Type$1.Object({
8445
- rendered_pack_id: Type$1.String({ format: "uuid" }),
8446
- source_pack_id: Type$1.String({ format: "uuid" }),
8892
+ renderedPackId: Type$1.String({ format: "uuid" }),
8893
+ sourcePackId: Type$1.String({ format: "uuid" }),
8447
8894
  rubric: Rubric
8448
8895
  }, {
8449
8896
  $id: "JudgePackInput",
@@ -8451,7 +8898,7 @@ var JudgePackInput = Type$1.Object({
8451
8898
  });
8452
8899
  /** One scored criterion. Mirrors `AssessBriefScore`. */
8453
8900
  var JudgePackScore = Type$1.Object({
8454
- criterion_id: Type$1.String({ minLength: 1 }),
8901
+ criterionId: Type$1.String({ minLength: 1 }),
8455
8902
  score: Type$1.Number({
8456
8903
  minimum: 0,
8457
8904
  maximum: 1
@@ -8469,8 +8916,8 @@ var JudgePackOutput = Type$1.Object({
8469
8916
  maximum: 1
8470
8917
  }),
8471
8918
  verdict: Type$1.String({ minLength: 1 }),
8472
- judge_model: Type$1.Optional(Type$1.String()),
8473
- renderer_binary_cid: Type$1.Optional(Type$1.Union([Type$1.String(), Type$1.Null()]))
8919
+ judgeModel: Type$1.Optional(Type$1.String()),
8920
+ rendererBinaryCid: Type$1.Optional(Type$1.Union([Type$1.String(), Type$1.Null()]))
8474
8921
  }, {
8475
8922
  $id: "JudgePackOutput",
8476
8923
  additionalProperties: false
@@ -8494,7 +8941,7 @@ var JudgePackOutput = Type$1.Object({
8494
8941
  */
8495
8942
  var RENDER_PACK_TYPE = "render_pack";
8496
8943
  var RenderPackInput = Type$1.Object({
8497
- pack_id: Type$1.String({ format: "uuid" }),
8944
+ packId: Type$1.String({ format: "uuid" }),
8498
8945
  persist: Type$1.Optional(Type$1.Boolean()),
8499
8946
  pinned: Type$1.Optional(Type$1.Boolean())
8500
8947
  }, {
@@ -8502,11 +8949,11 @@ var RenderPackInput = Type$1.Object({
8502
8949
  additionalProperties: false
8503
8950
  });
8504
8951
  var RenderPackOutput = Type$1.Object({
8505
- rendered_pack_id: Type$1.Union([Type$1.String({ format: "uuid" }), Type$1.Null()]),
8506
- rendered_cid: Type$1.String({ minLength: 1 }),
8507
- render_method: Type$1.String({ minLength: 1 }),
8508
- byte_size: Type$1.Number({ minimum: 0 }),
8509
- entries_rendered: Type$1.Number({ minimum: 0 }),
8952
+ renderedPackId: Type$1.Union([Type$1.String({ format: "uuid" }), Type$1.Null()]),
8953
+ renderedCid: Type$1.String({ minLength: 1 }),
8954
+ renderMethod: Type$1.String({ minLength: 1 }),
8955
+ byteSize: Type$1.Number({ minimum: 0 }),
8956
+ entriesRendered: Type$1.Number({ minimum: 0 }),
8510
8957
  summary: Type$1.String({ minLength: 1 })
8511
8958
  }, {
8512
8959
  $id: "RenderPackOutput",
@@ -8578,6 +9025,30 @@ new Proxy({}, { get(_, prop) {
8578
9025
  return getTaskTypeRegistry().get(prop);
8579
9026
  } });
8580
9027
  //#endregion
9028
+ //#region ../tasks/src/validation.ts
9029
+ function getTaskTypeEntry(taskType) {
9030
+ const taskTypes = BUILT_IN_TASK_TYPES;
9031
+ if (!Object.prototype.hasOwnProperty.call(taskTypes, taskType)) return;
9032
+ return taskTypes[taskType];
9033
+ }
9034
+ function formatField(prefix, path) {
9035
+ return path ? `${prefix}${path}` : prefix;
9036
+ }
9037
+ function schemaErrors(prefix, schema, value) {
9038
+ return [...Value.Errors(schema, value)].map((error) => ({
9039
+ field: formatField(prefix, error.path),
9040
+ message: error.message
9041
+ }));
9042
+ }
9043
+ function validateTaskOutput(taskType, output) {
9044
+ const entry = getTaskTypeEntry(taskType);
9045
+ if (!entry) return [{
9046
+ field: "taskType",
9047
+ message: `Unknown task type: ${taskType}`
9048
+ }];
9049
+ return schemaErrors("output", entry.outputSchema, output);
9050
+ }
9051
+ //#endregion
8581
9052
  //#region ../tasks/src/wire.ts
8582
9053
  /**
8583
9054
  * Wire-format types for the MoltNet Task model.
@@ -8589,7 +9060,7 @@ new Proxy({}, { get(_, prop) {
8589
9060
  * - `TaskReporter` output records (PR 0)
8590
9061
  *
8591
9062
  * Invariant: every property on `Task` is type-neutral (applies to all
8592
- * `task_type`s). Type-specific payloads live inside `input` / `output`
9063
+ * `taskType`s). Type-specific payloads live inside `input` / `output`
8593
9064
  * JSONB, validated against schemas registered under `task_types`.
8594
9065
  *
8595
9066
  * Identity rule:
@@ -8632,8 +9103,8 @@ var IsoTimestamp = Type$1.String({ format: "date-time" });
8632
9103
  * Embedded in `tasks.references` JSONB array.
8633
9104
  */
8634
9105
  var TaskRef = Type$1.Object({
8635
- task_id: Type$1.Union([Uuid, Type$1.Null()]),
8636
- output_cid: Cid,
9106
+ taskId: Type$1.Union([Uuid, Type$1.Null()]),
9107
+ outputCid: Cid,
8637
9108
  role: Type$1.Union([
8638
9109
  Type$1.Literal("judged_work"),
8639
9110
  Type$1.Literal("reviewed_diff"),
@@ -8662,11 +9133,11 @@ var TaskRef = Type$1.Object({
8662
9133
  * `TaskOutput.usage` for convenience.
8663
9134
  */
8664
9135
  var TaskUsage = Type$1.Object({
8665
- input_tokens: Type$1.Integer({ minimum: 0 }),
8666
- output_tokens: Type$1.Integer({ minimum: 0 }),
8667
- cache_read_tokens: Type$1.Optional(Type$1.Integer({ minimum: 0 })),
8668
- cache_write_tokens: Type$1.Optional(Type$1.Integer({ minimum: 0 })),
8669
- tool_calls: Type$1.Optional(Type$1.Integer({ minimum: 0 })),
9136
+ inputTokens: Type$1.Integer({ minimum: 0 }),
9137
+ outputTokens: Type$1.Integer({ minimum: 0 }),
9138
+ cacheReadTokens: Type$1.Optional(Type$1.Integer({ minimum: 0 })),
9139
+ cacheWriteTokens: Type$1.Optional(Type$1.Integer({ minimum: 0 })),
9140
+ toolCalls: Type$1.Optional(Type$1.Integer({ minimum: 0 })),
8670
9141
  model: Type$1.Optional(Type$1.String()),
8671
9142
  provider: Type$1.Optional(Type$1.String())
8672
9143
  }, {
@@ -8686,62 +9157,65 @@ var TaskError = Type$1.Object({
8686
9157
  additionalProperties: false
8687
9158
  });
8688
9159
  Type$1.Object({
8689
- agent_id: Type$1.Union([Uuid, Type$1.Null()]),
8690
- human_id: Type$1.Union([Uuid, Type$1.Null()])
9160
+ agentId: Type$1.Union([Uuid, Type$1.Null()]),
9161
+ humanId: Type$1.Union([Uuid, Type$1.Null()])
8691
9162
  }, {
8692
9163
  $id: "ActorPair",
8693
9164
  additionalProperties: false
8694
9165
  });
8695
9166
  Type$1.Object({
8696
9167
  id: Uuid,
8697
- task_type: Type$1.String({ minLength: 1 }),
8698
- team_id: Uuid,
8699
- diary_id: Type$1.Union([Uuid, Type$1.Null()]),
8700
- output_kind: OutputKind,
9168
+ taskType: Type$1.String({ minLength: 1 }),
9169
+ teamId: Uuid,
9170
+ diaryId: Type$1.Union([Uuid, Type$1.Null()]),
9171
+ outputKind: OutputKind,
8701
9172
  input: Type$1.Record(Type$1.String(), Type$1.Unknown()),
8702
- input_schema_cid: Cid,
8703
- input_cid: Cid,
8704
- criteria_cid: Type$1.Union([Cid, Type$1.Null()]),
9173
+ inputSchemaCid: Cid,
9174
+ inputCid: Cid,
9175
+ criteriaCid: Type$1.Union([Cid, Type$1.Null()]),
8705
9176
  references: Type$1.Array(TaskRef),
8706
- correlation_id: Type$1.Union([Uuid, Type$1.Null()]),
8707
- imposed_by_agent_id: Type$1.Union([Uuid, Type$1.Null()]),
8708
- imposed_by_human_id: Type$1.Union([Uuid, Type$1.Null()]),
8709
- accepted_attempt_n: Type$1.Union([Type$1.Number(), Type$1.Null()]),
9177
+ correlationId: Type$1.Union([Uuid, Type$1.Null()]),
9178
+ imposedByAgentId: Type$1.Union([Uuid, Type$1.Null()]),
9179
+ imposedByHumanId: Type$1.Union([Uuid, Type$1.Null()]),
9180
+ acceptedAttemptN: Type$1.Union([Type$1.Number(), Type$1.Null()]),
8710
9181
  status: TaskStatus,
8711
- queued_at: IsoTimestamp,
8712
- completed_at: Type$1.Union([IsoTimestamp, Type$1.Null()]),
8713
- expires_at: Type$1.Union([IsoTimestamp, Type$1.Null()]),
8714
- cancelled_by_agent_id: Type$1.Union([Uuid, Type$1.Null()]),
8715
- cancelled_by_human_id: Type$1.Union([Uuid, Type$1.Null()]),
8716
- cancel_reason: Type$1.Union([Type$1.String(), Type$1.Null()]),
8717
- max_attempts: Type$1.Number({ minimum: 1 })
9182
+ queuedAt: IsoTimestamp,
9183
+ completedAt: Type$1.Union([IsoTimestamp, Type$1.Null()]),
9184
+ expiresAt: Type$1.Union([IsoTimestamp, Type$1.Null()]),
9185
+ cancelledByAgentId: Type$1.Union([Uuid, Type$1.Null()]),
9186
+ cancelledByHumanId: Type$1.Union([Uuid, Type$1.Null()]),
9187
+ cancelReason: Type$1.Union([Type$1.String(), Type$1.Null()]),
9188
+ maxAttempts: Type$1.Number({ minimum: 1 })
8718
9189
  }, {
8719
9190
  $id: "Task",
8720
9191
  additionalProperties: false
8721
9192
  });
8722
9193
  Type$1.Object({
8723
- task_id: Uuid,
8724
- attempt_n: Type$1.Number({ minimum: 1 }),
8725
- claimed_by_agent_id: Uuid,
8726
- runtime_id: Type$1.Union([Uuid, Type$1.Null()]),
8727
- claimed_at: IsoTimestamp,
8728
- started_at: Type$1.Union([IsoTimestamp, Type$1.Null()]),
8729
- completed_at: Type$1.Union([IsoTimestamp, Type$1.Null()]),
9194
+ taskId: Uuid,
9195
+ attemptN: Type$1.Number({ minimum: 1 }),
9196
+ claimedByAgentId: Uuid,
9197
+ runtimeId: Type$1.Union([Uuid, Type$1.Null()]),
9198
+ claimedAt: IsoTimestamp,
9199
+ startedAt: Type$1.Union([IsoTimestamp, Type$1.Null()]),
9200
+ completedAt: Type$1.Union([IsoTimestamp, Type$1.Null()]),
8730
9201
  status: TaskAttemptStatus,
8731
9202
  output: Type$1.Union([Type$1.Record(Type$1.String(), Type$1.Unknown()), Type$1.Null()]),
8732
- output_cid: Type$1.Union([Cid, Type$1.Null()]),
9203
+ outputCid: Type$1.Union([Cid, Type$1.Null()]),
8733
9204
  error: Type$1.Union([TaskError, Type$1.Null()]),
8734
9205
  usage: Type$1.Union([TaskUsage, Type$1.Null()]),
8735
- content_signature: Type$1.Union([Type$1.String(), Type$1.Null()]),
8736
- signed_at: Type$1.Union([IsoTimestamp, Type$1.Null()])
9206
+ contentSignature: Type$1.Union([Type$1.String(), Type$1.Null()]),
9207
+ signedAt: Type$1.Union([IsoTimestamp, Type$1.Null()])
8737
9208
  }, {
8738
9209
  $id: "TaskAttempt",
8739
9210
  additionalProperties: false
8740
9211
  });
8741
9212
  Type$1.Object({
8742
- task_id: Uuid,
8743
- attempt_n: Type$1.Number({ minimum: 1 }),
8744
- seq: Type$1.Number({ minimum: 0 }),
9213
+ taskId: Uuid,
9214
+ attemptN: Type$1.Number({ minimum: 1 }),
9215
+ seq: Type$1.Number({
9216
+ minimum: 0,
9217
+ 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."
9218
+ }),
8745
9219
  timestamp: IsoTimestamp,
8746
9220
  kind: TaskMessageKind,
8747
9221
  payload: Type$1.Record(Type$1.String(), Type$1.Unknown())
@@ -8750,34 +9224,34 @@ Type$1.Object({
8750
9224
  additionalProperties: false
8751
9225
  });
8752
9226
  Type$1.Object({
8753
- task_id: Uuid,
8754
- attempt_n: Type$1.Number({ minimum: 1 }),
9227
+ taskId: Uuid,
9228
+ attemptN: Type$1.Number({ minimum: 1 }),
8755
9229
  status: Type$1.Union([
8756
9230
  Type$1.Literal("completed"),
8757
9231
  Type$1.Literal("failed"),
8758
9232
  Type$1.Literal("cancelled")
8759
9233
  ]),
8760
9234
  output: Type$1.Union([Type$1.Record(Type$1.String(), Type$1.Unknown()), Type$1.Null()]),
8761
- output_cid: Type$1.Union([Cid, Type$1.Null()]),
9235
+ outputCid: Type$1.Union([Cid, Type$1.Null()]),
8762
9236
  usage: TaskUsage,
8763
- duration_ms: Type$1.Number({ minimum: 0 }),
9237
+ durationMs: Type$1.Number({ minimum: 0 }),
8764
9238
  error: Type$1.Optional(TaskError),
8765
- content_signature: Type$1.Optional(Type$1.String())
9239
+ contentSignature: Type$1.Optional(Type$1.String())
8766
9240
  }, {
8767
9241
  $id: "TaskOutput",
8768
9242
  additionalProperties: false
8769
9243
  });
8770
9244
  Type$1.Object({
8771
- runtime_id: Uuid,
8772
- agent_id: Uuid,
9245
+ runtimeId: Uuid,
9246
+ agentId: Uuid,
8773
9247
  timestamp: IsoTimestamp,
8774
9248
  status: Type$1.Union([
8775
9249
  Type$1.Literal("idle"),
8776
9250
  Type$1.Literal("busy"),
8777
9251
  Type$1.Literal("draining")
8778
9252
  ]),
8779
- active_task_ids: Type$1.Array(Uuid),
8780
- supported_task_types: Type$1.Array(Type$1.String())
9253
+ activeTaskIds: Type$1.Array(Uuid),
9254
+ supportedTaskTypes: Type$1.Array(Type$1.String())
8781
9255
  }, {
8782
9256
  $id: "RuntimeHeartbeat",
8783
9257
  additionalProperties: false
@@ -8798,10 +9272,10 @@ function buildAssessBriefPrompt(input, ctx) {
8798
9272
  ...ctx.target.diaryEntryIds.map((id) => `- ${id}`),
8799
9273
  ""
8800
9274
  ].join("\n") : "";
8801
- const preambleSection = input.rubric_preamble ? [
9275
+ const preambleSection = input.rubricPreamble ? [
8802
9276
  "### Rubric preamble",
8803
9277
  "",
8804
- input.rubric_preamble,
9278
+ input.rubricPreamble,
8805
9279
  ""
8806
9280
  ].join("\n") : "";
8807
9281
  return [
@@ -8832,12 +9306,12 @@ function buildAssessBriefPrompt(input, ctx) {
8832
9306
  "",
8833
9307
  "- `llm_judged`: score 0..1 continuous. `rationale` REQUIRED (2–4 sentences).",
8834
9308
  "- `boolean`: score exactly 0 or 1. `rationale` optional.",
8835
- "- `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`.",
9309
+ "- `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`.",
8836
9310
  "",
8837
9311
  "### Final output",
8838
9312
  "",
8839
9313
  "Emit a JSON object matching `AssessBriefOutput`:",
8840
- " { \"scores\": [{criterion_id, score, rationale?, evidence?}], \"composite\", \"verdict\", \"judge_model\"? }",
9314
+ " { \"scores\": [{criterionId, score, rationale?, evidence?}], \"composite\", \"verdict\", \"judgeModel\"? }",
8841
9315
  "`composite` = Σ(weight_i × score_i) recomputed. The runtime will reject a mismatch.",
8842
9316
  "Write a signed diary entry (tags: \"judgment\", \"assess_brief\") capturing the rationale before emitting the JSON."
8843
9317
  ].filter(Boolean).join("\n");
@@ -8859,7 +9333,7 @@ function buildAssessBriefPrompt(input, ctx) {
8859
9333
  * N isolated `createAgentSession` children (one per tag cluster or
8860
9334
  * entry_type axis the curator picks after recon), each with a narrow
8861
9335
  * tool subset and a turn cap, and returns compressed summaries. Parent
8862
- * curator keeps a warm context and only sees {candidate_ids, notes}
9336
+ * curator keeps a warm context and only sees {candidateIds, notes}
8863
9337
  * per probe — mirrors the fan-out pattern pi-mono SDK example #13
8864
9338
  * (session runtime) + #05 (custom tools) makes possible. Until that
8865
9339
  * lands, the `checkpoints[]` output field is the fallback: curator
@@ -8867,7 +9341,7 @@ function buildAssessBriefPrompt(input, ctx) {
8867
9341
  * resume without replaying the tool history.
8868
9342
  */
8869
9343
  function buildCuratePackPrompt(input, ctx) {
8870
- const { diary_id: diaryId, task_prompt: taskPrompt, entry_types: entryTypes, tag_filters: tagFilters, token_budget: tokenBudget, recipe } = input;
9344
+ const { diaryId, taskPrompt, entryTypes, tagFilters, tokenBudget, recipe } = input;
8871
9345
  const effectiveEntryTypes = entryTypes ?? [
8872
9346
  "semantic",
8873
9347
  "episodic",
@@ -8954,7 +9428,7 @@ function buildCuratePackPrompt(input, ctx) {
8954
9428
  "",
8955
9429
  "The tool returns a JSON payload whose top-level fields are `packId` and",
8956
9430
  "`packCid` (NOT `id`). Copy those exact UUID/CID strings verbatim into",
8957
- "`pack_id` and `pack_cid` in your final output — do not substitute an",
9431
+ "`packId` and `packCid` in your final output — do not substitute an",
8958
9432
  "entry id, do not reformat, do not fabricate a UUID.",
8959
9433
  "",
8960
9434
  "## Hard constraints",
@@ -8971,14 +9445,14 @@ function buildCuratePackPrompt(input, ctx) {
8971
9445
  "Write to stdout a JSON object matching `CuratePackOutput`:",
8972
9446
  "```",
8973
9447
  "{",
8974
- " \"pack_id\": \"<uuid>\",",
8975
- " \"pack_cid\": \"<cid>\",",
9448
+ " \"packId\": \"<uuid>\",",
9449
+ " \"packCid\": \"<cid>\",",
8976
9450
  " \"entries\": [",
8977
- " { \"entry_id\": \"<uuid>\", \"rank\": 1, \"rationale\": \"<why>\" }",
9451
+ " { \"entryId\": \"<uuid>\", \"rank\": 1, \"rationale\": \"<why>\" }",
8978
9452
  " ],",
8979
- " \"recipe_params\": { \"recipe\": \"...\", \"prompt\": \"...\", ... },",
9453
+ " \"recipeParams\": { \"recipe\": \"...\", \"prompt\": \"...\", ... },",
8980
9454
  " \"checkpoints\": [",
8981
- " { \"phase\": \"recon\", \"candidate_ids\": [...], \"dropped_ids\": [...], \"notes\": \"...\" }",
9455
+ " { \"phase\": \"recon\", \"candidateIds\": [...], \"droppedIds\": [...], \"notes\": \"...\" }",
8982
9456
  " ],",
8983
9457
  " \"summary\": \"<2-4 sentences: what you looked for, how you narrowed, what defines the final set>\"",
8984
9458
  "}",
@@ -8996,7 +9470,7 @@ function buildCuratePackPrompt(input, ctx) {
8996
9470
  * is told to inspect them itself.
8997
9471
  */
8998
9472
  function buildFulfillBriefPrompt(input, ctx) {
8999
- const { brief, title, acceptance_criteria: acceptanceCriteria, seed_files: seedFiles, scope_hint: scopeHint } = input;
9473
+ const { brief, title, acceptanceCriteria, seedFiles, scopeHint } = input;
9000
9474
  const criteriaSection = acceptanceCriteria?.length ? [
9001
9475
  "### Acceptance criteria",
9002
9476
  "",
@@ -9045,14 +9519,14 @@ function buildFulfillBriefPrompt(input, ctx) {
9045
9519
  "### Final output",
9046
9520
  "",
9047
9521
  "When done, write to stdout a JSON object with shape matching `FulfillBriefOutput`:",
9048
- " { \"branch\", \"commits\": [{sha, message, diary_entry_id}], \"pull_request_url\", \"diary_entry_ids\", \"summary\" }",
9522
+ " { \"branch\", \"commits\": [{sha, message, diaryEntryId}], \"pullRequestUrl\", \"diaryEntryIds\", \"summary\" }",
9049
9523
  "The runtime parses this as the structured task output. Failing to emit it is a failure."
9050
9524
  ].filter(Boolean).join("\n");
9051
9525
  }
9052
9526
  //#endregion
9053
9527
  //#region ../agent-runtime/src/prompts/judge-pack.ts
9054
9528
  function buildJudgePackPrompt(input, ctx) {
9055
- const { rendered_pack_id: renderedPackId, source_pack_id: sourcePackId, rubric } = input;
9529
+ const { renderedPackId, sourcePackId, rubric } = input;
9056
9530
  const criteriaList = rubric.criteria.map((c, i) => `${i + 1}. **${c.id}** (weight ${c.weight}, scoring: \`${c.scoring}\`) — ${c.description}`).join("\n");
9057
9531
  const preambleSection = rubric.preamble ? [
9058
9532
  "### Rubric preamble",
@@ -9075,7 +9549,7 @@ function buildJudgePackPrompt(input, ctx) {
9075
9549
  "",
9076
9550
  `- **Rendered pack**: \`${renderedPackId}\``,
9077
9551
  `- **Source pack**: \`${sourcePackId}\``,
9078
- `- **Rubric**: \`${rubric.rubric_id}\` v${rubric.version}`,
9552
+ `- **Rubric**: \`${rubric.rubricId}\` v${rubric.version}`,
9079
9553
  "",
9080
9554
  preambleSection,
9081
9555
  "## Workflow",
@@ -9114,12 +9588,12 @@ function buildJudgePackPrompt(input, ctx) {
9114
9588
  " non-empty `contentSignature`. If `required_signed_total` is 0,",
9115
9589
  " score = 1. Populate `evidence` with `{ entries_verified,",
9116
9590
  " entries_total, required_signed_total, required_signed_ok,",
9117
- " signature_failures: [entry_ids] }` where `signature_failures` lists",
9591
+ " signatureFailures: [entryIds] }` where `signatureFailures` lists",
9118
9592
  " ONLY the REQUIRED-SIGNED entries that lack a signature.",
9119
9593
  "- `deterministic_coverage_check`: for every source entry, check",
9120
- " whether its `entry_id` (or a stable reference like title + CID",
9594
+ " whether its `entryId` (or a stable reference like title + CID",
9121
9595
  " prefix) appears in the rendered `content`. Score 1 iff coverage is",
9122
- " complete; otherwise 0. Populate `evidence` with `{ covered, total, missing: [entry_ids] }`.",
9596
+ " complete; otherwise 0. Populate `evidence` with `{ covered, total, missing: [entryIds] }`.",
9123
9597
  "",
9124
9598
  "## Constraints",
9125
9599
  "",
@@ -9133,17 +9607,17 @@ function buildJudgePackPrompt(input, ctx) {
9133
9607
  "Write to stdout a JSON object matching `JudgePackOutput`:",
9134
9608
  "```",
9135
9609
  "{",
9136
- " \"scores\": [{\"criterion_id\": \"...\", \"score\": 0.0, \"rationale\": \"...\", \"evidence\": {...}}],",
9610
+ " \"scores\": [{\"criterionId\": \"...\", \"score\": 0.0, \"rationale\": \"...\", \"evidence\": {...}}],",
9137
9611
  " \"composite\": <sum-of-weighted-scores>,",
9138
9612
  " \"verdict\": \"<1-3 sentence overall>\",",
9139
- " \"judge_model\": \"<provider:model>\",",
9140
- " \"renderer_binary_cid\": \"<cid-string-only-if-available>\"",
9613
+ " \"judgeModel\": \"<provider:model>\",",
9614
+ " \"rendererBinaryCid\": \"<cid-string-only-if-available>\"",
9141
9615
  "}",
9142
9616
  "```",
9143
- "Omit `renderer_binary_cid` entirely when no binary CID is exposed by",
9617
+ "Omit `rendererBinaryCid` entirely when no binary CID is exposed by",
9144
9618
  "`moltnet_rendered_pack_get`. Do NOT emit `null` — the field is optional",
9145
9619
  "and absence is the correct representation when unavailable.",
9146
- `Write a signed diary entry (tags: \`judgment\`, \`judge_pack\`, \`rubric:${rubric.rubric_id}\`) capturing the rationale before`,
9620
+ `Write a signed diary entry (tags: \`judgment\`, \`judge_pack\`, \`rubric:${rubric.rubricId}\`) capturing the rationale before`,
9147
9621
  "emitting the JSON."
9148
9622
  ].filter((l) => l !== null).join("\n");
9149
9623
  }
@@ -9154,7 +9628,7 @@ function buildJudgePackPrompt(input, ctx) {
9154
9628
  * wraps `moltnet_pack_render` and emits the receipt.
9155
9629
  */
9156
9630
  function buildRenderPackPrompt(input, ctx) {
9157
- const { pack_id: packId, persist = true, pinned = false } = input;
9631
+ const { packId, persist = true, pinned = false } = input;
9158
9632
  return [
9159
9633
  "# Render Pack Agent",
9160
9634
  "",
@@ -9194,11 +9668,11 @@ function buildRenderPackPrompt(input, ctx) {
9194
9668
  "Write to stdout a JSON object matching `RenderPackOutput`:",
9195
9669
  "```",
9196
9670
  "{",
9197
- " \"rendered_pack_id\": \"<uuid-or-null>\",",
9198
- " \"rendered_cid\": \"<cid>\",",
9199
- " \"render_method\": \"<label>\",",
9200
- " \"byte_size\": <int>,",
9201
- " \"entries_rendered\": <int>,",
9671
+ " \"renderedPackId\": \"<uuid-or-null>\",",
9672
+ " \"renderedCid\": \"<cid>\",",
9673
+ " \"renderMethod\": \"<label>\",",
9674
+ " \"byteSize\": <int>,",
9675
+ " \"entriesRendered\": <int>,",
9202
9676
  " \"summary\": \"<1-3 sentence recap>\"",
9203
9677
  "}",
9204
9678
  "```",
@@ -9208,11 +9682,11 @@ function buildRenderPackPrompt(input, ctx) {
9208
9682
  //#endregion
9209
9683
  //#region ../agent-runtime/src/prompts/index.ts
9210
9684
  /**
9211
- * Resolve the correct prompt builder for `task.task_type` and invoke it.
9685
+ * Resolve the correct prompt builder for `task.taskType` and invoke it.
9212
9686
  * Throws if the type is unknown or the input fails TypeBox validation.
9213
9687
  */
9214
9688
  function buildPromptForTask(task, ctx) {
9215
- switch (task.task_type) {
9689
+ switch (task.taskType) {
9216
9690
  case FULFILL_BRIEF_TYPE:
9217
9691
  if (!Value.Check(FulfillBriefInput, task.input)) {
9218
9692
  const errors = [...Value.Errors(FulfillBriefInput, task.input)];
@@ -9262,8 +9736,100 @@ function buildPromptForTask(task, ctx) {
9262
9736
  diaryId: ctx.diaryId,
9263
9737
  taskId: ctx.taskId
9264
9738
  });
9265
- default: throw new Error(`No prompt builder registered for task_type="${task.task_type}"`);
9739
+ default: throw new Error(`No prompt builder registered for taskType="${task.taskType}"`);
9740
+ }
9741
+ }
9742
+ //#endregion
9743
+ //#region src/runtime/task-output.ts
9744
+ async function parseStructuredTaskOutput(assistantText, taskType) {
9745
+ const extracted = extractJsonObject(assistantText);
9746
+ if (!extracted) return {
9747
+ output: null,
9748
+ outputCid: null,
9749
+ error: {
9750
+ code: "output_missing",
9751
+ message: "Agent did not emit a parseable JSON object as its final message."
9752
+ }
9753
+ };
9754
+ const errors = validateTaskOutput(taskType, extracted);
9755
+ if (errors.length > 0) {
9756
+ const details = errors.slice(0, 3).map((error) => `${error.field}: ${error.message}`);
9757
+ const [firstError] = errors;
9758
+ return {
9759
+ output: null,
9760
+ outputCid: null,
9761
+ error: {
9762
+ code: firstError?.field === "taskType" ? "unknown_task_type" : "output_validation_failed",
9763
+ message: `Output failed schema validation: ${details.join("; ")}`
9764
+ }
9765
+ };
9766
+ }
9767
+ try {
9768
+ return {
9769
+ output: extracted,
9770
+ outputCid: await computeJsonCid(extracted),
9771
+ error: null
9772
+ };
9773
+ } catch (error) {
9774
+ return {
9775
+ output: null,
9776
+ outputCid: null,
9777
+ error: {
9778
+ code: "output_cid_compute_failed",
9779
+ message: `Validated output could not be canonicalized: ${error instanceof Error ? error.message : String(error)}`
9780
+ }
9781
+ };
9782
+ }
9783
+ }
9784
+ /**
9785
+ * Find the last balanced top-level JSON object in `text` and parse it.
9786
+ * Tolerates markdown fences and leading prose. Returns null if parsing fails.
9787
+ */
9788
+ function extractJsonObject(text) {
9789
+ if (!text) return null;
9790
+ const fenceMatch = /```(?:json)?\s*([\s\S]*?)```/gi;
9791
+ const candidates = [];
9792
+ for (const m of text.matchAll(fenceMatch)) candidates.push(m[1]);
9793
+ const scanForObject = (s) => {
9794
+ let depth = 0;
9795
+ let start = -1;
9796
+ let lastComplete = null;
9797
+ let inString = false;
9798
+ let escape = false;
9799
+ for (let i = 0; i < s.length; i++) {
9800
+ const ch = s[i];
9801
+ if (inString) {
9802
+ if (escape) escape = false;
9803
+ else if (ch === "\\") escape = true;
9804
+ else if (ch === "\"") inString = false;
9805
+ continue;
9806
+ }
9807
+ if (ch === "\"") {
9808
+ inString = true;
9809
+ continue;
9810
+ }
9811
+ if (ch === "{") {
9812
+ if (depth === 0) start = i;
9813
+ depth++;
9814
+ } else if (ch === "}") {
9815
+ depth--;
9816
+ if (depth === 0 && start !== -1) {
9817
+ lastComplete = s.slice(start, i + 1);
9818
+ start = -1;
9819
+ }
9820
+ }
9821
+ }
9822
+ return lastComplete;
9823
+ };
9824
+ candidates.push(text);
9825
+ for (let i = candidates.length - 1; i >= 0; i--) {
9826
+ const obj = scanForObject(candidates[i]);
9827
+ if (!obj) continue;
9828
+ try {
9829
+ return JSON.parse(obj);
9830
+ } catch {}
9266
9831
  }
9832
+ return null;
9267
9833
  }
9268
9834
  //#endregion
9269
9835
  //#region src/runtime/execute-pi-task.ts
@@ -9290,14 +9856,14 @@ function buildPromptForTask(task, ctx) {
9290
9856
  */
9291
9857
  function createPiTaskExecutor(opts) {
9292
9858
  let cachedCheckpoint = opts.checkpointPath ?? null;
9293
- return async (task, reporter) => {
9859
+ return async (claimedTask, reporter) => {
9294
9860
  if (!cachedCheckpoint) cachedCheckpoint = await ensureSnapshot({
9295
9861
  config: opts.sandboxConfig?.snapshot,
9296
9862
  onProgress: opts.onSnapshotProgress ?? ((m) => {
9297
9863
  process.stderr.write(`[snapshot] ${m}\n`);
9298
9864
  })
9299
9865
  });
9300
- return executePiTask(task, reporter, {
9866
+ return executePiTask(claimedTask, reporter, {
9301
9867
  ...opts,
9302
9868
  checkpointPath: cachedCheckpoint
9303
9869
  });
@@ -9309,8 +9875,9 @@ function createPiTaskExecutor(opts) {
9309
9875
  * a `TaskOutput` (failures surface as `status: 'failed'`); throws only on
9310
9876
  * unrecoverable setup errors.
9311
9877
  */
9312
- async function executePiTask(task, reporter, opts) {
9313
- const attemptN = opts.attemptN ?? 1;
9878
+ async function executePiTask(claimedTask, reporter, opts) {
9879
+ const task = claimedTask.task;
9880
+ const attemptN = claimedTask.attemptN;
9314
9881
  const startTime = Date.now();
9315
9882
  const mountPath = opts.mountPath ?? process.cwd();
9316
9883
  const checkpointPath = opts.checkpointPath ?? await ensureSnapshot({
@@ -9336,17 +9903,19 @@ async function executePiTask(task, reporter, opts) {
9336
9903
  extraAllowedHosts: opts.extraAllowedHosts,
9337
9904
  sandboxConfig: opts.sandboxConfig
9338
9905
  });
9339
- const diaryId = task.diary_id ?? "";
9906
+ const diaryId = task.diaryId ?? "";
9907
+ const taskTeamId = task.teamId ?? "";
9340
9908
  let reporterOpen = false;
9341
9909
  let session = null;
9342
- const makeFailedOutput = (code, message, usage = emptyUsage(opts.provider, opts.model)) => ({
9343
- task_id: task.id,
9344
- attempt_n: attemptN,
9910
+ const finalUsage = emptyUsage(opts.provider, opts.model);
9911
+ const makeFailedOutput = (code, message, usage = finalUsage) => ({
9912
+ taskId: task.id,
9913
+ attemptN,
9345
9914
  status: "failed",
9346
9915
  output: null,
9347
- output_cid: null,
9916
+ outputCid: null,
9348
9917
  usage,
9349
- duration_ms: Date.now() - startTime,
9918
+ durationMs: Date.now() - startTime,
9350
9919
  error: {
9351
9920
  code,
9352
9921
  message,
@@ -9367,8 +9936,8 @@ async function executePiTask(task, reporter, opts) {
9367
9936
  });
9368
9937
  await emit("info", {
9369
9938
  event: "execute_start",
9370
- task_type: task.task_type,
9371
- team_id: task.team_id,
9939
+ taskType: task.taskType,
9940
+ teamId: task.teamId,
9372
9941
  provider: opts.provider,
9373
9942
  model: opts.model
9374
9943
  });
@@ -9398,8 +9967,11 @@ async function executePiTask(task, reporter, opts) {
9398
9967
  const moltnetTools = createMoltNetTools({
9399
9968
  getAgent: () => moltnetAgent,
9400
9969
  getDiaryId: () => diaryId,
9970
+ getTeamId: () => taskTeamId,
9401
9971
  getSessionErrors: () => [],
9402
- clearSessionErrors: () => {}
9972
+ clearSessionErrors: () => {},
9973
+ getHostCwd: () => mountPath,
9974
+ hostExecBaseEnv: new Set([...HOST_EXEC_DEFAULT_BASE_ENV, ...Object.keys(managed.credentials.agentEnv)])
9403
9975
  });
9404
9976
  const piAuthDir = join(homedir(), ".pi", "agent");
9405
9977
  const modelHandle = getModel(opts.provider, opts.model);
@@ -9427,7 +9999,7 @@ async function executePiTask(task, reporter, opts) {
9427
9999
  let llmAbort = false;
9428
10000
  let assistantText = "";
9429
10001
  let reporterError = null;
9430
- const usage = emptyUsage(opts.provider, opts.model);
10002
+ const usage = finalUsage;
9431
10003
  const recordingPromise = [];
9432
10004
  const track = (p) => {
9433
10005
  recordingPromise.push(p.catch((err) => {
@@ -9457,12 +10029,12 @@ async function executePiTask(task, reporter, opts) {
9457
10029
  else if (event.type === "turn_end") {
9458
10030
  const msg = event.message;
9459
10031
  if (msg?.role === "assistant" && msg.usage) {
9460
- usage.input_tokens += Math.max(0, msg.usage.input ?? 0);
9461
- usage.output_tokens += Math.max(0, msg.usage.output ?? 0);
10032
+ usage.inputTokens += Math.max(0, msg.usage.input ?? 0);
10033
+ usage.outputTokens += Math.max(0, msg.usage.output ?? 0);
9462
10034
  const cr = Math.max(0, msg.usage.cacheRead ?? 0);
9463
10035
  const cw = Math.max(0, msg.usage.cacheWrite ?? 0);
9464
- if (cr) usage.cache_read_tokens = (usage.cache_read_tokens ?? 0) + cr;
9465
- if (cw) usage.cache_write_tokens = (usage.cache_write_tokens ?? 0) + cw;
10036
+ if (cr) usage.cacheReadTokens = (usage.cacheReadTokens ?? 0) + cr;
10037
+ if (cw) usage.cacheWriteTokens = (usage.cacheWriteTokens ?? 0) + cw;
9466
10038
  }
9467
10039
  track(emit("turn_end", { stop_reason: msg?.stopReason ?? "end_turn" }));
9468
10040
  llmAbort = msg?.stopReason === "error";
@@ -9484,28 +10056,13 @@ async function executePiTask(task, reporter, opts) {
9484
10056
  }
9485
10057
  await Promise.all(recordingPromise);
9486
10058
  let parsedOutput = null;
10059
+ let parsedOutputCid = null;
9487
10060
  let parseError = null;
9488
10061
  if (!runError && !llmAbort) {
9489
- const extracted = extractJsonObject(assistantText);
9490
- if (!extracted) parseError = {
9491
- code: "output_missing",
9492
- message: "Agent did not emit a parseable JSON object as its final message."
9493
- };
9494
- else {
9495
- const entry = BUILT_IN_TASK_TYPES[task.task_type];
9496
- if (!entry) parseError = {
9497
- code: "unknown_task_type",
9498
- message: `No output schema registered for task_type=${task.task_type}`
9499
- };
9500
- else {
9501
- const check = TypeCompiler.Compile(entry.outputSchema);
9502
- if (check.Check(extracted)) parsedOutput = extracted;
9503
- else parseError = {
9504
- code: "output_validation_failed",
9505
- message: `Output failed schema validation: ${[...check.Errors(extracted)].slice(0, 3).map((e) => `${e.path}: ${e.message}`).join("; ")}`
9506
- };
9507
- }
9508
- }
10062
+ const parsed = await parseStructuredTaskOutput(assistantText, task.taskType);
10063
+ parsedOutput = parsed.output;
10064
+ parsedOutputCid = parsed.outputCid;
10065
+ parseError = parsed.error;
9509
10066
  if (parseError) await emit("error", {
9510
10067
  message: parseError.message,
9511
10068
  phase: "output_validation"
@@ -9515,13 +10072,13 @@ async function executePiTask(task, reporter, opts) {
9515
10072
  const errorCode = runError?.code ?? parseError?.code ?? reporterError?.code ?? (llmAbort ? "llm_api_error" : void 0);
9516
10073
  const errorMessage = runError?.message ?? parseError?.message ?? reporterError?.message ?? (llmAbort ? "LLM API error during turn" : void 0);
9517
10074
  return {
9518
- task_id: task.id,
9519
- attempt_n: attemptN,
10075
+ taskId: task.id,
10076
+ attemptN,
9520
10077
  status,
9521
10078
  output: parsedOutput,
9522
- output_cid: null,
10079
+ outputCid: parsedOutputCid,
9523
10080
  usage,
9524
- duration_ms: Date.now() - startTime,
10081
+ durationMs: Date.now() - startTime,
9525
10082
  ...errorCode && errorMessage ? { error: {
9526
10083
  code: errorCode,
9527
10084
  message: errorMessage,
@@ -9536,7 +10093,7 @@ async function executePiTask(task, reporter, opts) {
9536
10093
  } catch {}
9537
10094
  if (reporterOpen) {
9538
10095
  try {
9539
- await reporter.finalize(emptyUsage(opts.provider, opts.model));
10096
+ await reporter.finalize(finalUsage);
9540
10097
  } catch {}
9541
10098
  try {
9542
10099
  await reporter.close();
@@ -9547,8 +10104,8 @@ async function executePiTask(task, reporter, opts) {
9547
10104
  }
9548
10105
  function emptyUsage(provider, model) {
9549
10106
  return {
9550
- input_tokens: 0,
9551
- output_tokens: 0,
10107
+ inputTokens: 0,
10108
+ outputTokens: 0,
9552
10109
  provider,
9553
10110
  model
9554
10111
  };
@@ -9582,79 +10139,16 @@ function truncateForWire(value) {
9582
10139
  };
9583
10140
  }
9584
10141
  }
9585
- /**
9586
- * Find the last balanced top-level JSON object in `text` and parse it.
9587
- * Tolerates markdown fences and leading prose. Returns null if parsing fails.
9588
- */
9589
- function extractJsonObject(text) {
9590
- if (!text) return null;
9591
- const fenceMatch = /```(?:json)?\s*([\s\S]*?)```/gi;
9592
- const candidates = [];
9593
- for (const m of text.matchAll(fenceMatch)) candidates.push(m[1]);
9594
- const scanForObject = (s) => {
9595
- let depth = 0;
9596
- let start = -1;
9597
- let lastComplete = null;
9598
- let inString = false;
9599
- let escape = false;
9600
- for (let i = 0; i < s.length; i++) {
9601
- const ch = s[i];
9602
- if (inString) {
9603
- if (escape) escape = false;
9604
- else if (ch === "\\") escape = true;
9605
- else if (ch === "\"") inString = false;
9606
- continue;
9607
- }
9608
- if (ch === "\"") {
9609
- inString = true;
9610
- continue;
9611
- }
9612
- if (ch === "{") {
9613
- if (depth === 0) start = i;
9614
- depth++;
9615
- } else if (ch === "}") {
9616
- depth--;
9617
- if (depth === 0 && start !== -1) {
9618
- lastComplete = s.slice(start, i + 1);
9619
- start = -1;
9620
- }
9621
- }
9622
- }
9623
- return lastComplete;
9624
- };
9625
- candidates.push(text);
9626
- for (let i = candidates.length - 1; i >= 0; i--) {
9627
- const obj = scanForObject(candidates[i]);
9628
- if (!obj) continue;
9629
- try {
9630
- return JSON.parse(obj);
9631
- } catch {}
9632
- }
9633
- return null;
9634
- }
9635
10142
  //#endregion
9636
10143
  //#region src/index.ts
9637
10144
  /**
9638
10145
  * @themoltnet/pi-extension — MoltNet pi extension
9639
10146
  *
9640
- * Sandboxes tool execution in a Gondolin VM with:
9641
- * - Auto-built and cached VM snapshots
9642
- * - Credential injection (pi OAuth + MoltNet identity)
9643
- * - Egress policy (only LLM provider + MoltNet API)
9644
- * - Tool redirection (read/write/edit/bash → VM)
9645
- * - MoltNet custom tools (diary entries — run on host via SDK)
9646
- * - Optional git worktree per session
9647
- *
9648
- * Usage:
9649
- * pi -e @themoltnet/pi-extension
9650
- * pi -e @themoltnet/pi-extension --agent legreffier
9651
- * pi -e @themoltnet/pi-extension --worktree-branch feat/my-task
9652
- * pi -e @themoltnet/pi-extension --sandbox-config ./sandbox.json
10147
+ * Runs pi coding-agent sessions inside a Gondolin VM with the agent's
10148
+ * MoltNet identity fully available inside the sandbox.
9653
10149
  *
9654
- * Sandbox config resolution (first match):
9655
- * 1. --sandbox-config flag (explicit path to JSON)
9656
- * 2. sandbox.json in cwd (convention)
9657
- * 3. Base only (git, gh, moltnet CLI, agent user)
10150
+ * See README.md for credential injection flow, tool split, sandbox.json
10151
+ * reference, and headless/programmatic usage.
9658
10152
  */
9659
10153
  var GUEST_WORKSPACE = "/workspace";
9660
10154
  function moltnetExtension(pi) {
@@ -9692,6 +10186,8 @@ function moltnetExtension(pi) {
9692
10186
  let worktreePath = null;
9693
10187
  let moltnetAgent = null;
9694
10188
  let diaryId = null;
10189
+ let teamId = null;
10190
+ let hostExecBaseEnv = HOST_EXEC_DEFAULT_BASE_ENV;
9695
10191
  async function ensureVm(ctx) {
9696
10192
  if (vm) return vm;
9697
10193
  if (vmStarting) return vmStarting;
@@ -9745,6 +10241,8 @@ function moltnetExtension(pi) {
9745
10241
  activateAgentEnv(managed.credentials.agentEnv, mainRepo);
9746
10242
  moltnetAgent = await connect({ configDir: managed.agentDir });
9747
10243
  diaryId = managed.credentials.agentEnv.MOLTNET_DIARY_ID ?? null;
10244
+ teamId = managed.credentials.agentEnv.MOLTNET_TEAM_ID ?? null;
10245
+ hostExecBaseEnv = new Set([...HOST_EXEC_DEFAULT_BASE_ENV, ...Object.keys(managed.credentials.agentEnv)]);
9748
10246
  vm = managed.vm;
9749
10247
  const label = worktreePath ? `${mountPath} → ${GUEST_WORKSPACE}` : `${localCwd} → ${GUEST_WORKSPACE}`;
9750
10248
  ctx?.ui.setStatus("sandbox", ctx.ui.theme.fg("accent", `Sandbox: running (${label})`));
@@ -9765,6 +10263,7 @@ function moltnetExtension(pi) {
9765
10263
  vm = null;
9766
10264
  vmStarting = null;
9767
10265
  moltnetAgent = null;
10266
+ teamId = null;
9768
10267
  }
9769
10268
  });
9770
10269
  pi.on("before_agent_start", async (event, ctx) => {
@@ -9804,10 +10303,13 @@ function moltnetExtension(pi) {
9804
10303
  const moltnetTools = createMoltNetTools({
9805
10304
  getAgent: () => moltnetAgent,
9806
10305
  getDiaryId: () => diaryId,
10306
+ getTeamId: () => teamId,
9807
10307
  getSessionErrors: () => sessionErrors,
9808
10308
  clearSessionErrors: () => {
9809
10309
  sessionErrors.length = 0;
9810
- }
10310
+ },
10311
+ getHostCwd: () => worktreePath ?? localCwd,
10312
+ hostExecBaseEnv
9811
10313
  });
9812
10314
  for (const tool of moltnetTools) pi.registerTool(tool);
9813
10315
  const sessionStartTime = Date.now();
@@ -9913,4 +10415,4 @@ function moltnetExtension(pi) {
9913
10415
  registerMoltnetReflectCommand(pi, state);
9914
10416
  }
9915
10417
  //#endregion
9916
- export { activateAgentEnv, buildPiJudgeRecipeManifest, computePiJudgeRecipeCid, createGondolinBashOps, createGondolinEditOps, createGondolinReadOps, createGondolinWriteOps, createMoltNetTools, createPiTaskExecutor, moltnetExtension as default, ensureSnapshot, executePiTask, findMainWorktree, loadCredentials, resolvePiJudgeRecipeVersions, resumeVm, toGuestPath };
10418
+ 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 };