@arhen/pi-core-subagent 1.3.58 → 1.3.59
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 +3 -1
- package/package.json +1 -1
- package/src/child.ts +12 -4
- package/src/format.ts +11 -0
- package/src/manager.ts +13 -10
package/README.md
CHANGED
|
@@ -305,13 +305,15 @@ Background (default) + intercom — the run returns a runId immediately; you sta
|
|
|
305
305
|
|
|
306
306
|
| Tool | Meaning |
|
|
307
307
|
|---|---|
|
|
308
|
-
| `ask_parent` | blocking question to the leader; parent answers via `reply_subagent` |
|
|
308
|
+
| `ask_parent` | blocking question to the leader; delivered mid-turn as a **steering** message labelled `[URGENT]` or `[not urgent]`, parent answers via `reply_subagent` |
|
|
309
309
|
| `notify_parent` | one-way message to the leader |
|
|
310
310
|
| `send_agent_message` | message to a sibling subagent's mailbox (`to` = its task id, or `"leader"`) |
|
|
311
311
|
| `poll_agent_messages` | drain this subagent's mailbox |
|
|
312
312
|
|
|
313
313
|
> **Intercom anti-deadlock:** children are told to never block indefinitely on intercom replies — an unanswered `ask_parent` times out after 10 minutes (the child is told to proceed with best judgment), and sibling polls are capped (~5 tries) with the same fallback. Gated siblings (later waves) may not be running yet — waiting on them is the top stall cause, so children are instructed not to.
|
|
314
314
|
|
|
315
|
+
> **Ask urgency:** `ask_parent` takes `urgent` (default `false`). Both variants steer into the leader's current turn so the question is never deferred to the end of a long turn. `[URGENT]` tells the leader to answer before its next step; `[not urgent]` tells it that the child keeps waiting, so it may finish its current step first. Failures steer for the same reason; completions and aborts queue as follow-ups.
|
|
316
|
+
|
|
315
317
|
## Commands
|
|
316
318
|
|
|
317
319
|
- `/subagents` — list runs; `/subagents peek` (or `ctrl+shift+a`) — browsable pane
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arhen/pi-core-subagent",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.59",
|
|
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
|
@@ -6,7 +6,7 @@ import type { MailboxMessage } from "./mailbox.ts";
|
|
|
6
6
|
export const CHILD_TALK_TOOLS = ["ask_parent", "notify_parent", "send_agent_message", "poll_agent_messages"] as const;
|
|
7
7
|
|
|
8
8
|
export interface ChildHandlers {
|
|
9
|
-
onAskParent(taskId: string, question: string): Promise<string>;
|
|
9
|
+
onAskParent(taskId: string, question: string, urgent: boolean): Promise<string>;
|
|
10
10
|
onNotifyParent(taskId: string, message: string, level: "info" | "warning" | "error"): void;
|
|
11
11
|
onSendMessage(taskId: string, to: string, text: string): boolean;
|
|
12
12
|
onPollMailbox(taskId: string): MailboxMessage[];
|
|
@@ -18,18 +18,26 @@ export function createChildTools(taskId: string, handlers: ChildHandlers): ToolD
|
|
|
18
18
|
name: "ask_parent",
|
|
19
19
|
label: "Ask Parent",
|
|
20
20
|
description:
|
|
21
|
-
"Ask the parent agent a clarifying question and BLOCK until it replies (10 min cap — then proceed with best judgment). Use sparingly — only when you truly cannot proceed without information only the parent has. Prefer figuring it out yourself.",
|
|
21
|
+
"Ask the parent agent a clarifying question and BLOCK until it replies (10 min cap — then proceed with best judgment). Use sparingly — only when you truly cannot proceed without information only the parent has. Prefer figuring it out yourself. The parent sees the question mid-turn: set urgent when you cannot continue until it answers, leave it unset when the parent may finish its current step first.",
|
|
22
22
|
promptSnippet: "Ask the parent agent a question when truly blocked.",
|
|
23
23
|
promptGuidelines: [
|
|
24
24
|
"Use ask_parent only as a last resort when blocked on information only the parent has.",
|
|
25
25
|
"Ask one focused question at a time. The parent's reply resumes your work.",
|
|
26
|
+
"Set urgent: true only when you cannot keep working while waiting; otherwise the parent is told it may answer after its current step.",
|
|
26
27
|
],
|
|
27
28
|
parameters: Type.Object({
|
|
28
29
|
question: Type.String({ description: "A single, focused question for the parent agent" }),
|
|
30
|
+
urgent: Type.Optional(
|
|
31
|
+
Type.Boolean({
|
|
32
|
+
description:
|
|
33
|
+
"True when nothing else can proceed until the parent answers — it is told to stop and reply now",
|
|
34
|
+
default: false,
|
|
35
|
+
}),
|
|
36
|
+
),
|
|
29
37
|
}),
|
|
30
38
|
async execute(_toolCallId, params) {
|
|
31
|
-
const { question } = params as { question: string };
|
|
32
|
-
const answer = await handlers.onAskParent(taskId, question);
|
|
39
|
+
const { question, urgent } = params as { question: string; urgent?: boolean };
|
|
40
|
+
const answer = await handlers.onAskParent(taskId, question, urgent === true);
|
|
33
41
|
return { content: [{ type: "text" as const, text: answer || "(parent gave no answer)" }], details: {} };
|
|
34
42
|
},
|
|
35
43
|
},
|
package/src/format.ts
CHANGED
|
@@ -250,6 +250,17 @@ export function makeTaskNotice(run: RunSnapshot, task: TaskSnapshot, kind: strin
|
|
|
250
250
|
: `Session file kept — resume_subagent(runId: "${run.id}", taskId: "${task.id}", model?: ...) revives it with full context. subagent_result for what it produced so far.`,
|
|
251
251
|
].join("\n");
|
|
252
252
|
}
|
|
253
|
+
export function makeAskNotice(
|
|
254
|
+
run: RunSnapshot,
|
|
255
|
+
extra: { taskId?: string; agent?: string; question?: string; urgent?: boolean },
|
|
256
|
+
): string {
|
|
257
|
+
const who = extra.agent ? `${extra.agent} (${extra.taskId ?? "task"})` : (extra.taskId ?? "a subagent");
|
|
258
|
+
const reply = `reply_subagent(runId: "${run.id}", taskId: "${extra.taskId ?? ""}", message: ...)`;
|
|
259
|
+
return extra.urgent
|
|
260
|
+
? `[URGENT] Subagent ${who} is blocked and cannot continue until you answer: ${extra.question ?? ""}\nAnswer now, before your next step, with ${reply}.`
|
|
261
|
+
: `[not urgent] Subagent ${who} asks: ${extra.question ?? ""}\nIt waits while you keep working — finish your current step first if you want, then answer with ${reply}.`;
|
|
262
|
+
}
|
|
263
|
+
|
|
253
264
|
export function makeNotice(run: RunSnapshot, kind: string): string {
|
|
254
265
|
const lines = [
|
|
255
266
|
`Background subagent run ${run.id} ${kind}: ${run.tasks.filter((t) => t.status === "completed").length}/${run.tasks.length} succeeded.`,
|
package/src/manager.ts
CHANGED
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
getFirstText,
|
|
30
30
|
isStartupFailure,
|
|
31
31
|
isTalking,
|
|
32
|
+
makeAskNotice,
|
|
32
33
|
makeNotice,
|
|
33
34
|
makeTaskNotice,
|
|
34
35
|
SubagentsWidget,
|
|
@@ -494,19 +495,21 @@ export class SubagentManager {
|
|
|
494
495
|
private notifyParent(
|
|
495
496
|
run: RunSnapshot,
|
|
496
497
|
kind: "completed" | "failed" | "aborted" | "asked",
|
|
497
|
-
extra?: { taskId?: string; question?: string },
|
|
498
|
+
extra?: { taskId?: string; agent?: string; question?: string; urgent?: boolean },
|
|
498
499
|
): void {
|
|
499
500
|
if (kind !== "asked" && run.awaited) return;
|
|
500
501
|
// single-task completed run: the task notice already said everything (failure paths may not have notified per-task)
|
|
501
502
|
if (kind === "completed" && run.tasks.length === 1 && run.notifyPerTask) return;
|
|
502
|
-
const body =
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
503
|
+
const body = kind === "asked" ? makeAskNotice(run, extra ?? {}) : makeNotice(run, kind);
|
|
504
|
+
// asks steer: the leader sees the question during its turn and the urgent flag tells it whether to
|
|
505
|
+
// answer now or after the current step. Failures steer for the same reason — a broken result must
|
|
506
|
+
// not be consumed. Completions and aborts queue as follow-ups.
|
|
506
507
|
try {
|
|
507
|
-
this.pi.sendUserMessage(body, {
|
|
508
|
+
this.pi.sendUserMessage(body, {
|
|
509
|
+
deliverAs: kind === "completed" || kind === "aborted" ? "followUp" : "steer",
|
|
510
|
+
});
|
|
508
511
|
} catch {}
|
|
509
|
-
this.emit("subagent:notification", { runId: run.id, kind, body });
|
|
512
|
+
this.emit("subagent:notification", { runId: run.id, taskId: extra?.taskId, kind, body });
|
|
510
513
|
}
|
|
511
514
|
|
|
512
515
|
private widgetTui: TUI | null = null;
|
|
@@ -614,14 +617,14 @@ export class SubagentManager {
|
|
|
614
617
|
|
|
615
618
|
private makeChildHandlers(run: RunSnapshot, task: TaskSnapshot, ctx: ExtensionContext): ChildHandlers {
|
|
616
619
|
return {
|
|
617
|
-
onAskParent: async (_taskId, question) => {
|
|
620
|
+
onAskParent: async (_taskId, question, urgent) => {
|
|
618
621
|
if (TERMINAL.includes(task.status)) {
|
|
619
622
|
return "(your task has already ended — stop work and return immediately)";
|
|
620
623
|
}
|
|
621
624
|
this.updateTask(run, task, { status: "awaiting_parent" }, ctx);
|
|
622
625
|
|
|
623
626
|
if (!this.collectParked(run.id, { kind: "ask", taskId: task.id, agent: task.agent, text: question })) {
|
|
624
|
-
this.notifyParent(run, "asked", { taskId: task.id, question });
|
|
627
|
+
this.notifyParent(run, "asked", { taskId: task.id, agent: task.agent, question, urgent });
|
|
625
628
|
}
|
|
626
629
|
|
|
627
630
|
const reply = await this.awaitParentReply(run.id, task.id, PARENT_REPLY_TIMEOUT_MS);
|
|
@@ -891,7 +894,7 @@ export class SubagentManager {
|
|
|
891
894
|
const worktreeNote = wt
|
|
892
895
|
? ` You work in an isolated git worktree (branch ${wt.branch})${task.stackedOn ? `, stacked on ${task.stackedOn} (its changes are already in your tree)` : ""}. Never run git commands that switch branches, create branches, or move the worktree (git switch/checkout/branch/worktree). The extension commits your changes when you finish. git status/diff are fine for inspecting your own changes. node_modules is a SHARED symlink to the main checkout: never install, upgrade, or delete dependencies (no npm/bun/yarn/pnpm install, no \`rm -rf node_modules\`) — those writes escape your worktree and damage the user's project. If the task truly needs a dependency change, edit the manifest only and say so in your answer.`
|
|
893
896
|
: "";
|
|
894
|
-
const subagentInstruction = `You are running as a subagent. Your bash tool already executes in the project working directory — never prefix commands with \`cd\`. Do not call subagent/delegation tools unless the parent explicitly asks. Return a concise final answer. You MAY use ask_parent only when truly blocked on information only the parent has; notify_parent for one-way updates; send_agent_message/poll_agent_messages to coordinate with siblings. Your mailbox address and siblings: ${task.roster ?? "(none)"}. Use the exact task ids (e.g. task_2) as send_agent_message targets. Siblings run independently and may start late or finish early — never block indefinitely on their replies: poll at most 5 times, then proceed with your best judgment. A gated sibling (marked ↳ waits in the graph) may not be running yet; do not wait for it. An unanswered ask_parent times out after 10 minutes — proceed with your best judgment then. When your work is done, call notify_parent ONCE with a concise result summary — key findings, verdicts, file:line evidence — so the leader can start consuming your output before the run finishes.${worktreeNote}`;
|
|
897
|
+
const subagentInstruction = `You are running as a subagent. Your bash tool already executes in the project working directory — never prefix commands with \`cd\`. Do not call subagent/delegation tools unless the parent explicitly asks. Return a concise final answer. You MAY use ask_parent only when truly blocked on information only the parent has (set urgent: true only when nothing else can proceed while you wait); notify_parent for one-way updates; send_agent_message/poll_agent_messages to coordinate with siblings. Your mailbox address and siblings: ${task.roster ?? "(none)"}. Use the exact task ids (e.g. task_2) as send_agent_message targets. Siblings run independently and may start late or finish early — never block indefinitely on their replies: poll at most 5 times, then proceed with your best judgment. A gated sibling (marked ↳ waits in the graph) may not be running yet; do not wait for it. An unanswered ask_parent times out after 10 minutes — proceed with your best judgment then. When your work is done, call notify_parent ONCE with a concise result summary — key findings, verdicts, file:line evidence — so the leader can start consuming your output before the run finishes.${worktreeNote}`;
|
|
895
898
|
|
|
896
899
|
const loader = new DefaultResourceLoader({
|
|
897
900
|
cwd: childCwd,
|