@themoltnet/pi-extension 0.33.0 → 0.33.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -308,6 +308,12 @@ export declare interface ExecutePiTaskOptions {
308
308
  sandboxConfig?: SandboxConfig;
309
309
  /** Host environment variable names to forward into the Pi VM. */
310
310
  forwardEnv?: string[];
311
+ /**
312
+ * Runtime profile context defaults. Merged with task.input.context at
313
+ * execution time because the selected runtime profile is known only after
314
+ * claim. Task entries override profile entries with the same slug.
315
+ */
316
+ runtimeProfileContext?: readonly ContextRef[];
311
317
  /**
312
318
  * Forwarded to `buildTaskUserPrompt` for per-type builders. Static
313
319
  * across tasks. Today no built-in builder needs per-task `extras` —
@@ -480,14 +486,14 @@ export declare interface InjectedTaskContext {
480
486
  }
481
487
 
482
488
  /**
483
- * Resolve a task's `input.context[]` and inject the side effects pi
489
+ * Resolve effective runtime context and inject the side effects Pi
484
490
  * needs. Safe to call with an empty array — returns an inert result.
485
491
  */
486
492
  export declare function injectTaskContext(args: InjectTaskContextArgs): Promise<InjectedTaskContext>;
487
493
 
488
494
  export declare interface InjectTaskContextArgs {
489
495
  /** Empty array (the default for any non-eval task) is a no-op. */
490
- context: TaskContext;
496
+ context: readonly ContextRef[];
491
497
  /** Guest filesystem handle. In production this is `managed.vm.fs`. */
492
498
  fs: VmFsForContext;
493
499
  /** Guest path where the active host workspace is mounted. */
@@ -922,15 +928,6 @@ declare const Task: Type.TObject<{
922
928
 
923
929
  declare type Task = Static<typeof Task>;
924
930
 
925
- /** Reusable input fragment for any task type. Soft cap at 5 items. */
926
- declare const TaskContext: Type.TArray<Type.TObject<{
927
- slug: Type.TString;
928
- binding: Type.TUnion<[Type.TLiteral<"skill">, Type.TLiteral<"context_inline">, Type.TLiteral<"prompt_prefix">, Type.TLiteral<"user_inline">]>;
929
- content: Type.TString;
930
- }>>;
931
-
932
- declare type TaskContext = Static<typeof TaskContext>;
933
-
934
931
  declare const TaskMessage: Type.TObject<{
935
932
  taskId: Type.TString;
936
933
  attemptN: Type.TNumber;
package/dist/index.js CHANGED
@@ -1965,7 +1965,7 @@ var findLatestRuntimeSlotForAttempt = (options) => (options.client ?? client).ge
1965
1965
  ...options
1966
1966
  });
1967
1967
  /**
1968
- * Queue asynchronous deletion of terminal tasks in bulk. By default, live, unauthorized, missing, and protected tasks are skipped. Set force: true with a reason to delete protected terminal tasks.
1968
+ * Queue asynchronous deletion of waiting, queued, and terminal tasks in bulk. By default, dispatched, running, unauthorized, missing, and protected tasks are skipped. Set force: true with a reason to delete protected terminal tasks.
1969
1969
  */
1970
1970
  var batchDeleteTasks = (options) => (options.client ?? client).delete({
1971
1971
  security: [
@@ -10030,7 +10030,7 @@ _Object_({
10030
10030
  * (server-side schema check). Self-assessment is a truthful self-rating,
10031
10031
  * NOT enforcement — `verification.passed=false` does not block /complete
10032
10032
  * and does not affect `acceptedAttemptN`. See
10033
- * `docs/understand/agent-runtime.md` for the full producer/judge flow.
10033
+ * `docs/use/tasks-and-runtime.md` for the full producer/judge flow.
10034
10034
  *
10035
10035
  * **Binding evaluation** (judgment tasks: `assess_brief`, `judge_pack`).
10036
10036
  * A separate task whose IS the application of `successCriteria` to
@@ -19937,8 +19937,19 @@ async function resolvePersistentSessionManager(args) {
19937
19937
  //#endregion
19938
19938
  //#region ../agent-runtime/src/context-bindings.ts
19939
19939
  var PROMPT_SEPARATOR = "\n\n---\n\n";
19940
+ var MAX_MERGED_RUNTIME_CONTEXT_ENTRIES = 10;
19940
19941
  /**
19941
- * Resolve `task.input.context[]` into delivered side-effects (skills
19942
+ * Merge runtime-profile context defaults with task-scoped context. Profile
19943
+ * entries are defaults; task entries with the same slug override them.
19944
+ */
19945
+ function mergeRuntimeProfileContext(profileContext, taskContext) {
19946
+ const taskSlugs = new Set(taskContext.map((ref) => ref.slug));
19947
+ const merged = [...profileContext.filter((ref) => !taskSlugs.has(ref.slug)), ...taskContext];
19948
+ if (merged.length > MAX_MERGED_RUNTIME_CONTEXT_ENTRIES) throw new Error(`merged runtime context has ${merged.length} entries; maximum is ${MAX_MERGED_RUNTIME_CONTEXT_ENTRIES}`);
19949
+ return merged;
19950
+ }
19951
+ /**
19952
+ * Resolve runtime context entries into delivered side-effects (skills
19942
19953
  * persisted via `deliver.skill`) and prompt fragments
19943
19954
  * (`systemPromptPrefix`, `userInlineSuffix`) the caller weaves into the
19944
19955
  * built prompt.
@@ -19958,9 +19969,10 @@ var PROMPT_SEPARATOR = "\n\n---\n\n";
19958
19969
  * - `user_inline` → content appended to `userInlineSuffix` in
19959
19970
  * declared order, same separator.
19960
19971
  *
19961
- * No fetching, no hashing — bytes are inlined in `ContextRef.content`,
19962
- * and the task's `inputCid` already pins the entire input. The proposer
19963
- * chose these bytes; the resolver just dispatches them.
19972
+ * No fetching, no hashing — bytes are inlined in `ContextRef.content`.
19973
+ * Task-scoped entries are pinned by the task's `inputCid`; profile-scoped
19974
+ * entries are pinned by the runtime profile revision/source the daemon
19975
+ * resolved. The resolver just dispatches already-selected bytes.
19964
19976
  *
19965
19977
  * The function is pure with respect to its arguments: file writes are
19966
19978
  * confined to the injected `deliver` callback, which makes the
@@ -20006,12 +20018,13 @@ function formatInlineContextBlock(slug, content) {
20006
20018
  "### Injected Task Context",
20007
20019
  "",
20008
20020
  `Context id: \`${slug}\``,
20009
- "The following raw context was supplied by the task creator. Treat it",
20010
- "as task-relevant background that may override generic coding instincts",
20011
- "when it contains repo- or workflow-specific constraints.",
20021
+ "The following raw context was selected for this task by its task input",
20022
+ "or runtime profile. Treat it as task-relevant background that may",
20023
+ "override generic coding instincts when it contains repo- or",
20024
+ "workflow-specific constraints.",
20012
20025
  "The same content may also be materialized by the runtime under",
20013
20026
  "`/moltnet-task-context/context` for tool-based inspection. Do not",
20014
- "create or rely on workspace mirror files for this task context.",
20027
+ "create or rely on workspace mirror files for this runtime context.",
20015
20028
  "",
20016
20029
  "<context>",
20017
20030
  content,
@@ -21334,11 +21347,11 @@ function buildRenderPackUserPrompt(input, ctx) {
21334
21347
  * `judge_eval_attempt` task(s) grade against their own hidden rubric.
21335
21348
  *
21336
21349
  * Context delivery is handled by `resolveTaskContext` (see
21337
- * libs/agent-runtime/src/context-bindings.ts) and runs BEFORE this
21338
- * prompt is rendered: `prompt_prefix` items are concatenated ahead of
21339
- * the body, `skill` items are persisted at the runtime's skill path,
21340
- * and `user_inline` items are appended to the first user message. This
21341
- * builder does NOT inline `input.context[]` itself.
21350
+ * libs/agent-runtime/src/context-bindings.ts) and is selected BEFORE this
21351
+ * prompt is rendered. Task-scoped context lives in `input.context`; runtime
21352
+ * profile defaults arrive as `ctx.effectiveRuntimeContext` after the runtime
21353
+ * merges them with task context. This builder only renders context
21354
+ * discipline; it does NOT inline context bytes itself.
21342
21355
  *
21343
21356
  * Prompt-shape notes (issue #1175, area 1):
21344
21357
  * - No `Correlation` section: the agent never acts on it. The id is
@@ -21356,12 +21369,13 @@ function buildRenderPackUserPrompt(input, ctx) {
21356
21369
  */
21357
21370
  function buildRunEvalUserPrompt(input, ctx) {
21358
21371
  const { scenario, variantLabel, successCriteria } = input;
21359
- const hasContext = input.context.length > 0;
21360
- const hasInlineContext = input.context.some((entry) => entry.binding === "context_inline");
21372
+ const effectiveRuntimeContext = ctx.effectiveRuntimeContext ?? input.context;
21373
+ const hasContext = effectiveRuntimeContext.length > 0;
21374
+ const hasInlineContext = effectiveRuntimeContext.some((entry) => entry.binding === "context_inline");
21361
21375
  const header = `# Run Eval Agent\n\nYou are running an evaluation scenario as variant \`${variantLabel}\`.\nTask id: \`${ctx.taskId}\``;
21362
21376
  const contextDiscipline = hasContext ? [
21363
21377
  "This task includes Injected Task Context supplied by the task",
21364
- "creator. You MUST inspect it BEFORE you write solution files or",
21378
+ "input or runtime profile. You MUST inspect it BEFORE you write solution files or",
21365
21379
  "draft your final answer — not after.",
21366
21380
  "",
21367
21381
  "Reconcile every constraint from that context **into the code path",
@@ -21534,7 +21548,8 @@ function buildTaskUserPrompt(task, ctx) {
21534
21548
  return buildRunEvalUserPrompt(task.input, {
21535
21549
  diaryId: ctx.diaryId,
21536
21550
  taskId: ctx.taskId,
21537
- correlationId: task.correlationId
21551
+ correlationId: task.correlationId,
21552
+ effectiveRuntimeContext: ctx.effectiveRuntimeContext
21538
21553
  });
21539
21554
  default: throw new Error(`No prompt builder registered for taskType="${task.taskType}"`);
21540
21555
  }
@@ -25007,119 +25022,6 @@ var require_multistream = /* @__PURE__ */ __commonJSMin(((exports, module) => {
25007
25022
  module.exports.pino = pino;
25008
25023
  })))();
25009
25024
  //#endregion
25010
- //#region src/runtime/inject-task-context.ts
25011
- /**
25012
- * Slice 1.5 of #943 — wire the agent-runtime resolver into the
25013
- * pi-extension execution path.
25014
- *
25015
- * `resolveTaskContext` is a pure dispatcher; this module provides the
25016
- * Gondolin-aware deliverer and the post-resolution shape the
25017
- * `execute-pi-task` caller needs to splice into pi's setup:
25018
- *
25019
- * - `systemPromptPrefix` → fed into `appendSystemPrompt` alongside
25020
- * the runtime instructor (it IS a system-prompt fragment).
25021
- * - `userInlineSuffix` → appended to the `buildTaskUserPrompt`
25022
- * output BEFORE `session.prompt(text)`.
25023
- * - `skills` → spliced into the `skillsOverride` callback's
25024
- * return value. pi includes them in `<available_skills>` in the
25025
- * system prompt; the agent fetches the body on demand via the
25026
- * Read tool.
25027
- *
25028
- * Task-context files are written into a memory-backed VM mount. pi only reads
25029
- * `<available_skills>` metadata (name, description, location), never the skill
25030
- * body, so we construct synthetic `Skill` objects pointing at the in-VM path
25031
- * without ever materialising the file on the host.
25032
- */
25033
- /**
25034
- * Where in the VM we write task-context bodies — the memory-backed mount
25035
- * declared in `vm-manager.ts`. See the comment on
25036
- * `GUEST_TASK_CONTEXT_MOUNT` there for the full rationale (ephemeral by
25037
- * intent + the worktree symlink interaction with Gondolin's sandbox-escape
25038
- * protection). The agent's Gondolin Read tool accepts paths under this mount
25039
- * via `toGuestPath` in `tool-operations.ts`.
25040
- */
25041
- var SKILL_ROOT_IN_VM = `${GUEST_TASK_CONTEXT_MOUNT}/skills`;
25042
- var INLINE_CONTEXT_ROOT_IN_VM = `${GUEST_TASK_CONTEXT_MOUNT}/context`;
25043
- /** Bounds borrowed from pi's skill validation; conservative caps so a
25044
- * malformed SKILL.md doesn't bloat the system prompt. */
25045
- var MAX_SKILL_NAME = 64;
25046
- var MAX_SKILL_DESCRIPTION = 1024;
25047
- /**
25048
- * Resolve a task's `input.context[]` and inject the side effects pi
25049
- * needs. Safe to call with an empty array — returns an inert result.
25050
- */
25051
- async function injectTaskContext(args) {
25052
- const skills = [];
25053
- args.guestWorkspace;
25054
- const resolved = await resolveTaskContext({
25055
- context: args.context,
25056
- deliver: {
25057
- skill: async ({ slug, content }) => {
25058
- const dir = `${SKILL_ROOT_IN_VM}/${slug}`;
25059
- const filePath = `${dir}/SKILL.md`;
25060
- await args.fs.mkdir(dir, { recursive: true });
25061
- await args.fs.writeFile(filePath, content, { mode: 420 });
25062
- skills.push(buildSyntheticSkill({
25063
- slug,
25064
- content,
25065
- filePath,
25066
- dir
25067
- }));
25068
- },
25069
- contextFile: async ({ suggestedFileName, content }) => {
25070
- await args.fs.mkdir(INLINE_CONTEXT_ROOT_IN_VM, { recursive: true });
25071
- const filePath = `${INLINE_CONTEXT_ROOT_IN_VM}/${suggestedFileName}`;
25072
- await args.fs.writeFile(filePath, content, { mode: 420 });
25073
- }
25074
- }
25075
- });
25076
- return {
25077
- injected: resolved.injected,
25078
- skills,
25079
- systemPromptPrefix: resolved.systemPromptPrefix,
25080
- userInlineSuffix: resolved.userInlineSuffix
25081
- };
25082
- }
25083
- /**
25084
- * Build a `Skill` object pi will faithfully render in
25085
- * `<available_skills>`. We extract `name` and `description` from the
25086
- * skill content's YAML frontmatter using pi's own `parseFrontmatter`
25087
- * helper (proper YAML, not a regex hack) and fall back to the slug +
25088
- * a generic description so a SKILL.md without frontmatter still
25089
- * renders something meaningful.
25090
- *
25091
- * Frontmatter parsing is best-effort: a malformed YAML block is
25092
- * optional metadata, not a reason to fail the task. We swallow parser
25093
- * errors and fall back to the slug-derived metadata; the skill body
25094
- * is unaffected.
25095
- *
25096
- * pi's `formatSkillsForPrompt` only reads `name`, `description`, and
25097
- * `filePath` — `sourceInfo`/`baseDir` exist on the type but never
25098
- * surface in the prompt, so a synthetic `SourceInfo` is enough.
25099
- */
25100
- function buildSyntheticSkill(args) {
25101
- let fm = {};
25102
- try {
25103
- fm = parseFrontmatter(args.content).frontmatter;
25104
- } catch {}
25105
- return {
25106
- name: clip(typeof fm.name === "string" && fm.name.trim().length > 0 ? fm.name.trim() : args.slug, MAX_SKILL_NAME),
25107
- description: clip(typeof fm.description === "string" && fm.description.trim().length > 0 ? fm.description.trim() : `Task-injected context skill (${args.slug})`, MAX_SKILL_DESCRIPTION),
25108
- filePath: args.filePath,
25109
- baseDir: args.dir,
25110
- sourceInfo: createSyntheticSourceInfo(args.filePath, {
25111
- source: "moltnet:task-context",
25112
- scope: "temporary",
25113
- origin: "top-level",
25114
- baseDir: args.dir
25115
- }),
25116
- disableModelInvocation: fm["disable-model-invocation"] === true
25117
- };
25118
- }
25119
- function clip(s, max) {
25120
- return s.length > max ? s.slice(0, max) : s;
25121
- }
25122
- //#endregion
25123
25025
  //#region src/runtime/resolve-prior-context.ts
25124
25026
  /**
25125
25027
  * Fetch the named attempt's output and project it into the prompt's
@@ -25306,6 +25208,113 @@ async function withTimeout(promise, timeoutMs, onTimeout) {
25306
25208
  }
25307
25209
  }
25308
25210
  //#endregion
25211
+ //#region src/runtime/runtime-context.ts
25212
+ /**
25213
+ * Pi-specific runtime context handling.
25214
+ *
25215
+ * `@themoltnet/agent-runtime` owns generic context semantics: merge profile
25216
+ * defaults with task context, resolve bindings, and produce prompt fragments.
25217
+ * This module owns the Pi/Gondolin boundary: validate effective context for an
25218
+ * attempt, write skill/context files into the VM, and build synthetic Pi Skill
25219
+ * metadata for injected skill bindings.
25220
+ */
25221
+ /**
25222
+ * Where in the VM we write runtime-context bodies — the memory-backed mount
25223
+ * declared in `vm-manager.ts`. See the comment on
25224
+ * `GUEST_TASK_CONTEXT_MOUNT` there for the full rationale (ephemeral by
25225
+ * intent + the worktree symlink interaction with Gondolin's sandbox-escape
25226
+ * protection). The agent's Gondolin Read tool accepts paths under this mount
25227
+ * via `toGuestPath` in `tool-operations.ts`.
25228
+ */
25229
+ var SKILL_ROOT_IN_VM = `${GUEST_TASK_CONTEXT_MOUNT}/skills`;
25230
+ var INLINE_CONTEXT_ROOT_IN_VM = `${GUEST_TASK_CONTEXT_MOUNT}/context`;
25231
+ /** Bounds borrowed from pi's skill validation; conservative caps so a
25232
+ * malformed SKILL.md doesn't bloat the system prompt. */
25233
+ var MAX_SKILL_NAME = 64;
25234
+ var MAX_SKILL_DESCRIPTION = 1024;
25235
+ function resolveEffectiveRuntimeContext(args) {
25236
+ const taskContext = args.rawTaskContext === void 0 ? [] : args.rawTaskContext;
25237
+ if (!Check(TaskContext, taskContext)) throw new Error(`task.input.context failed TaskContext validation: ${JSON.stringify([...Errors(TaskContext, taskContext)].slice(0, 3))}`);
25238
+ const profileContext = args.runtimeProfileContext ?? [];
25239
+ if (!Check(TaskContext, profileContext)) throw new Error(`runtime profile context failed TaskContext validation: ${JSON.stringify([...Errors(TaskContext, profileContext)].slice(0, 3))}`);
25240
+ return mergeRuntimeProfileContext(profileContext, taskContext);
25241
+ }
25242
+ /**
25243
+ * Resolve effective runtime context and inject the side effects Pi
25244
+ * needs. Safe to call with an empty array — returns an inert result.
25245
+ */
25246
+ async function injectRuntimeContext(args) {
25247
+ const skills = [];
25248
+ args.guestWorkspace;
25249
+ const resolved = await resolveTaskContext({
25250
+ context: args.context,
25251
+ deliver: {
25252
+ skill: async ({ slug, content }) => {
25253
+ const dir = `${SKILL_ROOT_IN_VM}/${slug}`;
25254
+ const filePath = `${dir}/SKILL.md`;
25255
+ await args.fs.mkdir(dir, { recursive: true });
25256
+ await args.fs.writeFile(filePath, content, { mode: 420 });
25257
+ skills.push(buildSyntheticSkill({
25258
+ slug,
25259
+ content,
25260
+ filePath,
25261
+ dir
25262
+ }));
25263
+ },
25264
+ contextFile: async ({ suggestedFileName, content }) => {
25265
+ await args.fs.mkdir(INLINE_CONTEXT_ROOT_IN_VM, { recursive: true });
25266
+ const filePath = `${INLINE_CONTEXT_ROOT_IN_VM}/${suggestedFileName}`;
25267
+ await args.fs.writeFile(filePath, content, { mode: 420 });
25268
+ }
25269
+ }
25270
+ });
25271
+ return {
25272
+ injected: resolved.injected,
25273
+ skills,
25274
+ systemPromptPrefix: resolved.systemPromptPrefix,
25275
+ userInlineSuffix: resolved.userInlineSuffix
25276
+ };
25277
+ }
25278
+ /**
25279
+ * Build a `Skill` object pi will faithfully render in
25280
+ * `<available_skills>`. We extract `name` and `description` from the
25281
+ * skill content's YAML frontmatter using pi's own `parseFrontmatter`
25282
+ * helper (proper YAML, not a regex hack) and fall back to the slug +
25283
+ * a generic description so a SKILL.md without frontmatter still
25284
+ * renders something meaningful.
25285
+ *
25286
+ * Frontmatter parsing is best-effort: a malformed YAML block is
25287
+ * optional metadata, not a reason to fail the task. We swallow parser
25288
+ * errors and fall back to the slug-derived metadata; the skill body
25289
+ * is unaffected.
25290
+ *
25291
+ * pi's `formatSkillsForPrompt` only reads `name`, `description`, and
25292
+ * `filePath` — `sourceInfo`/`baseDir` exist on the type but never
25293
+ * surface in the prompt, so a synthetic `SourceInfo` is enough.
25294
+ */
25295
+ function buildSyntheticSkill(args) {
25296
+ let fm = {};
25297
+ try {
25298
+ fm = parseFrontmatter(args.content).frontmatter;
25299
+ } catch {}
25300
+ return {
25301
+ name: clip(typeof fm.name === "string" && fm.name.trim().length > 0 ? fm.name.trim() : args.slug, MAX_SKILL_NAME),
25302
+ description: clip(typeof fm.description === "string" && fm.description.trim().length > 0 ? fm.description.trim() : `Runtime-injected context skill (${args.slug})`, MAX_SKILL_DESCRIPTION),
25303
+ filePath: args.filePath,
25304
+ baseDir: args.dir,
25305
+ sourceInfo: createSyntheticSourceInfo(args.filePath, {
25306
+ source: "moltnet:runtime-context",
25307
+ scope: "temporary",
25308
+ origin: "top-level",
25309
+ baseDir: args.dir
25310
+ }),
25311
+ disableModelInvocation: fm["disable-model-invocation"] === true
25312
+ };
25313
+ }
25314
+ function clip(s, max) {
25315
+ return s.length > max ? s.slice(0, max) : s;
25316
+ }
25317
+ //#endregion
25309
25318
  //#region src/runtime/subagent-tool.ts
25310
25319
  var SUBAGENT_SUBMIT_TOOL_NAME = "submit_subagent_output";
25311
25320
  var DEFAULT_SUBAGENT_SUBMIT_VALIDATION_RETRIES = 2;
@@ -25841,6 +25850,46 @@ function resolveSubmitTools(taskType, opts = {}) {
25841
25850
  };
25842
25851
  }
25843
25852
  //#endregion
25853
+ //#region src/runtime/task-event-emitter.ts
25854
+ var LOG_TRUNCATE_LIMIT = 4 * 1024;
25855
+ async function emitTaskEvent(input) {
25856
+ try {
25857
+ input.onTurnEvent(input.kind, summarizePayloadForLog(input.kind, input.payload));
25858
+ } catch (err) {
25859
+ process.stderr.write(`[emit] onTurnEvent threw for kind="${input.kind}": ${err instanceof Error ? err.message : String(err)}\n`);
25860
+ }
25861
+ try {
25862
+ await input.reporter.record({
25863
+ kind: input.kind,
25864
+ payload: input.payload
25865
+ });
25866
+ } catch (err) {
25867
+ const detail = err instanceof Error ? err.message : String(err);
25868
+ input.log(`executePiTask: reporter.record() failed for task ${input.taskId} attempt ${input.attemptN} kind="${input.kind}": ${detail}`);
25869
+ }
25870
+ }
25871
+ function summarizePayloadForLog(kind, payload) {
25872
+ switch (kind) {
25873
+ case "text_delta": {
25874
+ const delta = payload.delta;
25875
+ return { chars: typeof delta === "string" ? delta.length : 0 };
25876
+ }
25877
+ case "tool_call_start": return { tool: payload.tool_name };
25878
+ case "tool_call_end": return {
25879
+ tool: payload.tool_name,
25880
+ is_error: payload.is_error === true,
25881
+ ...payload.is_error === true && payload.result !== void 0 ? { result: payload.result } : {}
25882
+ };
25883
+ case "turn_end": return { stop_reason: payload.stop_reason };
25884
+ case "error": return {
25885
+ phase: payload.phase,
25886
+ message: typeof payload.message === "string" ? payload.message.slice(0, LOG_TRUNCATE_LIMIT) : payload.message
25887
+ };
25888
+ case "info": return Object.fromEntries(Object.entries(payload).map(([k, v]) => [k, typeof v === "string" ? v.slice(0, LOG_TRUNCATE_LIMIT) : v]));
25889
+ default: return payload;
25890
+ }
25891
+ }
25892
+ //#endregion
25844
25893
  //#region src/runtime/task-workspace.ts
25845
25894
  function prepareTaskWorkspace(task, requestedMountPath, executionPlan) {
25846
25895
  const branch = executionPlan?.worktreeBranch ?? null;
@@ -26209,15 +26258,17 @@ async function executePiTask(claimedTask, reporter, opts) {
26209
26258
  onTurnEvent = noopTurnEventHandler;
26210
26259
  }
26211
26260
  else onTurnEvent = opts.onTurnEvent ?? noopTurnEventHandler;
26212
- const emit = (kind, payload) => {
26213
- try {
26214
- onTurnEvent(kind, summarizePayloadForLog(kind, payload));
26215
- } catch (err) {
26216
- process.stderr.write(`[emit] onTurnEvent threw for kind="${kind}": ${err instanceof Error ? err.message : String(err)}\n`);
26217
- }
26218
- return reporter.record({
26261
+ const emit = async (kind, payload) => {
26262
+ await emitTaskEvent({
26219
26263
  kind,
26220
- payload
26264
+ payload,
26265
+ onTurnEvent,
26266
+ reporter,
26267
+ taskId: task.id,
26268
+ attemptN,
26269
+ log: (message) => {
26270
+ process.stderr.write(`${message}\n`);
26271
+ }
26221
26272
  });
26222
26273
  };
26223
26274
  const emitError = async (phase, message, extra = {}) => {
@@ -26321,6 +26372,21 @@ async function executePiTask(claimedTask, reporter, opts) {
26321
26372
  message
26322
26373
  });
26323
26374
  }
26375
+ const rawContext = task.input.context;
26376
+ let effectiveRuntimeContext;
26377
+ try {
26378
+ effectiveRuntimeContext = resolveEffectiveRuntimeContext({
26379
+ rawTaskContext: rawContext,
26380
+ runtimeProfileContext: opts.runtimeProfileContext
26381
+ });
26382
+ } catch (err) {
26383
+ const message = err instanceof Error ? err.message : String(err);
26384
+ await emit("error", {
26385
+ message,
26386
+ phase: "context_resolution"
26387
+ });
26388
+ return makeFailedOutput("context_resolution_failed", message);
26389
+ }
26324
26390
  let taskPrompt;
26325
26391
  try {
26326
26392
  const assembled = buildTaskUserPrompt(task, {
@@ -26333,7 +26399,8 @@ async function executePiTask(claimedTask, reporter, opts) {
26333
26399
  source: executionPlan?.workspaceSeed?.source === "producer" ? "producer_copy" : executionPlan?.workspaceAttachment !== void 0 ? "producer_attachment" : void 0
26334
26400
  },
26335
26401
  extras: opts.promptExtras,
26336
- priorContext: resolvedPriorContext
26402
+ priorContext: resolvedPriorContext,
26403
+ effectiveRuntimeContext
26337
26404
  });
26338
26405
  taskPrompt = assembled.text;
26339
26406
  await emit("info", {
@@ -26350,13 +26417,10 @@ async function executePiTask(claimedTask, reporter, opts) {
26350
26417
  });
26351
26418
  return makeFailedOutput("prompt_build_failed", message);
26352
26419
  }
26353
- const rawContext = task.input.context;
26354
26420
  let injectedContext;
26355
26421
  try {
26356
- const contextArray = rawContext === void 0 ? [] : rawContext;
26357
- if (!Check(TaskContext, contextArray)) throw new Error(`task.input.context failed TaskContext validation: ${JSON.stringify([...Errors(TaskContext, contextArray)].slice(0, 3))}`);
26358
- injectedContext = await injectTaskContext({
26359
- context: contextArray,
26422
+ injectedContext = await injectRuntimeContext({
26423
+ context: effectiveRuntimeContext,
26360
26424
  fs: managed.vm.fs,
26361
26425
  guestWorkspace: managed.guestWorkspace
26362
26426
  });
@@ -26948,27 +27012,6 @@ function wireSessionAbort(cancelSignal, session) {
26948
27012
  * `task_messages.payload` row. Bodies above 4 KiB are replaced with a
26949
27013
  * `{ truncated, original_size }` marker so the JSONL/DB size stays bounded.
26950
27014
  */
26951
- function summarizePayloadForLog(kind, payload) {
26952
- switch (kind) {
26953
- case "text_delta": {
26954
- const delta = payload.delta;
26955
- return { chars: typeof delta === "string" ? delta.length : 0 };
26956
- }
26957
- case "tool_call_start": return { tool: payload.tool_name };
26958
- case "tool_call_end": return {
26959
- tool: payload.tool_name,
26960
- is_error: payload.is_error === true,
26961
- ...payload.is_error === true && payload.result !== void 0 ? { result: payload.result } : {}
26962
- };
26963
- case "turn_end": return { stop_reason: payload.stop_reason };
26964
- case "error": return {
26965
- phase: payload.phase,
26966
- message: typeof payload.message === "string" ? payload.message.slice(0, TRUNCATE_LIMIT) : payload.message
26967
- };
26968
- case "info": return Object.fromEntries(Object.entries(payload).map(([k, v]) => [k, typeof v === "string" ? v.slice(0, TRUNCATE_LIMIT) : v]));
26969
- default: return payload;
26970
- }
26971
- }
26972
27015
  /**
26973
27016
  * Classify a `tool_execution_end` event for telemetry purposes.
26974
27017
  *
@@ -27565,4 +27608,4 @@ function moltnetExtension(pi) {
27565
27608
  registerMoltnetReflectCommand(pi, state);
27566
27609
  }
27567
27610
  //#endregion
27568
- export { HOST_EXEC_DEFAULT_BASE_ENV, activateAgentEnv, buildAgentSession, createGondolinBashOps, createGondolinEditOps, createGondolinReadOps, createGondolinWriteOps, createMoltNetTools, createPiOtelExtension, createPiProviderErrorRetryUi, createPiRetryTriage, createPiTaskExecutor, createSubagentTool, moltnetExtension as default, ensureSnapshot, executePiTask, findMainWorktree, injectTaskContext, loadCredentials, normalizeRetryTriageResult, redactRetryTriageSecrets, resolveTaskWorktreePath, resumeVm, toGuestPath };
27611
+ export { HOST_EXEC_DEFAULT_BASE_ENV, activateAgentEnv, buildAgentSession, createGondolinBashOps, createGondolinEditOps, createGondolinReadOps, createGondolinWriteOps, createMoltNetTools, createPiOtelExtension, createPiProviderErrorRetryUi, createPiRetryTriage, createPiTaskExecutor, createSubagentTool, moltnetExtension as default, ensureSnapshot, executePiTask, findMainWorktree, injectRuntimeContext as injectTaskContext, loadCredentials, normalizeRetryTriageResult, redactRetryTriageSecrets, resolveTaskWorktreePath, resumeVm, toGuestPath };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@themoltnet/pi-extension",
3
- "version": "0.33.0",
3
+ "version": "0.33.1",
4
4
  "type": "module",
5
5
  "description": "MoltNet pi extension — sandboxed tool execution in Gondolin VMs with MoltNet identity and persistent memory",
6
6
  "keywords": [
@@ -36,7 +36,7 @@
36
36
  "@earendil-works/gondolin": "^0.9.1",
37
37
  "@opentelemetry/api": "^1.9.0",
38
38
  "typebox": "^1.2.8",
39
- "@themoltnet/agent-runtime": "0.35.0",
39
+ "@themoltnet/agent-runtime": "0.35.1",
40
40
  "@themoltnet/sdk": "0.119.0"
41
41
  },
42
42
  "peerDependencies": {