pi-subagents 0.32.0 → 0.33.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 +30 -3
- package/README.md +147 -58
- package/install.mjs +2 -1
- package/package.json +3 -2
- package/skills/pi-subagents/SKILL.md +70 -14
- package/src/agents/agent-management.ts +177 -5
- package/src/agents/agent-memory.ts +254 -0
- package/src/agents/agent-serializer.ts +11 -0
- package/src/agents/agents.ts +142 -12
- package/src/agents/chain-serializer.ts +27 -2
- package/src/extension/doctor.ts +1 -9
- package/src/extension/fanout-child.ts +2 -2
- package/src/extension/index.ts +65 -90
- package/src/extension/rpc.ts +369 -0
- package/src/extension/schemas.ts +52 -8
- package/src/extension/tool-description.ts +200 -0
- package/src/intercom/intercom-bridge.ts +21 -253
- package/src/intercom/native-supervisor-channel.ts +510 -0
- package/src/runs/background/async-execution.ts +51 -7
- package/src/runs/background/async-job-tracker.ts +12 -2
- package/src/runs/background/async-status.ts +27 -2
- package/src/runs/background/completion-batcher.ts +166 -0
- package/src/runs/background/control-channel.ts +106 -1
- package/src/runs/background/fleet-view.ts +515 -0
- package/src/runs/background/notify.ts +161 -44
- package/src/runs/background/result-watcher.ts +1 -2
- package/src/runs/background/run-id-resolver.ts +3 -2
- package/src/runs/background/run-status.ts +166 -6
- package/src/runs/background/scheduled-runs.ts +514 -0
- package/src/runs/background/subagent-runner.ts +409 -35
- package/src/runs/background/wait.ts +353 -0
- package/src/runs/foreground/chain-execution.ts +95 -21
- package/src/runs/foreground/execution.ts +150 -21
- package/src/runs/foreground/subagent-executor.ts +378 -64
- package/src/runs/shared/dynamic-fanout.ts +1 -1
- package/src/runs/shared/model-fallback.ts +167 -20
- package/src/runs/shared/model-scope.ts +128 -0
- package/src/runs/shared/nested-events.ts +31 -0
- package/src/runs/shared/parallel-utils.ts +1 -0
- package/src/runs/shared/pi-args.ts +30 -1
- package/src/runs/shared/subagent-prompt-runtime.ts +108 -2
- package/src/runs/shared/tool-budget.ts +74 -0
- package/src/runs/shared/turn-budget.ts +52 -0
- package/src/shared/artifacts.ts +1 -0
- package/src/shared/atomic-json.ts +15 -2
- package/src/shared/child-transcript.ts +212 -0
- package/src/shared/settings.ts +3 -1
- package/src/shared/types.ts +134 -19
- package/src/slash/prompt-workflows.ts +330 -0
- package/src/slash/slash-commands.ts +16 -2
- package/src/tui/render.ts +16 -8
- package/src/extension/companion-suggestions.ts +0 -359
|
@@ -3,6 +3,7 @@ import { buildRuntimeName, frontmatterNameForConfig, parsePackageName } from "./
|
|
|
3
3
|
import { parseFrontmatter } from "./frontmatter.ts";
|
|
4
4
|
import { ChainOutputValidationError, validateChainOutputBindings } from "../runs/shared/chain-outputs.ts";
|
|
5
5
|
import { validateAcceptanceInput } from "../runs/shared/acceptance.ts";
|
|
6
|
+
import { validateToolBudgetConfig } from "../runs/shared/tool-budget.ts";
|
|
6
7
|
import type { ChainStep } from "../shared/settings.ts";
|
|
7
8
|
import type { AgentSource } from "./agents.ts";
|
|
8
9
|
|
|
@@ -78,6 +79,19 @@ function parseStepBody(agent: string, sectionBody: string): ChainStepConfig {
|
|
|
78
79
|
if (key === "progress") {
|
|
79
80
|
if (rawValue === "true") step.progress = true;
|
|
80
81
|
else if (rawValue === "false") step.progress = false;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (key === "toolbudget") {
|
|
85
|
+
let parsed: unknown;
|
|
86
|
+
try {
|
|
87
|
+
parsed = JSON.parse(rawValue);
|
|
88
|
+
} catch (error) {
|
|
89
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
90
|
+
throw new Error(`Invalid toolBudget in .chain.md step '${agent}': ${message}`);
|
|
91
|
+
}
|
|
92
|
+
const validation = validateToolBudgetConfig(parsed, `toolBudget for step '${agent}'`);
|
|
93
|
+
if (validation.error) throw new Error(validation.error);
|
|
94
|
+
step.toolBudget = parsed as ChainStepConfig["toolBudget"];
|
|
81
95
|
}
|
|
82
96
|
}
|
|
83
97
|
|
|
@@ -125,6 +139,11 @@ export function parseChain(content: string, source: AgentSource, filePath: strin
|
|
|
125
139
|
};
|
|
126
140
|
}
|
|
127
141
|
|
|
142
|
+
function validateJsonChainToolBudget(value: unknown, label: string): void {
|
|
143
|
+
const validation = validateToolBudgetConfig(value, label);
|
|
144
|
+
if (validation.error) throw new Error(validation.error);
|
|
145
|
+
}
|
|
146
|
+
|
|
128
147
|
export function parseJsonChain(content: string, source: AgentSource, filePath: string): ChainConfig {
|
|
129
148
|
let parsed: unknown;
|
|
130
149
|
try {
|
|
@@ -152,6 +171,7 @@ export function parseJsonChain(content: string, source: AgentSource, filePath: s
|
|
|
152
171
|
throw new Error(`JSON chain '${filePath}' step ${i + 1} must be an object.`);
|
|
153
172
|
}
|
|
154
173
|
const stepRecord = step as Record<string, unknown>;
|
|
174
|
+
if (stepRecord.toolBudget !== undefined) validateJsonChainToolBudget(stepRecord.toolBudget, `step ${i + 1} toolBudget`);
|
|
155
175
|
const acceptanceErrors = validateAcceptanceInput(stepRecord.acceptance, `step ${i + 1} acceptance`);
|
|
156
176
|
if (acceptanceErrors.length > 0) {
|
|
157
177
|
throw new Error(`Invalid JSON chain '${filePath}': ${acceptanceErrors.join(" ")}`);
|
|
@@ -161,13 +181,17 @@ export function parseJsonChain(content: string, source: AgentSource, filePath: s
|
|
|
161
181
|
for (let taskIndex = 0; taskIndex < parallel.length; taskIndex++) {
|
|
162
182
|
const task = parallel[taskIndex];
|
|
163
183
|
if (!task || typeof task !== "object" || Array.isArray(task)) continue;
|
|
164
|
-
const
|
|
184
|
+
const taskRecord = task as Record<string, unknown>;
|
|
185
|
+
if (taskRecord.toolBudget !== undefined) validateJsonChainToolBudget(taskRecord.toolBudget, `step ${i + 1} parallel task ${taskIndex + 1} toolBudget`);
|
|
186
|
+
const taskErrors = validateAcceptanceInput(taskRecord.acceptance, `step ${i + 1} parallel task ${taskIndex + 1} acceptance`);
|
|
165
187
|
if (taskErrors.length > 0) {
|
|
166
188
|
throw new Error(`Invalid JSON chain '${filePath}': ${taskErrors.join(" ")}`);
|
|
167
189
|
}
|
|
168
190
|
}
|
|
169
191
|
} else if (parallel && typeof parallel === "object") {
|
|
170
|
-
const
|
|
192
|
+
const parallelRecord = parallel as Record<string, unknown>;
|
|
193
|
+
if (parallelRecord.toolBudget !== undefined) validateJsonChainToolBudget(parallelRecord.toolBudget, `step ${i + 1} dynamic template toolBudget`);
|
|
194
|
+
const templateErrors = validateAcceptanceInput(parallelRecord.acceptance, `step ${i + 1} dynamic template acceptance`);
|
|
171
195
|
if (templateErrors.length > 0) {
|
|
172
196
|
throw new Error(`Invalid JSON chain '${filePath}': ${templateErrors.join(" ")}`);
|
|
173
197
|
}
|
|
@@ -243,6 +267,7 @@ export function serializeChain(config: ChainConfig): string {
|
|
|
243
267
|
if (step.skills === false) lines.push("skills: false");
|
|
244
268
|
else if (Array.isArray(step.skills) && step.skills.length > 0) lines.push(`skills: ${step.skills.join(", ")}`);
|
|
245
269
|
if (step.progress !== undefined) lines.push(`progress: ${step.progress ? "true" : "false"}`);
|
|
270
|
+
if (step.toolBudget !== undefined) lines.push(`toolBudget: ${JSON.stringify(step.toolBudget)}`);
|
|
246
271
|
lines.push("");
|
|
247
272
|
lines.push(step.task ?? "");
|
|
248
273
|
if (i < config.steps.length - 1) lines.push("");
|
package/src/extension/doctor.ts
CHANGED
|
@@ -39,7 +39,6 @@ interface DoctorReportInput {
|
|
|
39
39
|
sessionError?: string;
|
|
40
40
|
expandTilde?: (value: string) => string;
|
|
41
41
|
paths?: DoctorPaths;
|
|
42
|
-
companionPackageLines?: string[];
|
|
43
42
|
deps?: Partial<DoctorDeps>;
|
|
44
43
|
}
|
|
45
44
|
|
|
@@ -158,14 +157,8 @@ function formatIntercomDiagnostic(diagnostic: IntercomBridgeDiagnostic, context:
|
|
|
158
157
|
`- bridge: ${diagnostic.active ? "active" : "inactive"}${diagnostic.reason ? ` (${diagnostic.reason})` : ""}`,
|
|
159
158
|
`- mode: ${diagnostic.mode}; context: ${context ?? "unspecified"}`,
|
|
160
159
|
`- orchestrator target: ${diagnostic.orchestratorTarget ?? "not available"}`,
|
|
161
|
-
`-
|
|
160
|
+
`- supervisor channel: ${diagnostic.supervisorChannelAvailable ? "available" : "unavailable"} (${diagnostic.extensionDir})`,
|
|
162
161
|
];
|
|
163
|
-
if (diagnostic.configPath && diagnostic.intercomConfigEnabled !== undefined) {
|
|
164
|
-
lines.push(`- intercom config: ${diagnostic.intercomConfigEnabled === false ? "disabled" : "enabled or absent"} (${diagnostic.configPath})`);
|
|
165
|
-
}
|
|
166
|
-
if (diagnostic.intercomConfigError) {
|
|
167
|
-
lines.push(`- intercom config warning: ${diagnostic.intercomConfigError}; runtime assumes enabled`);
|
|
168
|
-
}
|
|
169
162
|
return lines;
|
|
170
163
|
}
|
|
171
164
|
|
|
@@ -216,7 +209,6 @@ export function buildDoctorReport(input: DoctorReportInput): string {
|
|
|
216
209
|
orchestratorTarget: input.orchestratorTarget,
|
|
217
210
|
cwd: input.cwd,
|
|
218
211
|
}), input.context).join("\n")).split("\n"),
|
|
219
|
-
...(input.companionPackageLines?.length ? ["", ...input.companionPackageLines] : []),
|
|
220
212
|
];
|
|
221
213
|
return lines.join("\n");
|
|
222
214
|
}
|
|
@@ -158,8 +158,8 @@ export default function registerFanoutChildSubagentExtension(pi: ExtensionAPI):
|
|
|
158
158
|
label: "Subagent",
|
|
159
159
|
description: [
|
|
160
160
|
"Delegate to subagents from child-safe fanout mode.",
|
|
161
|
-
"Allowed management/control actions: list, get, status, interrupt, resume, append-step, doctor.",
|
|
162
|
-
"Agent config mutation actions create, update,
|
|
161
|
+
"Allowed management/control actions: list, get, status, interrupt, resume, steer, append-step, doctor.",
|
|
162
|
+
"Agent config mutation actions (create, update, delete, eject, disable, enable, reset) are blocked in this mode.",
|
|
163
163
|
].join("\n"),
|
|
164
164
|
parameters: SubagentParams,
|
|
165
165
|
execute(id, params, signal, onUpdate, ctx) {
|
package/src/extension/index.ts
CHANGED
|
@@ -12,38 +12,36 @@
|
|
|
12
12
|
* { "asyncByDefault": true, "forceTopLevelAsync": true, "maxSubagentDepth": 1, "intercomBridge": { "mode": "always", "instructionFile": "./intercom-bridge.md" }, "worktreeSetupHook": "./scripts/setup-worktree.mjs" }
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
+
import { randomUUID } from "node:crypto";
|
|
15
16
|
import * as fs from "node:fs";
|
|
16
17
|
import * as os from "node:os";
|
|
17
18
|
import * as path from "node:path";
|
|
18
19
|
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
19
|
-
import { type ExtensionAPI, type ExtensionContext, type ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
20
|
+
import { keyText, type ExtensionAPI, type ExtensionContext, type ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
20
21
|
import { Box, Container, Spacer, Text, truncateToWidth, visibleWidth, wrapTextWithAnsi, type Component } from "@earendil-works/pi-tui";
|
|
21
22
|
import { discoverAgents } from "../agents/agents.ts";
|
|
22
23
|
import { cleanupAllArtifactDirs, cleanupOldArtifacts, getArtifactsDir } from "../shared/artifacts.ts";
|
|
23
24
|
import { resolveCurrentSessionId } from "../shared/session-identity.ts";
|
|
24
25
|
import { cleanupOldChainDirs } from "../shared/settings.ts";
|
|
25
26
|
import { clearLegacyResultAnimationTimer, renderWidget, renderSubagentResult } from "../tui/render.ts";
|
|
26
|
-
import { SubagentParams } from "./schemas.ts";
|
|
27
|
+
import { SubagentParams, WaitParams } from "./schemas.ts";
|
|
27
28
|
import { createSubagentExecutor, type SubagentParamsLike } from "../runs/foreground/subagent-executor.ts";
|
|
28
29
|
import { createAsyncJobTracker } from "../runs/background/async-job-tracker.ts";
|
|
29
30
|
import { createResultWatcher } from "../runs/background/result-watcher.ts";
|
|
31
|
+
import { createScheduledRunManager } from "../runs/background/scheduled-runs.ts";
|
|
30
32
|
import { registerSlashCommands } from "../slash/slash-commands.ts";
|
|
31
33
|
import { registerPromptTemplateDelegationBridge } from "../slash/prompt-template-bridge.ts";
|
|
32
34
|
import { registerSlashSubagentBridge } from "../slash/slash-bridge.ts";
|
|
35
|
+
import { createNativeSupervisorChannel } from "../intercom/native-supervisor-channel.ts";
|
|
36
|
+
import { registerSubagentRpcBridge } from "./rpc.ts";
|
|
33
37
|
import { clearSlashSnapshots, getSlashRenderableSnapshot, resolveSlashMessageDetails, restoreSlashFinalSnapshots, type SlashMessageDetails } from "../slash/slash-live-state.ts";
|
|
34
38
|
import { inspectSubagentStatus } from "../runs/background/run-status.ts";
|
|
39
|
+
import { waitForSubagents } from "../runs/background/wait.ts";
|
|
35
40
|
import registerSubagentNotify, { type SubagentNotifyDetails } from "../runs/background/notify.ts";
|
|
36
41
|
import { SUBAGENT_CHILD_ENV, SUBAGENT_PARENT_SESSION_ENV } from "../runs/shared/pi-args.ts";
|
|
37
42
|
import { formatDuration, shortenPath } from "../shared/formatters.ts";
|
|
38
43
|
import { loadConfig } from "./config.ts";
|
|
39
|
-
import {
|
|
40
|
-
buildCompanionDoctorLines,
|
|
41
|
-
buildCompanionListLines,
|
|
42
|
-
collectCompanionStatuses,
|
|
43
|
-
handleCompanionCommand,
|
|
44
|
-
maybeSendCompanionStartupMessage,
|
|
45
|
-
resolveCompanionOrchestratorTarget,
|
|
46
|
-
} from "./companion-suggestions.ts";
|
|
44
|
+
import { buildSubagentToolDescription } from "./tool-description.ts";
|
|
47
45
|
import {
|
|
48
46
|
type Details,
|
|
49
47
|
type SubagentState,
|
|
@@ -258,7 +256,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
258
256
|
ensureAccessibleDir(ASYNC_DIR);
|
|
259
257
|
cleanupOldChainDirs();
|
|
260
258
|
|
|
261
|
-
|
|
259
|
+
const config = loadConfig();
|
|
262
260
|
const asyncByDefault = config.asyncByDefault === true;
|
|
263
261
|
const tempArtifactsDir = getArtifactsDir(null);
|
|
264
262
|
cleanupAllArtifactDirs(DEFAULT_ARTIFACT_CONFIG.cleanupDays);
|
|
@@ -279,14 +277,13 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
279
277
|
completionSeen: new Map(),
|
|
280
278
|
watcher: null,
|
|
281
279
|
watcherRestartTimer: null,
|
|
282
|
-
companionSuggestionStartupShown: false,
|
|
283
|
-
companionSuggestionListShown: false,
|
|
284
280
|
resultFileCoalescer: {
|
|
285
281
|
schedule: () => false,
|
|
286
282
|
clear: () => {},
|
|
287
283
|
},
|
|
288
284
|
};
|
|
289
285
|
|
|
286
|
+
const supervisorChannel = createNativeSupervisorChannel(pi, state);
|
|
290
287
|
const { startResultWatcher, primeExistingResults, stopResultWatcher } = createResultWatcher(
|
|
291
288
|
pi,
|
|
292
289
|
state,
|
|
@@ -298,6 +295,8 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
298
295
|
|
|
299
296
|
const runtimeCleanup = () => {
|
|
300
297
|
stopResultWatcher();
|
|
298
|
+
scheduledRunManager.stop();
|
|
299
|
+
supervisorChannel.dispose();
|
|
301
300
|
clearPendingForegroundControlNotices(state);
|
|
302
301
|
if (state.poller) {
|
|
303
302
|
clearInterval(state.poller);
|
|
@@ -307,20 +306,32 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
307
306
|
globalStore[runtimeCleanupStoreKey] = runtimeCleanup;
|
|
308
307
|
|
|
309
308
|
const { ensurePoller, handleStarted, handleComplete, resetJobs, restoreActiveJobs } = createAsyncJobTracker(pi, state, ASYNC_DIR);
|
|
309
|
+
let executorExecute: ((id: string, params: SubagentParamsLike, signal: AbortSignal, onUpdate: ((r: AgentToolResult<Details>) => void) | undefined, ctx: ExtensionContext) => Promise<AgentToolResult<Details>>) | undefined;
|
|
310
|
+
const scheduledRunManager = createScheduledRunManager({
|
|
311
|
+
config,
|
|
312
|
+
launch: (params, ctx, signal) => {
|
|
313
|
+
if (!executorExecute) {
|
|
314
|
+
return Promise.resolve({
|
|
315
|
+
content: [{ type: "text", text: "Scheduled subagent launch is unavailable (executor not ready)." }],
|
|
316
|
+
isError: true,
|
|
317
|
+
details: { mode: "management" as const, results: [] },
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
return executorExecute(randomUUID(), params, signal, undefined, ctx);
|
|
321
|
+
},
|
|
322
|
+
});
|
|
310
323
|
const executor = createSubagentExecutor({
|
|
311
324
|
pi,
|
|
312
325
|
state,
|
|
313
326
|
config,
|
|
314
327
|
asyncByDefault,
|
|
315
|
-
|
|
316
|
-
const statuses = collectCompanionStatuses({ pi, config, cwd, context, orchestratorTarget });
|
|
317
|
-
return surface === "doctor" ? buildCompanionDoctorLines(statuses) : buildCompanionListLines(statuses);
|
|
318
|
-
},
|
|
328
|
+
handleScheduledRunAction: (params, ctx) => scheduledRunManager.handleToolCall(params, ctx),
|
|
319
329
|
tempArtifactsDir,
|
|
320
330
|
getSubagentSessionRoot,
|
|
321
331
|
expandTilde,
|
|
322
332
|
discoverAgents,
|
|
323
333
|
});
|
|
334
|
+
executorExecute = executor.execute;
|
|
324
335
|
|
|
325
336
|
pi.registerMessageRenderer<SlashMessageDetails>(SLASH_RESULT_TYPE, (message, options, theme) => {
|
|
326
337
|
const details = resolveSlashMessageDetails(message.details);
|
|
@@ -360,7 +371,8 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
360
371
|
text += `\n ${theme.fg("dim", `⎿ ${line}`)}`;
|
|
361
372
|
}
|
|
362
373
|
if (!options.expanded && trimmedPreview.includes("\n")) {
|
|
363
|
-
|
|
374
|
+
const expandKey = keyText("app.tools.expand");
|
|
375
|
+
text += `\n ${theme.fg("dim", `${expandKey} full notification`)}`;
|
|
364
376
|
}
|
|
365
377
|
if (details.sessionLabel && details.sessionValue) {
|
|
366
378
|
text += `\n ${theme.fg("muted", `${details.sessionLabel}: ${shortenPath(details.sessionValue)}`)}`;
|
|
@@ -425,6 +437,12 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
425
437
|
},
|
|
426
438
|
});
|
|
427
439
|
|
|
440
|
+
const rpcBridge = registerSubagentRpcBridge({
|
|
441
|
+
events: pi.events,
|
|
442
|
+
getContext: () => state.lastUiContext,
|
|
443
|
+
execute: (id, params, signal, onUpdate, ctx) => executor.execute(id, params, signal, onUpdate, ctx),
|
|
444
|
+
});
|
|
445
|
+
|
|
428
446
|
function effectiveParallelTaskCount(tasks: Array<{ count?: unknown }> | undefined): number {
|
|
429
447
|
if (!tasks || tasks.length === 0) return 0;
|
|
430
448
|
return tasks.reduce((total, task) => {
|
|
@@ -433,66 +451,10 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
433
451
|
}, 0);
|
|
434
452
|
}
|
|
435
453
|
|
|
436
|
-
pi.registerCommand("subagents-companions", {
|
|
437
|
-
description: "Show or hide pi-subagents companion package recommendations",
|
|
438
|
-
handler: async (args, ctx) => {
|
|
439
|
-
try {
|
|
440
|
-
const statuses = collectCompanionStatuses({
|
|
441
|
-
pi,
|
|
442
|
-
config,
|
|
443
|
-
cwd: ctx.cwd,
|
|
444
|
-
orchestratorTarget: resolveCompanionOrchestratorTarget(pi, ctx),
|
|
445
|
-
});
|
|
446
|
-
const result = handleCompanionCommand(args, ctx, statuses);
|
|
447
|
-
if (result.updatedConfig) config = result.updatedConfig;
|
|
448
|
-
pi.sendMessage({ content: result.text, display: true });
|
|
449
|
-
if (result.error && ctx.hasUI) ctx.ui.notify(result.text, "error");
|
|
450
|
-
} catch (error) {
|
|
451
|
-
const message = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
452
|
-
pi.sendMessage({ content: `Failed to update companion suggestions: ${message}`, display: true });
|
|
453
|
-
if (ctx.hasUI) ctx.ui.notify(`Failed to update companion suggestions: ${message}`, "error");
|
|
454
|
-
}
|
|
455
|
-
},
|
|
456
|
-
});
|
|
457
|
-
|
|
458
454
|
const tool: ToolDefinition<typeof SubagentParams, Details> = {
|
|
459
455
|
name: "subagent",
|
|
460
456
|
label: "Subagent",
|
|
461
|
-
description:
|
|
462
|
-
|
|
463
|
-
EXECUTION (use exactly ONE mode):
|
|
464
|
-
• Before executing, use { action: "list" } to inspect configured agents/chains. Only execute agents listed as executable/non-disabled.
|
|
465
|
-
• SINGLE: { agent, task? } - one task; omit task for self-contained agents
|
|
466
|
-
• CHAIN: { chain: [{agent:"agent-a"}, {parallel:[{agent:"agent-b",count:3}]}] } - sequential pipeline with optional parallel fan-out
|
|
467
|
-
• PARALLEL: { tasks: [{agent,task,count?,output?,reads?,progress?}, ...], concurrency?: number, worktree?: true } - concurrent execution (worktree: isolate each task in a git worktree)
|
|
468
|
-
• 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" })
|
|
469
|
-
• Optional timeout: { timeoutMs } or { maxRuntimeMs } sets a run-level max runtime for foreground and async/background runs
|
|
470
|
-
• 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
|
|
471
|
-
|
|
472
|
-
CHAIN TEMPLATE VARIABLES (use in task strings):
|
|
473
|
-
• {task} - The original task/request from the user
|
|
474
|
-
• {previous} - Text response from the previous step (empty for first step)
|
|
475
|
-
• {chain_dir} - Shared directory for chain files (e.g., <tmpdir>/pi-subagents-<scope>/chain-runs/abc123/)
|
|
476
|
-
|
|
477
|
-
Example: { chain: [{agent:"agent-a", task:"Analyze {task}"}, {agent:"agent-b", task:"Plan based on {previous}"}] }
|
|
478
|
-
|
|
479
|
-
MANAGEMENT (use action field, omit agent/task/chain/tasks):
|
|
480
|
-
• { action: "list" } - discover executable agents/chains
|
|
481
|
-
• { action: "get", agent: "name" } - full detail; packaged agents use dotted runtime names like "package.agent"
|
|
482
|
-
• { action: "models", agent?: "name" } - show the runtime-loaded builtin subagent model mapping, optionally filtered to one builtin
|
|
483
|
-
• { action: "create", config: { name: "custom-agent", package: "code-analysis", systemPrompt, systemPromptMode, inheritProjectContext, inheritSkills, defaultContext, ... } }
|
|
484
|
-
• { action: "update", agent: "code-analysis.custom-agent", config: { package: "analysis", ... } } - merge
|
|
485
|
-
• { action: "delete", agent: "code-analysis.custom-agent" }
|
|
486
|
-
• Use chainName for chain operations; packaged chains also use dotted runtime names
|
|
487
|
-
|
|
488
|
-
CONTROL:
|
|
489
|
-
• { action: "status", id: "..." } - inspect an async/background run by id or prefix
|
|
490
|
-
• { action: "interrupt", id?: "..." } - soft-interrupt the current child turn and leave the run paused
|
|
491
|
-
• { action: "resume", id: "...", message: "...", index?: 0 } - interrupt then follow up with a live async child, or revive a completed async/foreground child from its session
|
|
492
|
-
• { action: "append-step", id: "...", chain: [{agent:"agent-c", task:"Use {previous}"}] } - append one step to the tail of a running async chain
|
|
493
|
-
|
|
494
|
-
DIAGNOSTICS:
|
|
495
|
-
• { action: "doctor" } - read-only report for runtime paths, discovery, sessions, and intercom`,
|
|
457
|
+
description: buildSubagentToolDescription(config),
|
|
496
458
|
parameters: SubagentParams,
|
|
497
459
|
|
|
498
460
|
execute(id, params, signal, onUpdate, ctx) {
|
|
@@ -542,6 +504,27 @@ DIAGNOSTICS:
|
|
|
542
504
|
};
|
|
543
505
|
|
|
544
506
|
pi.registerTool(tool);
|
|
507
|
+
|
|
508
|
+
const waitTool: ToolDefinition<typeof WaitParams, Details> = {
|
|
509
|
+
name: "wait",
|
|
510
|
+
label: "Wait",
|
|
511
|
+
description: `Block until background (async) subagent runs started in this session finish, then return.
|
|
512
|
+
|
|
513
|
+
Use this after launching async subagents when you have no independent work left and must not end your turn — for example inside a skill that has to run to completion, or any non-interactive run (\`pi -p ...\`) where the whole task is a single turn and ending it would abandon the still-running children.
|
|
514
|
+
|
|
515
|
+
• { } — return as soon as the FIRST active run finishes (default). Ideal for a rolling fleet: launch N, wait, spawn a replacement for the one that finished, wait again — keeping N in flight.
|
|
516
|
+
• { all: true } — block until EVERY active run in this session is finished.
|
|
517
|
+
• { id: "..." } — wait for one specific run (id or prefix) to finish.
|
|
518
|
+
• { timeoutMs: 600000 } — stop waiting after N ms (the runs keep going regardless; default 30 min)
|
|
519
|
+
|
|
520
|
+
wait also returns when a run needs attention (a child that went idle or blocked for a decision), not only on completion — so a stuck child never stalls the loop; the summary names the run(s) to inspect/nudge/resume/interrupt. It wakes the instant a completion or control event arrives (subscribed to Pi's event bus, with a poll fallback that reconciles crashed runners), keeps the turn alive for normal notification delivery, and resolves early if the turn is aborted.`,
|
|
521
|
+
parameters: WaitParams,
|
|
522
|
+
execute(_id, params, signal, _onUpdate, _ctx) {
|
|
523
|
+
return waitForSubagents(params, signal, { state, events: pi.events });
|
|
524
|
+
},
|
|
525
|
+
};
|
|
526
|
+
pi.registerTool(waitTool);
|
|
527
|
+
|
|
545
528
|
registerSlashCommands(pi, state);
|
|
546
529
|
|
|
547
530
|
const eventUnsubscribeStoreKey = "__piSubagentEventUnsubscribes";
|
|
@@ -557,7 +540,7 @@ DIAGNOSTICS:
|
|
|
557
540
|
}
|
|
558
541
|
}
|
|
559
542
|
}
|
|
560
|
-
registerSubagentNotify(pi);
|
|
543
|
+
registerSubagentNotify(pi, state, { batchConfig: config.completionBatch });
|
|
561
544
|
|
|
562
545
|
const existingVisibleControlNotices = globalStore[controlNoticeSeenStoreKey];
|
|
563
546
|
const visibleControlNotices = existingVisibleControlNotices instanceof Set ? existingVisibleControlNotices as Set<string> : new Set<string>();
|
|
@@ -574,6 +557,7 @@ DIAGNOSTICS:
|
|
|
574
557
|
pi.events.on(SUBAGENT_ASYNC_STARTED_EVENT, handleStarted),
|
|
575
558
|
pi.events.on(SUBAGENT_ASYNC_COMPLETE_EVENT, handleComplete),
|
|
576
559
|
pi.events.on(SUBAGENT_CONTROL_EVENT, controlEventHandler),
|
|
560
|
+
rpcBridge.dispose,
|
|
577
561
|
];
|
|
578
562
|
globalStore[eventUnsubscribeStoreKey] = eventUnsubscribes;
|
|
579
563
|
|
|
@@ -615,30 +599,19 @@ DIAGNOSTICS:
|
|
|
615
599
|
}
|
|
616
600
|
}
|
|
617
601
|
state.lastUiContext = ctx;
|
|
618
|
-
state.companionSuggestionStartupShown = false;
|
|
619
|
-
state.companionSuggestionListShown = false;
|
|
620
602
|
cleanupSessionArtifacts(ctx);
|
|
621
603
|
clearPendingForegroundControlNotices(state);
|
|
622
604
|
resetJobs(ctx);
|
|
623
605
|
restoreActiveJobs(ctx);
|
|
606
|
+
scheduledRunManager.bindSession(ctx);
|
|
624
607
|
restoreSlashFinalSnapshots(ctx.sessionManager.getEntries());
|
|
625
608
|
primeExistingResults();
|
|
626
609
|
};
|
|
627
610
|
|
|
628
611
|
pi.on("session_start", (_event, ctx) => {
|
|
629
612
|
resetSessionState(ctx);
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
ctx,
|
|
633
|
-
state,
|
|
634
|
-
statuses: collectCompanionStatuses({
|
|
635
|
-
pi,
|
|
636
|
-
config,
|
|
637
|
-
cwd: ctx.cwd,
|
|
638
|
-
orchestratorTarget: resolveCompanionOrchestratorTarget(pi, ctx),
|
|
639
|
-
fast: true,
|
|
640
|
-
}),
|
|
641
|
-
});
|
|
613
|
+
rpcBridge.emitReady(ctx);
|
|
614
|
+
supervisorChannel.start();
|
|
642
615
|
});
|
|
643
616
|
|
|
644
617
|
pi.on("session_shutdown", () => {
|
|
@@ -654,6 +627,7 @@ DIAGNOSTICS:
|
|
|
654
627
|
delete globalStore[eventUnsubscribeStoreKey];
|
|
655
628
|
}
|
|
656
629
|
stopResultWatcher();
|
|
630
|
+
scheduledRunManager.stop();
|
|
657
631
|
if (state.poller) clearInterval(state.poller);
|
|
658
632
|
state.poller = null;
|
|
659
633
|
clearPendingForegroundControlNotices(state);
|
|
@@ -667,6 +641,7 @@ DIAGNOSTICS:
|
|
|
667
641
|
slashBridge.dispose();
|
|
668
642
|
promptTemplateBridge.cancelAll();
|
|
669
643
|
promptTemplateBridge.dispose();
|
|
644
|
+
supervisorChannel.dispose();
|
|
670
645
|
if (globalStore[runtimeCleanupStoreKey] === runtimeCleanup) {
|
|
671
646
|
delete globalStore[runtimeCleanupStoreKey];
|
|
672
647
|
}
|