@themoltnet/pi-extension 0.14.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,7 +1,11 @@
1
+ import { AgentSession } from '@earendil-works/pi-coding-agent';
2
+ import { Api } from '@earendil-works/pi-ai';
1
3
  import { BashOperations } from '@earendil-works/pi-coding-agent';
2
4
  import { connect } from '@themoltnet/sdk';
3
5
  import { EditOperations } from '@earendil-works/pi-coding-agent';
4
6
  import { ExtensionAPI } from '@earendil-works/pi-coding-agent';
7
+ import { LoadSkillsResult } from '@earendil-works/pi-coding-agent';
8
+ import { Model } from '@earendil-works/pi-ai';
5
9
  import { ReadOperations } from '@earendil-works/pi-coding-agent';
6
10
  import { Skill } from '@earendil-works/pi-coding-agent';
7
11
  import { Static } from '@sinclair/typebox';
@@ -27,6 +31,33 @@ import { WriteOperations } from '@earendil-works/pi-coding-agent';
27
31
  */
28
32
  export declare function activateAgentEnv(agentEnv: Record<string, string | undefined>, repoRoot: string): void;
29
33
 
34
+ /**
35
+ * Construct an in-memory `AgentSession`. The caller is responsible for
36
+ * eventually invoking `session.prompt(...)` and for tearing down — the
37
+ * helper does no lifecycle management beyond construction.
38
+ */
39
+ export declare function buildAgentSession(args: BuildAgentSessionArgs): Promise<AgentSession>;
40
+
41
+ declare interface BuildAgentSessionArgs {
42
+ /** Host directory mounted at /workspace inside the VM. */
43
+ mountPath: string;
44
+ /** pi auth directory (resolved from `PI_CODING_AGENT_DIR` or `~/.pi/agent`). */
45
+ piAuthDir: string;
46
+ /** Resolved pi model handle (provider + model id). */
47
+ modelHandle: Model<Api>;
48
+ /** Pre-built customTools array. Caller composes Gondolin + MoltNet + submit tools. */
49
+ customTools: ToolDefinition[];
50
+ /** System-prompt fragments appended after pi's defaults. Parent passes the
51
+ * runtime instructor; subagents pass their narrower variant. */
52
+ appendSystemPrompt: string[];
53
+ /** Skills to advertise in `<available_skills>`. Default: empty list. */
54
+ skillsOverride?: () => LoadSkillsResult;
55
+ /** Span attributes merged onto every OTel span the session emits. */
56
+ otelSpanAttrs: Record<string, string | number | boolean>;
57
+ /** Agent name for `gen_ai.agent.name` on the root span. */
58
+ agentName: string;
59
+ }
60
+
30
61
  declare interface ClaimedTask {
31
62
  /** The claimed task payload itself. */
32
63
  task: Task;
@@ -83,6 +114,73 @@ export declare function createPiOtelExtension(options?: PiOtelOptions): (pi: Ext
83
114
  */
84
115
  export declare function createPiTaskExecutor(opts: ExecutePiTaskOptions): (claimedTask: ClaimedTask, reporter: TaskReporter) => Promise<TaskOutput>;
85
116
 
117
+ /**
118
+ * Build the subagent custom tool for a parent session. The handle
119
+ * exposes the call counter so executors can emit summary telemetry
120
+ * when the parent terminates.
121
+ */
122
+ export declare function createSubagentTool(args: CreateSubagentToolArgs): SubagentToolHandle;
123
+
124
+ export declare interface CreateSubagentToolArgs {
125
+ /** Host directory mounted at /workspace inside the VM. */
126
+ mountPath: string;
127
+ /** pi auth directory the parent resolved. */
128
+ piAuthDir: string;
129
+ /** Resolved pi model handle — subagents share it. */
130
+ modelHandle: Model<Api>;
131
+ /** Agent name for telemetry. */
132
+ agentName: string;
133
+ /**
134
+ * Custom tools every subagent inherits (Gondolin-routed
135
+ * Read/Write/Edit/Bash + moltnet_* tools, etc). MUST NOT include
136
+ * the parent's submit-output tool, the parent's `subagent` tool,
137
+ * or any other parent-only artefact — the caller is responsible
138
+ * for filtering. The subagent appends its own submit tool.
139
+ */
140
+ inheritedCustomTools: ToolDefinition[];
141
+ /**
142
+ * The parent runtime instructor verbatim. Subagents prepend it to
143
+ * their own short "you are a subagent" preamble so the same
144
+ * invariants (gh auth, diary discipline, accountable commits)
145
+ * apply if the subagent takes those actions. The parent's task
146
+ * description dictates whether they should.
147
+ */
148
+ parentRuntimeInstructor: string;
149
+ parentTaskId: string;
150
+ parentTaskType: string;
151
+ parentAttemptN: number;
152
+ /**
153
+ * Parent task's cancel signal. When the daemon cancels the parent
154
+ * task (operator cancel or task-level `runningTimeoutSec` expiry),
155
+ * each in-flight subagent's inner `session.abort()` is invoked so
156
+ * it tears down promptly instead of running until its own LLM
157
+ * call resolves. Mirrors the existing `wireSessionAbort` pattern
158
+ * the parent session uses.
159
+ *
160
+ * Optional only because the test seam can omit it; production
161
+ * callers (executePiTask) pass `reporter.cancelSignal`.
162
+ */
163
+ parentCancelSignal?: AbortSignal;
164
+ /**
165
+ * Per-call fallback timeout. Defends against an inner session that
166
+ * ignores `abort()` for any reason (LLM provider stuck, tool call
167
+ * hanging on I/O, etc.). When the timeout fires, `session.abort()`
168
+ * is invoked and the tool returns `isError: true` with a
169
+ * `subagent_timed_out` reason the parent LLM can recover from.
170
+ *
171
+ * Default: 5 minutes. Set to `0` to disable (relying purely on
172
+ * parentCancelSignal). Negative values are treated as the default.
173
+ */
174
+ timeoutMs?: number;
175
+ /**
176
+ * Test seam. Production callers leave this undefined and get
177
+ * `buildAgentSession` from the factory module. Tests inject a mock
178
+ * that returns a stub session implementing only `prompt()` to
179
+ * exercise the tool's logic without booting a VM.
180
+ */
181
+ buildAgentSession?: (args: BuildAgentSessionArgs) => Promise<AgentSession>;
182
+ }
183
+
86
184
  /**
87
185
  * Ensure a cached snapshot exists, building one if needed.
88
186
  * Returns the absolute path to the qcow2 checkpoint file.
@@ -300,6 +398,29 @@ export declare interface SandboxConfig {
300
398
  /** Extract snapshot-specific config for backwards compat with ensureSnapshot. */
301
399
  export declare type SnapshotConfig = NonNullable<SandboxConfig['snapshot']>;
302
400
 
401
+ export declare interface SubagentToolHandle {
402
+ /** ToolDefinition to register via `customTools` on the parent session. */
403
+ readonly tool: ToolDefinition;
404
+ /** How many times the parent LLM has called this tool. */
405
+ getCallCount: () => number;
406
+ }
407
+
408
+ /**
409
+ * Parameters shape the parent LLM sees when calling the subagent tool.
410
+ *
411
+ * - `task` — natural-language instructions for the subagent.
412
+ * The parent authors this per call. Must be
413
+ * non-empty.
414
+ * - `output_schema` — name of a registered SubagentOutputContract.
415
+ * Resolved at call time; unknown names error.
416
+ */
417
+ export declare const SubagentToolParameters: TObject<{
418
+ task: TString;
419
+ output_schema: TString;
420
+ }>;
421
+
422
+ export declare type SubagentToolParameters = Static<typeof SubagentToolParameters>;
423
+
303
424
  /**
304
425
  * The Task promise body.
305
426
  *
package/dist/index.js CHANGED
@@ -8580,6 +8580,39 @@ function extractUsage(message) {
8580
8580
  };
8581
8581
  }
8582
8582
  //#endregion
8583
+ //#region src/runtime/agent-session-factory.ts
8584
+ var NO_SKILLS = () => ({
8585
+ skills: [],
8586
+ diagnostics: []
8587
+ });
8588
+ /**
8589
+ * Construct an in-memory `AgentSession`. The caller is responsible for
8590
+ * eventually invoking `session.prompt(...)` and for tearing down — the
8591
+ * helper does no lifecycle management beyond construction.
8592
+ */
8593
+ async function buildAgentSession(args) {
8594
+ const piOtelExtension = createPiOtelExtension({
8595
+ agentName: args.agentName,
8596
+ spanAttributes: args.otelSpanAttrs
8597
+ });
8598
+ const resourceLoader = new DefaultResourceLoader({
8599
+ cwd: args.mountPath,
8600
+ agentDir: args.piAuthDir,
8601
+ extensionFactories: [piOtelExtension],
8602
+ appendSystemPrompt: args.appendSystemPrompt,
8603
+ skillsOverride: args.skillsOverride ?? NO_SKILLS
8604
+ });
8605
+ await resourceLoader.reload();
8606
+ return (await createAgentSession({
8607
+ agentDir: args.piAuthDir,
8608
+ cwd: args.mountPath,
8609
+ model: args.modelHandle,
8610
+ customTools: args.customTools,
8611
+ sessionManager: SessionManager.inMemory(),
8612
+ resourceLoader
8613
+ })).session;
8614
+ }
8615
+ //#endregion
8583
8616
  //#region ../agent-runtime/src/context-bindings.ts
8584
8617
  var PROMPT_SEPARATOR = "\n\n---\n\n";
8585
8618
  /**
@@ -9440,6 +9473,15 @@ function validateTaskOutput(taskType, output, input) {
9440
9473
  function getTaskOutputSchema(taskType) {
9441
9474
  return getTaskTypeEntry(taskType)?.outputSchema ?? null;
9442
9475
  }
9476
+ /**
9477
+ * Whether sessions running this task type should have the generic
9478
+ * `subagent` custom tool registered. Returns `false` for unknown task
9479
+ * types and for task types that didn't opt in. See `TaskTypeEntry`
9480
+ * for the design rationale.
9481
+ */
9482
+ function taskTypeUsesSubagents(taskType) {
9483
+ return getTaskTypeEntry(taskType)?.usesSubagents === true;
9484
+ }
9443
9485
  //#endregion
9444
9486
  //#region ../tasks/src/wire.ts
9445
9487
  /**
@@ -13935,6 +13977,25 @@ var require_multistream = /* @__PURE__ */ __commonJSMin(((exports, module) => {
13935
13977
  module.exports.pino = pino;
13936
13978
  })))();
13937
13979
  //#endregion
13980
+ //#region ../agent-runtime/src/subagent-output-contracts.ts
13981
+ var REGISTRY = /* @__PURE__ */ new Map();
13982
+ /**
13983
+ * Resolve a subagent output contract by name. Returns `null` for
13984
+ * unknown names — callers (the subagent custom tool) decide whether
13985
+ * that's a tool error the parent LLM can recover from or a hard fail.
13986
+ */
13987
+ function getSubagentOutputContract(name) {
13988
+ return REGISTRY.get(name) ?? null;
13989
+ }
13990
+ /**
13991
+ * List all registered contracts. Useful for diagnostics and for the
13992
+ * subagent tool's parameter description so a parent LLM can see what
13993
+ * contracts are available without enumerating them in its prompt.
13994
+ */
13995
+ function listSubagentOutputContracts() {
13996
+ return [...REGISTRY.values()];
13997
+ }
13998
+ //#endregion
13938
13999
  //#region src/runtime/inject-task-context.ts
13939
14000
  /**
13940
14001
  * Slice 1.5 of #943 — wire the agent-runtime resolver into the
@@ -14128,6 +14189,190 @@ function buildRuntimeInstructor(ctx) {
14128
14189
  ].join("\n");
14129
14190
  }
14130
14191
  //#endregion
14192
+ //#region src/runtime/subagent-tool.ts
14193
+ var SUBAGENT_SUBMIT_TOOL_NAME = "submit_subagent_output";
14194
+ /**
14195
+ * Parameters shape the parent LLM sees when calling the subagent tool.
14196
+ *
14197
+ * - `task` — natural-language instructions for the subagent.
14198
+ * The parent authors this per call. Must be
14199
+ * non-empty.
14200
+ * - `output_schema` — name of a registered SubagentOutputContract.
14201
+ * Resolved at call time; unknown names error.
14202
+ */
14203
+ var SubagentToolParameters = Type$1.Object({
14204
+ task: Type$1.String({
14205
+ minLength: 1,
14206
+ description: "Natural-language instructions for the subagent. The subagent starts with a fresh conversation and a narrowed system prompt; this is the only context it has from you."
14207
+ }),
14208
+ output_schema: Type$1.String({
14209
+ minLength: 1,
14210
+ description: "Name of a registered subagent output contract. The subagent must submit a structured payload via `submit_subagent_output` matching this contract."
14211
+ })
14212
+ }, { additionalProperties: false });
14213
+ var DEFAULT_SUBAGENT_TIMEOUT_MS = 300 * 1e3;
14214
+ /**
14215
+ * Build the subagent custom tool for a parent session. The handle
14216
+ * exposes the call counter so executors can emit summary telemetry
14217
+ * when the parent terminates.
14218
+ */
14219
+ function createSubagentTool(args) {
14220
+ const buildSession = args.buildAgentSession ?? buildAgentSession;
14221
+ let callCount = 0;
14222
+ return {
14223
+ tool: defineTool({
14224
+ name: "subagent",
14225
+ label: "Delegate to subagent",
14226
+ description: subagentToolDescription(),
14227
+ parameters: SubagentToolParameters,
14228
+ async execute(_id, params) {
14229
+ if (!Value.Check(SubagentToolParameters, params)) return toolError(`subagent: invalid parameters: ${JSON.stringify([...Value.Errors(SubagentToolParameters, params)].slice(0, 3))}`);
14230
+ const { task, output_schema } = params;
14231
+ const contract = getSubagentOutputContract(output_schema);
14232
+ if (!contract) return toolError(`subagent: unknown output_schema "${output_schema}". Registered contracts: [${listSubagentOutputContracts().map((c) => c.name).join(", ")}]`);
14233
+ callCount += 1;
14234
+ const callIndex = callCount;
14235
+ let captured = null;
14236
+ const submitTool = defineTool({
14237
+ name: SUBAGENT_SUBMIT_TOOL_NAME,
14238
+ label: `Submit ${output_schema}`,
14239
+ description: `Submit your structured output for this subagent task. Call exactly once when done. Args MUST match the ${output_schema} contract; mismatches return a tool error you can recover from in the same session.`,
14240
+ parameters: contract.parametersSchema,
14241
+ async execute(_innerId, innerParams) {
14242
+ if (!Value.Check(contract.parametersSchema, innerParams)) return toolError(`submit_subagent_output: schema validation failed: ${[...Value.Errors(contract.parametersSchema, innerParams)].slice(0, 3).map((e) => `${e.path}: ${e.message}`).join("; ")}. Re-call with a corrected payload.`);
14243
+ captured = innerParams;
14244
+ return {
14245
+ content: [{
14246
+ type: "text",
14247
+ text: "Output captured. Subagent session will terminate; no further action needed."
14248
+ }],
14249
+ details: { captured: true },
14250
+ terminate: true
14251
+ };
14252
+ }
14253
+ });
14254
+ const subagentInstructor = buildSubagentInstructor({
14255
+ contractName: output_schema,
14256
+ contractDescription: contract.description,
14257
+ parentTaskId: args.parentTaskId,
14258
+ callIndex
14259
+ });
14260
+ const session = await buildSession({
14261
+ mountPath: args.mountPath,
14262
+ piAuthDir: args.piAuthDir,
14263
+ modelHandle: args.modelHandle,
14264
+ agentName: args.agentName,
14265
+ customTools: [...args.inheritedCustomTools, submitTool],
14266
+ appendSystemPrompt: [args.parentRuntimeInstructor, subagentInstructor],
14267
+ skillsOverride: () => ({
14268
+ skills: [],
14269
+ diagnostics: []
14270
+ }),
14271
+ otelSpanAttrs: {
14272
+ "moltnet.task.id": args.parentTaskId,
14273
+ "moltnet.task.type": args.parentTaskType,
14274
+ "moltnet.task.attempt": args.parentAttemptN,
14275
+ "moltnet.subagent.contract": output_schema,
14276
+ "moltnet.subagent.index": callIndex
14277
+ }
14278
+ });
14279
+ let abortReason = null;
14280
+ let abortInvoked = false;
14281
+ const fireAbort = (reason) => {
14282
+ if (abortInvoked) return;
14283
+ abortInvoked = true;
14284
+ abortReason = reason;
14285
+ session.abort().catch((err) => {
14286
+ const message = err instanceof Error ? err.message : String(err);
14287
+ process.stderr.write(`[subagent] inner session.abort() failed: ${message}\n`);
14288
+ });
14289
+ };
14290
+ const cancelListener = args.parentCancelSignal ? (() => {
14291
+ const signal = args.parentCancelSignal;
14292
+ const listener = () => fireAbort("parent_cancelled");
14293
+ if (signal.aborted) listener();
14294
+ else signal.addEventListener("abort", listener, { once: true });
14295
+ return () => signal.removeEventListener("abort", listener);
14296
+ })() : null;
14297
+ const timeoutMs = args.timeoutMs === void 0 || args.timeoutMs < 0 ? DEFAULT_SUBAGENT_TIMEOUT_MS : args.timeoutMs;
14298
+ const timeoutHandle = timeoutMs > 0 ? setTimeout(() => fireAbort("subagent_timed_out"), timeoutMs) : null;
14299
+ try {
14300
+ await session.prompt(task);
14301
+ } catch (err) {
14302
+ return toolError(`subagent: inner session.prompt() threw: ${err instanceof Error ? err.message : String(err)}`);
14303
+ } finally {
14304
+ if (timeoutHandle) clearTimeout(timeoutHandle);
14305
+ if (cancelListener) cancelListener();
14306
+ }
14307
+ if (abortReason !== null) return toolError(`subagent: ${abortReason === "subagent_timed_out" ? `subagent timed out after ${timeoutMs}ms` : "parent task was cancelled"}. The parent should fail this task or retry with a clearer scope.`);
14308
+ if (captured === null) return toolError(`subagent: inner session ended without calling ${SUBAGENT_SUBMIT_TOOL_NAME}. The parent should retry with clearer instructions or fail the task.`);
14309
+ return {
14310
+ content: [{
14311
+ type: "text",
14312
+ text: JSON.stringify(captured)
14313
+ }],
14314
+ details: {
14315
+ captured: true,
14316
+ contract: output_schema,
14317
+ callIndex
14318
+ }
14319
+ };
14320
+ }
14321
+ }),
14322
+ getCallCount: () => callCount
14323
+ };
14324
+ }
14325
+ function subagentToolDescription() {
14326
+ return [
14327
+ "Delegate a sub-task to a fresh subagent session with isolated context.",
14328
+ "",
14329
+ "The subagent starts with no conversation history and only the `task` ",
14330
+ "string you provide as its instructions. It runs in the same VM with ",
14331
+ "the same tools you have (Gondolin-routed Read/Write/Edit/Bash, ",
14332
+ "moltnet_* tools), and is expected to call ",
14333
+ `\`${SUBAGENT_SUBMIT_TOOL_NAME}\` with a payload matching the named `,
14334
+ "contract before its session ends.",
14335
+ "",
14336
+ "On success, the tool result is the JSON-stringified subagent payload.",
14337
+ "On failure (unknown contract, validation error, subagent did not ",
14338
+ "submit) the tool returns isError:true with a recoverable message."
14339
+ ].join("\n");
14340
+ }
14341
+ function buildSubagentInstructor(args) {
14342
+ return [
14343
+ "# You are a subagent",
14344
+ "",
14345
+ `Parent task: \`${args.parentTaskId}\` (subagent call #${args.callIndex}).`,
14346
+ "",
14347
+ `Your assigned output contract is \`${args.contractName}\`:`,
14348
+ `${args.contractDescription}`,
14349
+ "",
14350
+ "Rules for this session:",
14351
+ "",
14352
+ `- You MUST call \`${SUBAGENT_SUBMIT_TOOL_NAME}\` exactly once with a `,
14353
+ " payload matching the contract above. Your session terminates on ",
14354
+ " the valid call.",
14355
+ "- The parent's message above is your task. Do not invent additional ",
14356
+ " steps the parent did not request.",
14357
+ "- All MoltNet runtime invariants from the parent runtime instructor ",
14358
+ " apply (diary discipline, gh-auth pattern, etc.) IF you take any ",
14359
+ " action that would trigger them. Most subagents do not commit code ",
14360
+ " or open PRs — only do so if your task message explicitly requires it.",
14361
+ "- You do NOT have access to the `subagent` tool. Do not attempt nested ",
14362
+ " delegation; do the work yourself."
14363
+ ].join("\n");
14364
+ }
14365
+ function toolError(text) {
14366
+ return {
14367
+ content: [{
14368
+ type: "text",
14369
+ text
14370
+ }],
14371
+ details: { captured: false },
14372
+ isError: true
14373
+ };
14374
+ }
14375
+ //#endregion
14131
14376
  //#region src/runtime/task-output.ts
14132
14377
  var METER_NAME = "@themoltnet/pi-extension/task-output";
14133
14378
  var parseResultCounter = null;
@@ -14439,6 +14684,7 @@ async function executePiTask(claimedTask, reporter, opts) {
14439
14684
  const taskTeamId = task.teamId ?? "";
14440
14685
  let reporterOpen = false;
14441
14686
  let session = null;
14687
+ let subagentHandle = null;
14442
14688
  const finalUsage = emptyUsage(opts.provider, opts.model);
14443
14689
  let cancelListener = null;
14444
14690
  const makeFailedOutput = (code, message, usage = finalUsage) => ({
@@ -14556,47 +14802,55 @@ async function executePiTask(claimedTask, reporter, opts) {
14556
14802
  });
14557
14803
  const piAuthDir = process.env.PI_CODING_AGENT_DIR ?? join(homedir(), ".pi", "agent");
14558
14804
  const modelHandle = getModel(opts.provider, opts.model);
14559
- const piOtelExtension = createPiOtelExtension({
14560
- agentName: opts.agentName,
14561
- spanAttributes: {
14562
- "moltnet.task.id": task.id,
14563
- "moltnet.task.attempt": attemptN,
14564
- "moltnet.task.type": task.taskType
14565
- }
14566
- });
14567
- const appendSystemPrompt = [buildRuntimeInstructor({
14805
+ const runtimeInstructor = buildRuntimeInstructor({
14568
14806
  taskId: task.id,
14569
14807
  taskType: task.taskType,
14570
14808
  attemptN,
14571
14809
  diaryId,
14572
14810
  agentName: opts.agentName,
14573
14811
  correlationId: task.correlationId ?? null
14574
- })];
14812
+ });
14813
+ const appendSystemPrompt = [runtimeInstructor];
14575
14814
  if (injectedContext.systemPromptPrefix) appendSystemPrompt.push(injectedContext.systemPromptPrefix);
14576
14815
  const injectedSkills = injectedContext.skills;
14577
- const resourceLoader = new DefaultResourceLoader({
14578
- cwd: mountPath,
14579
- agentDir: piAuthDir,
14580
- extensionFactories: [piOtelExtension],
14816
+ const parentSubagentTools = [];
14817
+ if (taskTypeUsesSubagents(task.taskType)) {
14818
+ subagentHandle = createSubagentTool({
14819
+ mountPath,
14820
+ piAuthDir,
14821
+ modelHandle,
14822
+ agentName: opts.agentName,
14823
+ inheritedCustomTools: [...gondolinCustomTools, ...moltnetTools],
14824
+ parentRuntimeInstructor: runtimeInstructor,
14825
+ parentTaskId: task.id,
14826
+ parentTaskType: task.taskType,
14827
+ parentAttemptN: attemptN,
14828
+ parentCancelSignal: reporter.cancelSignal
14829
+ });
14830
+ parentSubagentTools.push(subagentHandle.tool);
14831
+ }
14832
+ session = await buildAgentSession({
14833
+ mountPath,
14834
+ piAuthDir,
14835
+ modelHandle,
14836
+ agentName: opts.agentName,
14837
+ customTools: [
14838
+ ...gondolinCustomTools,
14839
+ ...moltnetTools,
14840
+ ...submitTools,
14841
+ ...parentSubagentTools
14842
+ ],
14581
14843
  appendSystemPrompt,
14582
14844
  skillsOverride: () => ({
14583
14845
  skills: injectedSkills,
14584
14846
  diagnostics: []
14585
- })
14847
+ }),
14848
+ otelSpanAttrs: {
14849
+ "moltnet.task.id": task.id,
14850
+ "moltnet.task.attempt": attemptN,
14851
+ "moltnet.task.type": task.taskType
14852
+ }
14586
14853
  });
14587
- await resourceLoader.reload();
14588
- session = (await createAgentSession({
14589
- agentDir: piAuthDir,
14590
- cwd: mountPath,
14591
- model: modelHandle,
14592
- customTools: [
14593
- ...gondolinCustomTools,
14594
- ...moltnetTools,
14595
- ...submitTools
14596
- ],
14597
- sessionManager: SessionManager.inMemory(),
14598
- resourceLoader
14599
- })).session;
14600
14854
  } catch (err) {
14601
14855
  const message = err instanceof Error ? err.message : String(err);
14602
14856
  await emit("error", {
@@ -14667,6 +14921,10 @@ async function executePiTask(claimedTask, reporter, opts) {
14667
14921
  phase: "session_prompt"
14668
14922
  });
14669
14923
  }
14924
+ if (subagentHandle && subagentHandle.getCallCount() > 0) await emit("info", {
14925
+ event: "subagent_summary",
14926
+ callCount: subagentHandle.getCallCount()
14927
+ });
14670
14928
  await Promise.all(recordingPromise);
14671
14929
  const cancelled = reporter.cancelSignal.aborted;
14672
14930
  let parsedOutput = null;
@@ -15126,4 +15384,4 @@ function moltnetExtension(pi) {
15126
15384
  registerMoltnetReflectCommand(pi, state);
15127
15385
  }
15128
15386
  //#endregion
15129
- export { HOST_EXEC_DEFAULT_BASE_ENV, activateAgentEnv, createGondolinBashOps, createGondolinEditOps, createGondolinReadOps, createGondolinWriteOps, createMoltNetTools, createPiOtelExtension, createPiTaskExecutor, moltnetExtension as default, ensureSnapshot, executePiTask, findMainWorktree, injectTaskContext, loadCredentials, resumeVm, toGuestPath };
15387
+ export { HOST_EXEC_DEFAULT_BASE_ENV, activateAgentEnv, buildAgentSession, createGondolinBashOps, createGondolinEditOps, createGondolinReadOps, createGondolinWriteOps, createMoltNetTools, createPiOtelExtension, createPiTaskExecutor, createSubagentTool, moltnetExtension as default, ensureSnapshot, executePiTask, findMainWorktree, injectTaskContext, loadCredentials, resumeVm, toGuestPath };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@themoltnet/pi-extension",
3
- "version": "0.14.0",
3
+ "version": "0.15.0",
4
4
  "type": "module",
5
5
  "description": "MoltNet pi extension — sandboxed tool execution in Gondolin VMs with MoltNet identity and persistent memory",
6
6
  "license": "MIT",
@@ -31,7 +31,7 @@
31
31
  "@earendil-works/gondolin": "^0.9.1",
32
32
  "@opentelemetry/api": "^1.9.0",
33
33
  "@sinclair/typebox": "^0.34.0",
34
- "@themoltnet/agent-runtime": "0.12.0",
34
+ "@themoltnet/agent-runtime": "0.13.0",
35
35
  "@themoltnet/sdk": "0.100.0"
36
36
  },
37
37
  "peerDependencies": {