pi-subagents 0.34.0 → 0.35.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/CHANGELOG.md +78 -9
- package/README.md +213 -32
- package/index.ts +1 -0
- package/install.mjs +1 -1
- package/package.json +23 -8
- package/prompts/review-loop.md +3 -1
- package/skills/pi-subagents/SKILL.md +87 -25
- package/src/agents/agent-management.ts +82 -15
- package/src/agents/agent-serializer.ts +19 -0
- package/src/agents/agents.ts +91 -49
- package/src/agents/frontmatter.ts +67 -13
- package/src/agents/skills.ts +25 -12
- package/src/api/background-work.ts +197 -0
- package/src/api/delegation.ts +158 -0
- package/src/extension/chain-validation.ts +165 -0
- package/src/extension/doctor.ts +15 -0
- package/src/extension/fanout-child.ts +3 -1
- package/src/extension/index.ts +65 -124
- package/src/extension/rpc.ts +10 -2
- package/src/extension/schemas.ts +18 -14
- package/src/extension/steering-notices.ts +35 -0
- package/src/extension/tool-description.ts +20 -9
- package/src/intercom/intercom-bridge.ts +3 -2
- package/src/intercom/native-supervisor-channel.ts +9 -1
- package/src/intercom/result-intercom.ts +4 -0
- package/src/runs/background/async-execution.ts +293 -44
- package/src/runs/background/async-job-tracker.ts +56 -9
- package/src/runs/background/async-resume.ts +159 -52
- package/src/runs/background/async-status.ts +25 -18
- package/src/runs/background/auto-drain.ts +67 -0
- package/src/runs/background/chain-root-attachment.ts +16 -8
- package/src/runs/background/control-channel.ts +260 -13
- package/src/runs/background/fleet-view.ts +23 -2
- package/src/runs/background/notify.ts +79 -10
- package/src/runs/background/result-watcher.ts +12 -9
- package/src/runs/background/run-id-resolver.ts +14 -2
- package/src/runs/background/run-status.ts +23 -15
- package/src/runs/background/scheduled-runs.ts +3 -0
- package/src/runs/background/stale-run-reconciler.ts +32 -10
- package/src/runs/background/steering.ts +237 -0
- package/src/runs/background/subagent-runner.ts +898 -236
- package/src/runs/background/subagent-wait.ts +484 -0
- package/src/runs/background/top-level-async.ts +2 -1
- package/src/runs/background/wait-config.ts +36 -0
- package/src/runs/background/wait-tool.ts +26 -0
- package/src/runs/foreground/async-steering-action.ts +230 -0
- package/src/runs/foreground/chain-clarify.ts +22 -6
- package/src/runs/foreground/chain-execution.ts +50 -32
- package/src/runs/foreground/execution.ts +308 -94
- package/src/runs/foreground/subagent-executor.ts +592 -268
- package/src/runs/shared/acceptance.ts +355 -97
- package/src/runs/shared/child-protocol.ts +121 -0
- package/src/runs/shared/completion-guard.ts +8 -127
- package/src/runs/shared/dynamic-fanout.ts +6 -4
- package/src/runs/shared/model-fallback.ts +36 -0
- package/src/runs/shared/nested-events.ts +9 -4
- package/src/runs/shared/nested-render.ts +4 -1
- package/src/runs/shared/parallel-utils.ts +7 -0
- package/src/runs/shared/pi-args.ts +34 -7
- package/src/runs/shared/pi-spawn.ts +18 -12
- package/src/runs/shared/session-lease.ts +279 -0
- package/src/runs/shared/single-output.ts +61 -6
- package/src/runs/shared/spawn-budget.ts +128 -0
- package/src/runs/shared/subagent-control.ts +10 -6
- package/src/runs/shared/subagent-prompt-runtime.ts +127 -26
- package/src/runs/shared/task-intent.ts +176 -0
- package/src/runs/shared/tool-availability.ts +65 -0
- package/src/runs/shared/turn-budget.ts +49 -4
- package/src/shared/atomic-json.ts +4 -1
- package/src/shared/fork-context.ts +28 -3
- package/src/shared/model-info.ts +7 -4
- package/src/shared/status-format.ts +7 -1
- package/src/shared/types.ts +203 -25
- package/src/shared/utils.ts +35 -7
- package/src/slash/delegation-adapters.ts +457 -0
- package/src/slash/delegation-request.ts +103 -0
- package/src/slash/prompt-template-bridge.ts +167 -344
- package/src/slash/slash-commands.ts +239 -6
- package/src/slash/subagents-admin.ts +428 -0
- package/src/slash/subagents-editor.ts +86 -0
- package/src/tui/fleet.ts +405 -0
- package/src/tui/render.ts +90 -16
- package/src/watchdog/change-signature.ts +127 -0
- package/src/watchdog/child-status.ts +205 -0
- package/src/watchdog/emission-guard.ts +123 -0
- package/src/watchdog/lsp-diagnostics.ts +532 -0
- package/src/watchdog/model-selection.ts +167 -0
- package/src/watchdog/register-child.ts +117 -0
- package/src/watchdog/register-main.ts +433 -0
- package/src/watchdog/render.ts +54 -0
- package/src/watchdog/review.ts +293 -0
- package/src/watchdog/runtime.ts +712 -0
- package/src/watchdog/settings.ts +528 -0
- package/src/watchdog/tool-actions.ts +155 -0
- package/src/watchdog/turn-delta.ts +161 -0
- package/src/watchdog/types.ts +188 -0
- package/src/watchdog/warning-format.ts +73 -0
- package/src/runs/background/wait.ts +0 -394
package/src/extension/schemas.ts
CHANGED
|
@@ -67,11 +67,11 @@ const JsonSchemaObject = Type.Unsafe({
|
|
|
67
67
|
|
|
68
68
|
const AcceptanceOverride = Type.Unsafe({
|
|
69
69
|
anyOf: [
|
|
70
|
-
{ type: "string", enum: ["auto", "
|
|
70
|
+
{ type: "string", enum: ["auto", "attested", "checked", "verified", "reviewed"] },
|
|
71
71
|
{ type: "boolean", enum: [false] },
|
|
72
72
|
{ type: "object", additionalProperties: true },
|
|
73
73
|
],
|
|
74
|
-
description: "Optional acceptance policy. Omitted means auto-inferred; verified requires configured runtime commands.",
|
|
74
|
+
description: "Optional acceptance policy. Omitted means auto-inferred; verified requires configured runtime commands. Reviewed is inferred-only because explicit runs cannot supply an independent reviewer result. Bare \"none\" requires { level: \"none\", reason: \"...\" }, while false is deprecated.",
|
|
75
75
|
});
|
|
76
76
|
|
|
77
77
|
const TurnBudgetOverride = Type.Object({
|
|
@@ -108,7 +108,7 @@ const TaskItem = Type.Object({
|
|
|
108
108
|
});
|
|
109
109
|
|
|
110
110
|
// Parallel task item (within a parallel step)
|
|
111
|
-
const ParallelTaskSchema = Type.Object({
|
|
111
|
+
export const ParallelTaskSchema = Type.Object({
|
|
112
112
|
agent: Type.String(),
|
|
113
113
|
task: Type.Optional(Type.String({ description: "Task template with {task}, {previous}, {chain_dir} variables. Defaults to {previous}." })),
|
|
114
114
|
phase: Type.Optional(Type.String({ description: "Optional phase/group label for status and graph rendering." })),
|
|
@@ -127,7 +127,7 @@ const ParallelTaskSchema = Type.Object({
|
|
|
127
127
|
acceptance: Type.Optional(AcceptanceOverride),
|
|
128
128
|
});
|
|
129
129
|
|
|
130
|
-
const DynamicExpandSchema = Type.Object({
|
|
130
|
+
export const DynamicExpandSchema = Type.Object({
|
|
131
131
|
from: Type.Object({
|
|
132
132
|
output: Type.String({ description: "Prior named structured output to expand from." }),
|
|
133
133
|
path: Type.String({ description: "JSON Pointer into the structured output, e.g. /items." }),
|
|
@@ -138,7 +138,7 @@ const DynamicExpandSchema = Type.Object({
|
|
|
138
138
|
onEmpty: Type.Optional(Type.String({ enum: ["skip", "fail"], description: "Empty input behavior. Defaults to skip." })),
|
|
139
139
|
}, { additionalProperties: false });
|
|
140
140
|
|
|
141
|
-
const DynamicParallelTemplateSchema = Type.Object({
|
|
141
|
+
export const DynamicParallelTemplateSchema = Type.Object({
|
|
142
142
|
agent: Type.String(),
|
|
143
143
|
task: Type.Optional(Type.String({ description: "Task template with {item}, {item.path}, {task}, {previous}, {chain_dir}, and {outputs.name} variables." })),
|
|
144
144
|
phase: Type.Optional(Type.String({ description: "Optional phase/group label for status and graph rendering." })),
|
|
@@ -155,13 +155,13 @@ const DynamicParallelTemplateSchema = Type.Object({
|
|
|
155
155
|
acceptance: Type.Optional(AcceptanceOverride),
|
|
156
156
|
}, { additionalProperties: false });
|
|
157
157
|
|
|
158
|
-
const DynamicCollectSchema = Type.Object({
|
|
158
|
+
export const DynamicCollectSchema = Type.Object({
|
|
159
159
|
as: Type.String({ description: "Safe output name for the ordered collected result array." }),
|
|
160
160
|
outputSchema: Type.Optional(JsonSchemaObject),
|
|
161
161
|
}, { additionalProperties: false });
|
|
162
162
|
|
|
163
163
|
// Flattened so chain steps do not need an object-shape anyOf/oneOf union.
|
|
164
|
-
const ChainItem = Type.Object({
|
|
164
|
+
export const ChainItem = Type.Object({
|
|
165
165
|
agent: Type.Optional(Type.String({ description: "Sequential step agent name" })),
|
|
166
166
|
task: Type.Optional(Type.String({
|
|
167
167
|
description: "Task template with variables: {task}=original request, {previous}=prior step's text response, {chain_dir}=shared folder, {outputs.name}=prior named output. Required for first step, defaults to '{previous}' for subsequent steps."
|
|
@@ -221,13 +221,13 @@ const SubagentParamsSchema = Type.Object({
|
|
|
221
221
|
description: "Management/control action only. Must be omitted for execution mode (single, parallel, or chain)."
|
|
222
222
|
})),
|
|
223
223
|
id: Type.Optional(Type.String({
|
|
224
|
-
description: "Run id or prefix for action='status', action='interrupt', action='resume', action='steer', or action='append-step'."
|
|
224
|
+
description: "Run id or prefix for action='status', action='interrupt', action='stop', action='resume', action='steer', or action='append-step'."
|
|
225
225
|
})),
|
|
226
226
|
runId: Type.Optional(Type.String({
|
|
227
|
-
description: "Target run ID for action='interrupt', action='resume', action='steer', or action='append-step'.
|
|
227
|
+
description: "Target run ID for action='interrupt', action='stop', action='resume', action='steer', or action='append-step'. Prefer id for new calls."
|
|
228
228
|
})),
|
|
229
229
|
dir: Type.Optional(Type.String({
|
|
230
|
-
description: "Async run directory for action='status', action='resume', or action='steer'."
|
|
230
|
+
description: "Async run directory for action='status', action='stop', action='resume', or action='steer'."
|
|
231
231
|
})),
|
|
232
232
|
index: Type.Optional(Type.Integer({ minimum: 0, description: "Zero-based child index for actions that target a specific child or transcript." })),
|
|
233
233
|
view: Type.Optional(Type.String({
|
|
@@ -235,7 +235,11 @@ const SubagentParamsSchema = Type.Object({
|
|
|
235
235
|
description: "Optional status view. Use view='fleet' for a read-only active foreground/async fleet surface, or view='transcript' with id/dir (and optional index) to tail a run transcript.",
|
|
236
236
|
})),
|
|
237
237
|
lines: Type.Optional(Type.Integer({ minimum: 1, maximum: 500, description: "Maximum transcript lines for action='status', view='transcript'. Defaults to 80." })),
|
|
238
|
-
message: Type.Optional(Type.String({ description: "Follow-up message for action='resume' or
|
|
238
|
+
message: Type.Optional(Type.String({ description: "Follow-up message for action='resume' (revive paused, completed, or failed children, or reach a routed nested run) or live async guidance for action='steer'. Stopped runs are non-resumable. Use index to choose a child from multi-child runs." })),
|
|
239
|
+
additional: Type.Optional(Type.Integer({ minimum: 1, description: "Positive launches to add with action='grant-spawn-budget'. Root interactive parent with native user confirmation only; total grants cannot exceed the original configured cap." })),
|
|
240
|
+
scope: Type.Optional(Type.String({ enum: ["session", "user", "project"], description: "Scope for action='watchdog.configure'. Defaults to session to avoid persistent settings writes unless user/project is explicit." })),
|
|
241
|
+
target: Type.Optional(Type.String({ enum: ["main", "children", "child"], description: "Target for action='watchdog.configure'. Defaults to main. Use target='child' with agent for a per-agent child watchdog override." })),
|
|
242
|
+
thinking: Type.Optional(Type.Unsafe({ anyOf: [{ type: "string" }, { type: "boolean", enum: [false] }], description: "Thinking level for action='watchdog.configure' (off/minimal/low/medium/high/xhigh/max, inherit, or false for off)." })),
|
|
239
243
|
schedule: Type.Optional(Type.String({ description: "Explicit one-shot schedule for action='schedule'. Only honored when scheduledRuns.enabled is true. Use '+10m' or a future ISO timestamp with timezone; scheduled runs always launch async with fresh context." })),
|
|
240
244
|
scheduleName: Type.Optional(Type.String({ description: "Optional display name for action='schedule'." })),
|
|
241
245
|
// Chain identifier for management (can't reuse 'chain' — that's the execution array)
|
|
@@ -293,9 +297,9 @@ const SubagentParamsSchema = Type.Object({
|
|
|
293
297
|
|
|
294
298
|
export const SubagentParams = keepTopLevelParameterDescriptions(SubagentParamsSchema);
|
|
295
299
|
|
|
296
|
-
const
|
|
300
|
+
const SubagentWaitParamsSchema = Type.Object({
|
|
297
301
|
id: Type.Optional(Type.String({
|
|
298
|
-
description: "
|
|
302
|
+
description: "Async run or remembered detached foreground run id/prefix to wait for one specific run. Omit to wait across every active async run started in this session.",
|
|
299
303
|
})),
|
|
300
304
|
all: Type.Optional(Type.Boolean({
|
|
301
305
|
description: "Wait for ALL active runs to finish. Default false: return as soon as the first run finishes, so a fleet manager can spawn a replacement and wait again. Ignored when id targets a single run.",
|
|
@@ -306,4 +310,4 @@ const WaitParamsSchema = Type.Object({
|
|
|
306
310
|
})),
|
|
307
311
|
});
|
|
308
312
|
|
|
309
|
-
export const
|
|
313
|
+
export const SubagentWaitParams = keepTopLevelParameterDescriptions(SubagentWaitParamsSchema);
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { SteeringNotice, SubagentState } from "../shared/types.ts";
|
|
3
|
+
|
|
4
|
+
export const SUBAGENT_STEERING_MESSAGE_TYPE = "subagent_steering_notice";
|
|
5
|
+
|
|
6
|
+
export interface SubagentSteeringMessageDetails extends SteeringNotice {
|
|
7
|
+
source?: "async";
|
|
8
|
+
asyncDir?: string;
|
|
9
|
+
noticeText?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function formatSteeringNotice(details: Pick<SubagentSteeringMessageDetails, "runId" | "requestId" | "state" | "message">): string {
|
|
13
|
+
return [
|
|
14
|
+
`Subagent steering ${details.state}: ${details.runId}`,
|
|
15
|
+
`Request: ${details.requestId}`,
|
|
16
|
+
details.message,
|
|
17
|
+
"Inspect the run status before sending another correction.",
|
|
18
|
+
].join("\n");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function handleSubagentSteeringNotice(input: {
|
|
22
|
+
pi: Pick<ExtensionAPI, "sendMessage">;
|
|
23
|
+
state: SubagentState;
|
|
24
|
+
details: SubagentSteeringMessageDetails;
|
|
25
|
+
}): void {
|
|
26
|
+
if (!input.details || (input.details.state !== "failed" && input.details.state !== "partial" && input.details.state !== "recovered")) return;
|
|
27
|
+
if (!input.state.currentSessionId || input.details.currentSessionId !== input.state.currentSessionId) return;
|
|
28
|
+
const noticeText = input.details.noticeText ?? formatSteeringNotice(input.details);
|
|
29
|
+
input.pi.sendMessage({
|
|
30
|
+
customType: SUBAGENT_STEERING_MESSAGE_TYPE,
|
|
31
|
+
content: noticeText,
|
|
32
|
+
display: true,
|
|
33
|
+
details: { ...input.details, noticeText },
|
|
34
|
+
}, { triggerTurn: true });
|
|
35
|
+
}
|
|
@@ -8,8 +8,8 @@ const CUSTOM_TOOL_DESCRIPTION_MAX_BYTES = 50 * 1024;
|
|
|
8
8
|
|
|
9
9
|
export const SUBAGENT_SAFETY_GUIDANCE = `SAFETY-CRITICAL SUBAGENT GUIDANCE:
|
|
10
10
|
• Use { action: "list" } before execution and only run executable/non-disabled agents or chains.
|
|
11
|
-
• Keep execution and management separate: omit action for SINGLE/PARALLEL/CHAIN execution; use action only for list/get/models/create/update/delete/status/interrupt/resume/append-step/doctor.
|
|
12
|
-
• Async/background runs: launch with async:true only when work can proceed independently. Do not sleep or poll status just to wait;
|
|
11
|
+
• Keep execution and management separate: omit action for SINGLE/PARALLEL/CHAIN execution; use action only for list/get/models/create/update/delete/status/grant-spawn-budget/interrupt/stop/resume/append-step/doctor.
|
|
12
|
+
• Async/background runs: launch with async:true only when work can proceed independently. Do not sleep or poll status just to wait. In an interactive session, normally return control and let Pi wake you; do not call subagent_wait merely to wait. Override that default and call subagent_wait when the current request is run-to-completion — for example, the user asked you to report results back before continuing or a skill must finish in one turn. Headless sessions auto-drain current-session work at agent_end; use subagent_wait when this turn must receive results before it ends.
|
|
13
13
|
• Child-safety boundary: ordinary child subagents are not orchestrators and must not run subagents. Only explicitly configured fanout children may use the child-safe subagent tool, still bounded by depth/session limits.
|
|
14
14
|
• Writing/review safety: keep one writer for the same cwd/worktree. Use fresh-context read-only reviewers/validators for independent review, then have the parent synthesize and apply fixes as the sole writer unless an isolated worktree was intentionally requested.
|
|
15
15
|
• Artifacts/status essentials: chain outputs live under {chain_dir}; async runs expose asyncId/asyncDir with status.json, events.jsonl, output logs, and status via { action: "status", id }. Include output paths and residual risks when reporting results.`;
|
|
@@ -22,6 +22,7 @@ EXECUTION (use exactly ONE mode):
|
|
|
22
22
|
• CHAIN: { chain: [{agent:"agent-a"}, {parallel:[{agent:"agent-b",count:3}]}] } - sequential pipeline with optional parallel fan-out
|
|
23
23
|
• PARALLEL: { tasks: [{agent,task,count?,output?,reads?,progress?}, ...], concurrency?: number, worktree?: true } - concurrent execution (worktree: isolate each task in a git worktree)
|
|
24
24
|
• Optional context: { context: "fresh" | "fork" } (explicit value overrides every child; when omitted, each requested agent uses its own defaultContext, otherwise "fresh"; inspect agent defaults via { action: "list" })
|
|
25
|
+
• Fork thinking: model strings accept a thinking suffix (provider/model:off|minimal|low|medium|high|xhigh|max). Forking over a parent transcript that carries signed Anthropic thinking blocks forces thinking off only when a child's effective primary or fallback model resolves to the Anthropic provider or anthropic-messages API; unresolved models are treated conservatively. The result notes affected children, including on failures. Use fresh context when an Anthropic child needs thinking.
|
|
25
26
|
• Optional timeout: { timeoutMs } or { maxRuntimeMs } sets a run-level max runtime for foreground and async/background runs
|
|
26
27
|
• If { action: "list" } shows proactive skill subagent suggestions, consider a small fresh-context fanout for broad tasks where one of those skills would materially help
|
|
27
28
|
|
|
@@ -30,19 +31,26 @@ CHAIN TEMPLATE VARIABLES (use in task strings):
|
|
|
30
31
|
• {previous} - Text response from the previous step (empty for first step)
|
|
31
32
|
• {chain_dir} - Shared directory for chain files (e.g., <tmpdir>/pi-subagents-<scope>/chain-runs/abc123/)
|
|
32
33
|
|
|
33
|
-
|
|
34
|
+
CHAIN EXAMPLES (quick reference for the nested schema):
|
|
35
|
+
• Sequential: { chain: [{agent:"agent-a", task:"Analyze {task}"}, {agent:"agent-b", task:"Plan based on {previous}"}] }
|
|
36
|
+
• Parallel fan-out: { chain: [{parallel: [{agent:"agent-a", task:"Check part of {task}", count: 3}]}] }
|
|
37
|
+
• Mixed: { chain: [{agent:"agent-a", task:"Research {task}"}, {parallel: [{agent:"agent-b", task:"Review {previous}", count: 2}]}, {agent:"agent-c", task:"Summarize {previous}"}] }
|
|
34
38
|
|
|
35
39
|
MANAGEMENT (use action field, omit agent/task/chain/tasks):
|
|
36
40
|
• { action: "list" } - discover executable agents/chains
|
|
37
41
|
• { action: "get", agent: "name" } - full detail; packaged agents use dotted runtime names like "package.agent"
|
|
38
42
|
• { action: "models", agent?: "name" } - show the runtime-loaded builtin subagent model mapping, optionally filtered to one builtin
|
|
39
|
-
• { action: "
|
|
43
|
+
• { action: "watchdog.status" | "watchdog.check" | "watchdog.recommend-model" } - inspect the opt-in subagent watchdog and its strong complementary model recommendation
|
|
44
|
+
• { action: "watchdog.configure", model: "recommended" | "inherit" | "provider/model[:thinking]", scope?: "session" | "user" | "project", target?: "main" | "children" | "child", agent?: "name", thinking?: "inherit" | "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max" } - configure watchdog model selection; default scope is session, use persistent scopes only when the user asks
|
|
45
|
+
• { action: "create", config: { name: "custom-agent", package: "code-analysis", systemPrompt, systemPromptMode, inheritProjectContext, inheritSkills, defaultContext, acceptance, acceptanceRole: "read-only" | "writer", ... } }
|
|
46
|
+
• acceptanceRole affects inferred acceptance only, never tool access. Explicit task mutation/no-edit intent wins; omission preserves name heuristics. Update with false or an empty string to clear it.
|
|
40
47
|
• { action: "update", agent: "code-analysis.custom-agent", config: { package: "analysis", ... } } - merge
|
|
41
48
|
• { action: "delete", agent: "code-analysis.custom-agent" }
|
|
42
49
|
• { action: "eject", agent: "reviewer", agentScope?: "user" | "project" } - copy a bundled/package agent to user/project scope as an editable custom file that shadows the original (default scope: user)
|
|
43
50
|
• { action: "disable", agent: "reviewer", agentScope?: "user" | "project" } - hide any agent from runtime discovery via a reversible settings override (default scope: user)
|
|
44
51
|
• { action: "enable", agent: "reviewer", agentScope?: "user" | "project" } - remove a disabled override and restore discovery
|
|
45
52
|
• { action: "reset", agent: "reviewer", agentScope?: "user" | "project" } - delete the scope's custom agent file and/or settings override, restoring the bundled default
|
|
53
|
+
• { action: "grant-spawn-budget", additional: 10 } - add bounded capacity from the root interactive parent after native user confirmation; grants are rejected while children are active and cumulative grants cannot exceed the original configured cap
|
|
46
54
|
• Use chainName for chain operations; packaged chains also use dotted runtime names
|
|
47
55
|
|
|
48
56
|
CONTROL:
|
|
@@ -50,8 +58,9 @@ CONTROL:
|
|
|
50
58
|
• { action: "status", view: "fleet" } - read-only active foreground/async fleet view with transcript commands
|
|
51
59
|
• { action: "status", id: "...", view: "transcript", index?: 0, lines?: 80 } - tail a run or child output/session transcript
|
|
52
60
|
• { action: "interrupt", id?: "..." } - soft-interrupt the current child turn and leave the run paused
|
|
53
|
-
• { action: "
|
|
54
|
-
• { action: "
|
|
61
|
+
• { action: "stop", id: "..." } - stop a current-session top-level async run; stopped runs finish with state "stopped"
|
|
62
|
+
• { action: "resume", id: "...", message: "...", index?: 0 } - revive a paused, completed, or failed async/foreground child from its session; stopped runs are non-resumable; routed nested runs may accept live follow-ups; use steer for a live top-level async child
|
|
63
|
+
• { action: "steer", id: "...", message: "...", index?: 0 } - await correlated child-Pi input acceptance for up to 3 seconds; returns delivered, scheduled, pending, partial, recovered, or failed with a request id. Only top-level single runs may recover after a further 15-second pause/revival bound; chain, parallel, and nested runs never auto-interrupt.
|
|
55
64
|
• { action: "append-step", id: "...", chain: [{agent:"agent-c", task:"Use {previous}"}] } - append one step to the tail of a running async chain
|
|
56
65
|
|
|
57
66
|
SCHEDULE (opt-in; requires { "scheduledRuns": { "enabled": true } } in config.json):
|
|
@@ -72,15 +81,17 @@ EXECUTE:
|
|
|
72
81
|
• SINGLE {agent, task?}; PARALLEL {tasks:[{agent,task,count?,output?,reads?,progress?}], concurrency?, worktree?}; CHAIN {chain:[{agent,task?},{parallel:[...]}]}.
|
|
73
82
|
• context can be "fresh" or "fork"; omitted uses each agent defaultContext, otherwise fresh. timeoutMs/maxRuntimeMs apply to foreground and async/background runs.
|
|
74
83
|
• Chain templates may use {task}, {previous}, {chain_dir}, and named outputs. Parallel worktree isolation requires a clean git repo.
|
|
84
|
+
• Chain example: { chain: [{agent:"agent-a", task:"Analyze {task}"}, {parallel: [{agent:"agent-b", task:"Check {previous}", count: 3}]}] }
|
|
75
85
|
• If list shows proactive skill subagent suggestions, use a small fresh-context fanout only when the task is broad enough.
|
|
76
86
|
|
|
77
87
|
MANAGE / CONTROL:
|
|
78
|
-
• Use action without execution fields: list, get, models, create, update, delete, eject, disable, enable, reset, doctor.
|
|
79
|
-
•
|
|
88
|
+
• Use action without execution fields: list, get, models, create, update, delete, eject, disable, enable, reset, grant-spawn-budget, doctor, watchdog.status, watchdog.check, watchdog.recommend-model, watchdog.configure.
|
|
89
|
+
• Agent acceptanceRole (read-only or writer) affects inferred acceptance only, never tools. Explicit task intent wins; omission keeps name heuristics. Update with false or an empty string to clear it.
|
|
90
|
+
• Async control actions: status, interrupt, stop, resume, steer, append-step. Use stop with an id for current-session top-level async runs. Use status view:"fleet" for active-run overview, view:"transcript" to tail child output, steer for acknowledged top-level live async guidance, and resume for paused/completed/failed revival or a routed nested follow-up. Stopped runs are non-resumable. Steering delivery means Pi accepted the correlated user input, not model compliance; use index for a specific child.
|
|
80
91
|
• Opt-in schedule actions: schedule, schedule-list, schedule-status, schedule-cancel. Schedule only explicit delayed runs the user asked for.
|
|
81
92
|
|
|
82
93
|
ASYNC / WAIT:
|
|
83
|
-
• async:true detaches background work. Do not sleep or poll just to wait
|
|
94
|
+
• async:true detaches background work. Do not sleep or poll just to wait. In an interactive session, normally return control to the user and let Pi wake you on completion; do not call subagent_wait merely to wait. Override that default with subagent_wait only for run-to-completion requests (the user asked for results back this turn, or a skill must finish in one turn). Non-interactive runs (pi -p) auto-drain current-session work at agent_end; call subagent_wait when this turn must receive results before it ends. Otherwise continue useful work or respond.
|
|
84
95
|
• Status and artifacts live under asyncId/asyncDir with status.json, events.jsonl, output logs, session files, and { action:"status", id:"..." }.
|
|
85
96
|
|
|
86
97
|
SAFETY:
|
|
@@ -21,8 +21,9 @@ const DEFAULT_INTERCOM_BRIDGE_TEMPLATE = `The inherited thread is reference-only
|
|
|
21
21
|
|
|
22
22
|
Use contact_supervisor first. It resolves the supervisor session "{orchestratorTarget}" and run metadata automatically.
|
|
23
23
|
- Need a decision, blocked, approval, or product/API/scope ambiguity: contact_supervisor({ reason: "need_decision", message: "<question>" })
|
|
24
|
-
-
|
|
25
|
-
-
|
|
24
|
+
- Need structured supervisor input rather than a freeform reply: contact_supervisor({ reason: "interview_request", message: "<what input is needed>", interview: { title: "...", questions: [] } })
|
|
25
|
+
- After contact_supervisor with reason "need_decision" or "interview_request", stay alive and continue only after the reply arrives. Do not finish your final response with a choose-one question.
|
|
26
|
+
- Do not ask for clarification when the only conflict is review-only/no-edit versus progress-writing or artifact-writing instructions. If an output path is configured but no write-capable tool is available, return the complete artifact in your final response; the runtime will persist it. Do not contact the supervisor merely because you cannot write that output path directly.
|
|
26
27
|
- Meaningful progress or unexpected discoveries that change the plan: contact_supervisor({ reason: "progress_update", message: "UPDATE: <summary>" })
|
|
27
28
|
- Generic intercom is lower-level plumbing/fallback only: intercom({ action: "ask", to: "{orchestratorTarget}", message: "<question>" })
|
|
28
29
|
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
SUBAGENT_RUN_ID_ENV,
|
|
13
13
|
SUBAGENT_SUPERVISOR_CHANNEL_DIR_ENV,
|
|
14
14
|
} from "../runs/shared/pi-args.ts";
|
|
15
|
-
import { POLL_INTERVAL_MS, TEMP_ROOT_DIR, type SubagentState } from "../shared/types.ts";
|
|
15
|
+
import { INTERCOM_DETACH_REQUEST_EVENT, POLL_INTERVAL_MS, TEMP_ROOT_DIR, type IntercomEventBus, type SubagentState } from "../shared/types.ts";
|
|
16
16
|
import { writeAtomicJson } from "../shared/atomic-json.ts";
|
|
17
17
|
|
|
18
18
|
const SUPERVISOR_CHANNEL_ROOT = path.join(TEMP_ROOT_DIR, "supervisor-channels");
|
|
@@ -646,6 +646,14 @@ export function createNativeSupervisorChannel(pi: ExtensionAPI, state: SubagentS
|
|
|
646
646
|
childIndex: request.childIndex,
|
|
647
647
|
},
|
|
648
648
|
});
|
|
649
|
+
if (request.expectsReply) {
|
|
650
|
+
(pi as { events?: IntercomEventBus }).events?.emit(INTERCOM_DETACH_REQUEST_EVENT, {
|
|
651
|
+
requestId: request.id,
|
|
652
|
+
runId: request.runId,
|
|
653
|
+
agent: request.agent,
|
|
654
|
+
childIndex: request.childIndex,
|
|
655
|
+
});
|
|
656
|
+
}
|
|
649
657
|
}
|
|
650
658
|
};
|
|
651
659
|
|
|
@@ -22,6 +22,7 @@ export function resolveSubagentResultStatus(input: {
|
|
|
22
22
|
detached?: boolean;
|
|
23
23
|
}): SubagentResultStatus {
|
|
24
24
|
if (input.detached) return "detached";
|
|
25
|
+
if (input.state === "stopped") return "stopped";
|
|
25
26
|
if (input.interrupted || input.state === "paused") return "paused";
|
|
26
27
|
if (typeof input.success === "boolean") return input.success ? "completed" : "failed";
|
|
27
28
|
if (input.state === "complete") return "completed";
|
|
@@ -35,6 +36,7 @@ function countStatuses(children: SubagentResultIntercomChild[]): Record<Subagent
|
|
|
35
36
|
completed: 0,
|
|
36
37
|
failed: 0,
|
|
37
38
|
paused: 0,
|
|
39
|
+
stopped: 0,
|
|
38
40
|
detached: 0,
|
|
39
41
|
};
|
|
40
42
|
for (const child of children) {
|
|
@@ -47,6 +49,7 @@ function formatStatusCounts(counts: Record<SubagentResultStatus, number>): strin
|
|
|
47
49
|
const parts = [
|
|
48
50
|
counts.completed ? `${counts.completed} completed` : undefined,
|
|
49
51
|
counts.failed ? `${counts.failed} failed` : undefined,
|
|
52
|
+
counts.stopped ? `${counts.stopped} stopped` : undefined,
|
|
50
53
|
counts.paused ? `${counts.paused} paused` : undefined,
|
|
51
54
|
counts.detached ? `${counts.detached} detached` : undefined,
|
|
52
55
|
].filter((part): part is string => Boolean(part));
|
|
@@ -56,6 +59,7 @@ function formatStatusCounts(counts: Record<SubagentResultStatus, number>): strin
|
|
|
56
59
|
function resolveGroupedStatus(children: SubagentResultIntercomChild[]): SubagentResultStatus {
|
|
57
60
|
const counts = countStatuses(children);
|
|
58
61
|
if (counts.failed > 0) return "failed";
|
|
62
|
+
if (counts.stopped > 0) return "stopped";
|
|
59
63
|
if (counts.paused > 0) return "paused";
|
|
60
64
|
if (counts.completed > 0) return "completed";
|
|
61
65
|
if (counts.detached > 0) return "detached";
|