@arhen/pi-core-subagent 1.3.2 → 1.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -234,7 +234,7 @@ Background + intercom:
234
234
 
235
235
  | Tool | Purpose |
236
236
  |---|---|
237
- | `subagent` | single / `tasks` (parallel or graph via `needs`) / `chain` (`{previous}`); `background:true` fire-and-forget; `allowIntercom:true` enables child talk tools; `notifyPerTask: true` wakes you as each task completes (default off) |
237
+ | `subagent` | single / `tasks` (parallel or graph via `needs`) / `chain` (`{previous}`); `background:true` fire-and-forget; `allowIntercom:true` enables child talk tools; `notifyPerTask: true` wakes you as each task completes (background runs only; default off) |
238
238
  | `subagent_status` | live per-task snapshot (non-blocking), including each child's session file path |
239
239
  | `subagent_result` | full output of a run or one task |
240
240
  | `await_subagent` | block until a run finishes (optional `timeoutMs`) |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arhen/pi-core-subagent",
3
- "version": "1.3.2",
3
+ "version": "1.3.3",
4
4
  "type": "module",
5
5
  "description": "pi extension: fast in-process subagents with a dependency-graph scheduler (needs edges gate tasks and carry upstream output into dependent prompts), plus background runs, intercom and agent-to-agent mailbox. Leader defines agents inline.",
6
6
  "license": "MIT",
package/src/child.ts CHANGED
@@ -100,7 +100,7 @@ export function createChildTools(taskId: string, handlers: ChildHandlers): ToolD
100
100
  .map((m) => `from ${m.from}: ${m.text}`)
101
101
  .join("\n");
102
102
  const capped = body.length > 4000 ? body.slice(0, 4000).replace(/[\uD800-\uDBFF]$/, "") : body; // multibyte-safe
103
- return { content: [{ type: "text" as const, text: body }], details: { messages } };
103
+ return { content: [{ type: "text" as const, text: capped }], details: { messages } };
104
104
  },
105
105
  },
106
106
  ];
package/src/index.ts CHANGED
@@ -17,7 +17,7 @@ import type { ThinkingLevel } from "@earendil-works/pi-agent-core";
17
17
  import { StringEnum, type Api, type AssistantMessage, type Model } from "@earendil-works/pi-ai";
18
18
  import { Text, truncateToWidth } from "@earendil-works/pi-tui";
19
19
  import type { Component, TUI } from "@earendil-works/pi-tui";
20
- import { Type } from "typebox";
20
+ import { Type, type Static } from "typebox";
21
21
  import { join } from "node:path";
22
22
  import { CHILD_TALK_TOOLS, createChildTools, createWatchdog, type ChildHandlers } from "./child.ts";
23
23
  import { createMailbox, type Mailbox } from "./mailbox.ts";
@@ -50,24 +50,6 @@ interface UsageStats {
50
50
  turns: number;
51
51
  }
52
52
 
53
- interface TaskInput {
54
- id?: string;
55
- /** Name the leader invents for this subagent (display + mailbox addressing). */
56
- agent: string;
57
- task: string;
58
- /** Task ids this task depends on. The edge carries the upstream output into this prompt. */
59
- needs?: string[];
60
- /** System prompt the leader writes for this agent. Optional — a minimal default is used. */
61
- prompt?: string;
62
- /** true = write toolset; false/omitted = read-only toolset. */
63
- write?: boolean;
64
- tools?: string[];
65
- model?: string;
66
- thinking?: string;
67
- cwd?: string;
68
- maxRuntimeMs?: number;
69
- }
70
-
71
53
  interface TaskSnapshot {
72
54
  id: string;
73
55
  runId: string;
@@ -1215,25 +1197,31 @@ class SubagentManager {
1215
1197
  awaitRun(runId: string, timeoutMs?: number): Promise<RunSnapshot | undefined> {
1216
1198
  const run = this.runs.get(runId);
1217
1199
  if (!run) return Promise.resolve(undefined);
1218
- run.awaited = true;
1219
- if (TERMINAL.includes(run.status)) return Promise.resolve(cloneRun(run));
1200
+ if (TERMINAL.includes(run.status)) {
1201
+ run.awaited = true;
1202
+ return Promise.resolve(cloneRun(run));
1203
+ }
1220
1204
  const settled = new Promise<RunSnapshot | undefined>((resolve) => {
1221
1205
  const prev = this.settlers.get(runId);
1222
1206
  this.settlers.set(runId, (r) => {
1223
1207
  prev?.(r);
1224
1208
  resolve(r);
1225
1209
  });
1226
- // Settle may have run between the terminal check and wiring.
1227
- if (TERMINAL.includes(run.status)) resolve(cloneRun(run));
1228
1210
  });
1229
- if (!timeoutMs) return settled;
1230
- return Promise.race([
1231
- settled,
1232
- new Promise<RunSnapshot | undefined>((resolve) => {
1233
- const timer = setTimeout(() => resolve(this.runs.get(runId) ? cloneRun(this.runs.get(runId)!) : undefined), timeoutMs);
1234
- settled.then(() => clearTimeout(timer));
1235
- }),
1236
- ]);
1211
+ if (timeoutMs) {
1212
+ return Promise.race([
1213
+ settled,
1214
+ new Promise<RunSnapshot | undefined>((resolve) => {
1215
+ const timer = setTimeout(() => resolve(this.runs.get(runId) ? cloneRun(this.runs.get(runId)!) : undefined), timeoutMs);
1216
+ settled.then(() => clearTimeout(timer));
1217
+ }),
1218
+ ]);
1219
+ }
1220
+ // Awaiting to completion: parent gets the real result, so suppress the
1221
+ // completion notice. On timeout we resolve a snapshot and leave awaited
1222
+ // unset, so the parent still receives the completion notification.
1223
+ run.awaited = true;
1224
+ return settled;
1237
1225
  }
1238
1226
  }
1239
1227
 
@@ -1255,29 +1243,13 @@ const TaskItem = Type.Object({
1255
1243
  needs: Type.Optional(Type.Array(Type.String(), { description: "Ids of tasks this one waits for; their outputs are prepended to this prompt." })),
1256
1244
  });
1257
1245
 
1258
- type SubagentParamsShape = {
1259
- agent?: string;
1260
- task?: string;
1261
- prompt?: string;
1262
- write?: boolean;
1263
- tasks?: TaskInput[];
1264
- chain?: TaskInput[];
1265
- model?: string;
1266
- thinking?: string;
1267
- cwd?: string;
1268
- tools?: string[];
1269
- concurrency?: number;
1270
- maxRuntimeMs?: number;
1271
- background?: boolean;
1272
- allowIntercom?: boolean;
1273
- notifyPerTask?: boolean;
1274
- };
1275
1246
 
1276
1247
  const SubagentParams = Type.Object({
1277
1248
  agent: Type.Optional(Type.String({ minLength: 1, description: "Name you invent for this subagent (single mode)" })),
1278
1249
  task: Type.Optional(Type.String({ minLength: 1, description: "Task (single mode)" })),
1279
1250
  prompt: Type.Optional(Type.String({ description: "System prompt for this agent (single mode)" })),
1280
1251
  write: Type.Optional(Type.Boolean({ description: "true = write toolset; default false = read-only (single mode)" })),
1252
+ tools: Type.Optional(Type.Array(Type.String(), { description: "Explicit tool allowlist (overrides the toolset) (single mode)" })),
1281
1253
  tasks: Type.Optional(Type.Array(TaskItem, { description: "Parallel tasks" })),
1282
1254
  chain: Type.Optional(Type.Array(TaskItem, { description: "Sequential tasks; {previous} = prior output" })),
1283
1255
  model: Type.Optional(Type.String({ description: "Model override (single mode)" })),
@@ -1286,10 +1258,14 @@ const SubagentParams = Type.Object({
1286
1258
  concurrency: Type.Optional(Type.Number({ description: `Parallel concurrency (default ${DEFAULT_CONCURRENCY}, max ${MAX_CONCURRENCY})` })),
1287
1259
  maxRuntimeMs: Type.Optional(Type.Number({ description: "Per-task timeout, ms. Omit for no cap (default): tasks run until done, stalled, or user-aborted." })),
1288
1260
  background: Type.Optional(Type.Boolean({ description: "Fire-and-forget: return immediately with a runId; you'll be notified on completion" })),
1289
- notifyPerTask: Type.Optional(Type.Boolean({ description: "Wake you (queued follow-up turn) as each task completes, even mid-run. Default false." })),
1261
+ notifyPerTask: Type.Optional(Type.Boolean({ description: "Wake you (queued follow-up turn) as each task completes — background runs only, since blocking runs can't be woken mid-tool. Default false." })),
1290
1262
  allowIntercom: Type.Optional(Type.Boolean({ description: "Let children ask you questions, notify you, and message sibling subagents" })),
1291
1263
  });
1292
1264
 
1265
+ /** Derived from the schemas — single source of truth, no hand-maintained mirror. */
1266
+ type TaskInput = Static<typeof TaskItem>;
1267
+ type SubagentParamsShape = Static<typeof SubagentParams>;
1268
+
1293
1269
  const RunIdParam = Type.Object({ runId: Type.String({ description: "Run id from subagent()" }) });
1294
1270
  const ResultParam = Type.Object({
1295
1271
  runId: Type.String(),
@@ -1391,7 +1367,7 @@ export default function (pi: ExtensionAPI) {
1391
1367
  parameters: SubagentParams,
1392
1368
  executionMode: "parallel", // sibling subagent calls run concurrently, not serialized
1393
1369
  async execute(_toolCallId, params, signal, onUpdate, ctx) {
1394
- const typed = params as unknown as SubagentParamsShape;
1370
+ const typed = params as SubagentParamsShape;
1395
1371
  if (typed.background) {
1396
1372
  const details = manager.startInBackground(typed, ctx);
1397
1373
  return {