@arhen/pi-core-subagent 1.3.7 → 1.3.9

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
@@ -241,6 +241,7 @@ Background (default) + intercom — the run returns a runId immediately; you sta
241
241
  | `subagent_result` | full output of a run or one task |
242
242
  | `await_subagent` | block until a run finishes (optional `timeoutMs`) |
243
243
  | `reply_subagent` | answer a child's `ask_parent` question |
244
+ | `steer_subagent` | inject a steering message into a running child's session (queues as steer if mid-turn; lands at its next model boundary) |
244
245
  | `subagent_cancel` | abort a running/queued run |
245
246
 
246
247
  ### Per-task fields
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arhen/pi-core-subagent",
3
- "version": "1.3.7",
3
+ "version": "1.3.9",
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/index.ts CHANGED
@@ -33,6 +33,7 @@ import {
33
33
  ReplyParam,
34
34
  ResultParam,
35
35
  RunIdParam,
36
+ SteerParam,
36
37
  SubagentParams,
37
38
  type SubagentParamsShape,
38
39
  } from "./schemas.ts";
@@ -76,7 +77,8 @@ export default function (pi: ExtensionAPI) {
76
77
  );
77
78
  };
78
79
  pi.registerCommand("subagents", {
79
- description: "List subagent runs. `/subagents peek` opens the browsable pane; `/subagents auto-bg on|off` toggles background-by-default.",
80
+ description:
81
+ "List subagent runs. `/subagents peek` opens the browsable pane; `/subagents auto-bg on|off` toggles background-by-default.",
80
82
  handler: async (args, ctx) => {
81
83
  const arg = String(args ?? "")
82
84
  .trim()
@@ -86,9 +88,15 @@ export default function (pi: ExtensionAPI) {
86
88
  const value = arg.split(/\s+/)[1];
87
89
  if (value === "on" || value === "off") {
88
90
  const next = manager.setAutoBg(value === "on");
89
- ctx.ui.notify(`auto-bg ${next ? "on" : "off"} — subagent calls default to ${next ? "background" : "blocking (inline result)"}.`, "info");
91
+ ctx.ui.notify(
92
+ `auto-bg ${next ? "on" : "off"} — subagent calls default to ${next ? "background" : "blocking (inline result)"}.`,
93
+ "info",
94
+ );
90
95
  } else {
91
- ctx.ui.notify(`auto-bg is ${manager.autoBgOn ? "on" : "off"} — use \`/subagents auto-bg on|off\` to change it.`, "info");
96
+ ctx.ui.notify(
97
+ `auto-bg is ${manager.autoBgOn ? "on" : "off"} — use \`/subagents auto-bg on|off\` to change it.`,
98
+ "info",
99
+ );
92
100
  }
93
101
  return;
94
102
  }
@@ -128,7 +136,7 @@ export default function (pi: ExtensionAPI) {
128
136
  // ponytail: this string is billed on every request. No example block — an example
129
137
  // biases the model toward one shape; guidelines + JSON schema describe all of them.
130
138
  description:
131
- 'Run isolated subagents (own context, own session). You invent each agent: name, optional system prompt, toolset (read-only default, write:true to edit). Use `agent`+`task` for one, `tasks` for many. `needs` declares dependency edges: a task waits for its needs and receives their outputs prepended to its prompt. background is the default (returns a runId immediately; toggle via `/subagents auto-bg off`); set background:false when you need the result inline in this turn. allowIntercom:true lets children talk to you and each other.',
139
+ "Run isolated subagents (own context, own session). You invent each agent: name, optional system prompt, toolset (read-only default, write:true to edit). Use `agent`+`task` for one, `tasks` for many. `needs` declares dependency edges: a task waits for its needs and receives their outputs prepended to its prompt. background is the default (returns a runId immediately; toggle via `/subagents auto-bg off`); set background:false when you need the result inline in this turn. allowIntercom:true lets children talk to you and each other.",
132
140
  promptSnippet: "Define and delegate work to specialized subagents.",
133
141
  promptGuidelines: [
134
142
  "Use subagent when independent review, testing, research, or parallel analysis improves quality.",
@@ -309,6 +317,33 @@ export default function (pi: ExtensionAPI) {
309
317
  },
310
318
  });
311
319
 
320
+ pi.registerTool<typeof SteerParam, { steered?: string[] }>({
321
+ name: "steer_subagent",
322
+ label: "Steer Subagent",
323
+ description:
324
+ "Inject a steering message into a running subagent's session (queues as steer if the child is mid-turn; delivered at its next model boundary).",
325
+ parameters: SteerParam,
326
+ async execute(_id, params) {
327
+ const { runId, taskId, message } = params as { runId: string; taskId?: string; message: string };
328
+ const ok = manager.steerTask(runId, taskId, message);
329
+ if (!ok)
330
+ return {
331
+ content: [{ type: "text", text: `No running task(s) for ${runId}${taskId ? `/${taskId}` : ""}.` }],
332
+ isError: true,
333
+ details: {},
334
+ };
335
+ return {
336
+ content: [
337
+ {
338
+ type: "text",
339
+ text: `Steering message queued for ${runId}${taskId ? `/${taskId}` : " (all running tasks)"}.`,
340
+ },
341
+ ],
342
+ details: {},
343
+ };
344
+ },
345
+ });
346
+
312
347
  pi.registerTool<typeof RunIdParam, { aborted?: number }>({
313
348
  name: "subagent_cancel",
314
349
  label: "Subagent Cancel",
package/src/manager.ts CHANGED
@@ -193,7 +193,10 @@ export class SubagentManager {
193
193
  private runs = new Map<string, RunSnapshot>();
194
194
  private settlers = new Map<string, (run: RunSnapshot) => void>();
195
195
  private pendingReplies = new Map<string, PendingReply>();
196
- private liveChildren = new Map<string, { abort: () => void; dispose: () => void; touchWatchdog: () => void }>();
196
+ private liveChildren = new Map<
197
+ string,
198
+ { abort: () => void; dispose: () => void; touchWatchdog: () => void; steer: (message: string) => void }
199
+ >();
197
200
  private mailboxes: Mailbox = createMailbox();
198
201
  private runControllers = new Map<string, AbortController>();
199
202
  private widgetTimers = new Map<string, ReturnType<typeof setTimeout>>(); // per-run stream throttle
@@ -217,7 +220,9 @@ export class SubagentManager {
217
220
  /** Flip the background-by-default flag; persists to the agent dir. Returns the new value. */
218
221
  setAutoBg(on: boolean): boolean {
219
222
  this.autoBg = on;
220
- void writeFile(join(getAgentDir(), "subagents-config.json"), JSON.stringify({ autoBg: on }, null, 2)).catch(() => {});
223
+ void writeFile(join(getAgentDir(), "subagents-config.json"), JSON.stringify({ autoBg: on }, null, 2)).catch(
224
+ () => {},
225
+ );
221
226
  return on;
222
227
  }
223
228
 
@@ -719,6 +724,13 @@ export class SubagentManager {
719
724
  abort: () => void child?.abort(),
720
725
  dispose: () => watchdog.dispose(),
721
726
  touchWatchdog: () => watchdog.touch(),
727
+ // Inject a steering message mid-run; queues as steer if the child is streaming.
728
+ steer: (message) =>
729
+ void child?.prompt(message, { streamingBehavior: "steer" }).catch((err) =>
730
+ this.pi.sendUserMessage(`[steer_subagent] ${err instanceof Error ? err.message : String(err)}`, {
731
+ deliverAs: "followUp",
732
+ }),
733
+ ),
722
734
  });
723
735
 
724
736
  const maxRuntimeMs = input.maxRuntimeMs ?? DEFAULT_RUNTIME_MS;
@@ -982,6 +994,16 @@ export class SubagentManager {
982
994
  return { run: cloneRun(run), background: true };
983
995
  }
984
996
 
997
+ /** Push a steering message into a live child's session. Returns false when unknown or not running. */
998
+ steerTask(runId: string, taskId: string | undefined, message: string): boolean {
999
+ const run = this.runs.get(runId);
1000
+ if (!run) return false;
1001
+ const ids = taskId ? [taskId] : run.tasks.map((t) => t.id).filter((id) => this.liveChildren.has(`${runId}:${id}`));
1002
+ if (ids.length === 0) return false;
1003
+ for (const id of ids) this.liveChildren.get(`${runId}:${id}`)?.steer(message);
1004
+ return true;
1005
+ }
1006
+
985
1007
  /** Abort ONE task; siblings keep running. Returns false when unknown or already finished. */
986
1008
  cancelTask(runId: string, taskId: string, ctx?: ExtensionContext): boolean {
987
1009
  const run = this.runs.get(runId);
package/src/schemas.ts CHANGED
@@ -91,3 +91,8 @@ export const ReplyParam = Type.Object({
91
91
  taskId: Type.String(),
92
92
  message: Type.String({ description: "Answer for the child" }),
93
93
  });
94
+ export const SteerParam = Type.Object({
95
+ runId: Type.String(),
96
+ taskId: Type.Optional(Type.String({ description: "Specific task id; defaults to all still-running tasks" })),
97
+ message: Type.String({ description: "Steering message to inject into the child's session" }),
98
+ });