pi-subagents 0.18.1 → 0.19.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 +11 -0
- package/README.md +2 -2
- package/agent-manager-chain-detail.ts +50 -6
- package/agent-manager-detail.ts +15 -2
- package/agent-manager.ts +76 -23
- package/async-execution.ts +45 -18
- package/chain-execution.ts +12 -2
- package/execution.ts +2 -4
- package/index.ts +1 -1
- package/intercom-bridge.ts +3 -3
- package/package.json +5 -1
- package/prompts/parallel-review.md +8 -0
- package/schemas.ts +4 -1
- package/settings.ts +5 -0
- package/slash-commands.ts +27 -28
- package/subagent-executor.ts +102 -18
- package/subagent-runner.ts +8 -0
- package/subagents-status.ts +216 -2
- package/worktree.ts +19 -10
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.19.0] - 2026-04-26
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- Added top-level parallel task support for per-task `output`, `reads`, and `progress`, including `/parallel` inline forwarding and async preservation.
|
|
9
|
+
- Added `/agents` launch toggles for forked context, background execution, and worktree-isolated parallel runs.
|
|
10
|
+
- Added a read-only detail view to `/subagents-status` for inspecting selected async runs, including recent events, output tails, and useful run paths.
|
|
11
|
+
- Added a packaged `/parallel-review` prompt template for launching fresh-context adversarial review subagents.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- Parallel and chain child runs now detach cleanly when a child uses intercom, preventing incoming handoff messages from aborting the parent foreground run.
|
|
15
|
+
|
|
5
16
|
## [0.18.1] - 2026-04-25
|
|
6
17
|
|
|
7
18
|
### Changed
|
package/README.md
CHANGED
|
@@ -977,9 +977,9 @@ The inherited thread is reference-only. Do not continue that conversation or sen
|
|
|
977
977
|
Use `intercom` only to coordinate with the orchestrator session `{orchestratorTarget}`.
|
|
978
978
|
|
|
979
979
|
- Need a decision or you're blocked: `intercom({ action: "ask", to: "{orchestratorTarget}", message: "<question>" })`
|
|
980
|
-
-
|
|
980
|
+
- Blocked or explicitly asked to send progress: `intercom({ action: "send", to: "{orchestratorTarget}", message: "UPDATE: <summary>" })`
|
|
981
981
|
|
|
982
|
-
If no
|
|
982
|
+
Do not send routine completion handoffs through intercom. If no coordination is needed, return a focused task result.
|
|
983
983
|
```
|
|
984
984
|
|
|
985
985
|
Bridge activation also requires all of the following:
|
|
@@ -2,6 +2,7 @@ import type { Theme } from "@mariozechner/pi-coding-agent";
|
|
|
2
2
|
import { matchesKey, truncateToWidth } from "@mariozechner/pi-tui";
|
|
3
3
|
import type { ChainConfig, ChainStepConfig } from "./agents.ts";
|
|
4
4
|
import { row, renderFooter, renderHeader, formatPath, formatScrollInfo } from "./render-helpers.ts";
|
|
5
|
+
import { isParallelStep, type ChainStep } from "./settings.ts";
|
|
5
6
|
|
|
6
7
|
export interface ChainDetailState {
|
|
7
8
|
scrollOffset: number;
|
|
@@ -14,11 +15,24 @@ export type ChainDetailAction =
|
|
|
14
15
|
|
|
15
16
|
const CHAIN_DETAIL_VIEWPORT_HEIGHT = 12;
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
type DetailChainStep = ChainStepConfig | ChainStep;
|
|
19
|
+
|
|
20
|
+
export function buildDependencyMap(steps: DetailChainStep[]): Map<number, number[]> {
|
|
18
21
|
const outputMap = new Map<string, number>();
|
|
19
22
|
const deps = new Map<number, number[]>();
|
|
20
23
|
for (let i = 0; i < steps.length; i++) {
|
|
21
24
|
const step = steps[i]!;
|
|
25
|
+
if (isParallelStep(step as ChainStep)) {
|
|
26
|
+
const reads = step.parallel.flatMap((task) => Array.isArray(task.reads) ? task.reads : []);
|
|
27
|
+
const sources = reads
|
|
28
|
+
.map((file) => outputMap.get(file))
|
|
29
|
+
.filter((idx): idx is number => idx !== undefined);
|
|
30
|
+
if (sources.length > 0) deps.set(i, [...new Set(sources)]);
|
|
31
|
+
for (const task of step.parallel) {
|
|
32
|
+
if (typeof task.output === "string" && task.output.length > 0) outputMap.set(task.output, i);
|
|
33
|
+
}
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
22
36
|
if (typeof step.output === "string" && step.output.length > 0) outputMap.set(step.output, i);
|
|
23
37
|
if (Array.isArray(step.reads) && step.reads.length > 0) {
|
|
24
38
|
const sources = step.reads
|
|
@@ -33,21 +47,51 @@ export function buildDependencyMap(steps: ChainStepConfig[]): Map<number, number
|
|
|
33
47
|
function buildChainDetailLines(chain: ChainConfig, width: number): string[] {
|
|
34
48
|
const contentWidth = width - 3;
|
|
35
49
|
const lines: string[] = [];
|
|
36
|
-
const
|
|
50
|
+
const steps = chain.steps as DetailChainStep[];
|
|
51
|
+
const dependencyMap = buildDependencyMap(steps);
|
|
37
52
|
lines.push(truncateToWidth(chain.description, contentWidth));
|
|
38
53
|
lines.push("");
|
|
39
54
|
lines.push(truncateToWidth(`File: ${formatPath(chain.filePath)}`, contentWidth));
|
|
40
55
|
lines.push("");
|
|
41
56
|
lines.push(truncateToWidth("── Flow ──", contentWidth));
|
|
42
57
|
|
|
43
|
-
for (let i = 0; i <
|
|
44
|
-
const step =
|
|
58
|
+
for (let i = 0; i < steps.length; i++) {
|
|
59
|
+
const step = steps[i]!;
|
|
60
|
+
const sources = dependencyMap.get(i);
|
|
61
|
+
const fromText = sources && sources.length > 0 ? ` (from ${sources.map((s) => s + 1).join(", ")})` : "";
|
|
62
|
+
if (isParallelStep(step as ChainStep)) {
|
|
63
|
+
lines.push(truncateToWidth(` ${i + 1} Parallel: ${step.parallel.map((task) => task.agent).join(" + ")}`, contentWidth));
|
|
64
|
+
if (step.concurrency !== undefined) lines.push(truncateToWidth(` concurrency: ${step.concurrency}`, contentWidth));
|
|
65
|
+
if (step.failFast !== undefined) lines.push(truncateToWidth(` fail fast: ${step.failFast ? "on" : "off"}`, contentWidth));
|
|
66
|
+
if (step.worktree !== undefined) lines.push(truncateToWidth(` worktree: ${step.worktree ? "on" : "off"}`, contentWidth));
|
|
67
|
+
for (let taskIndex = 0; taskIndex < step.parallel.length; taskIndex++) {
|
|
68
|
+
const task = step.parallel[taskIndex]!;
|
|
69
|
+
lines.push(truncateToWidth(` ${taskIndex + 1}. ${task.agent}`, contentWidth));
|
|
70
|
+
const taskPreview = (task.task ?? "").split("\n")[0] ?? "";
|
|
71
|
+
if (taskPreview) lines.push(truncateToWidth(` task: ${taskPreview}`, contentWidth));
|
|
72
|
+
if (Array.isArray(task.reads) && task.reads.length > 0) lines.push(truncateToWidth(` ← reads: ${task.reads.join(", ")}${fromText}`, contentWidth));
|
|
73
|
+
else if (task.reads === false) lines.push(truncateToWidth(" ← reads: (disabled)", contentWidth));
|
|
74
|
+
if (typeof task.output === "string" && task.output.length > 0) lines.push(truncateToWidth(` → output: ${task.output}`, contentWidth));
|
|
75
|
+
else if (task.output === false) lines.push(truncateToWidth(" → output: (disabled)", contentWidth));
|
|
76
|
+
if (task.model) lines.push(truncateToWidth(` model: ${task.model}`, contentWidth));
|
|
77
|
+
if (task.skill !== undefined) {
|
|
78
|
+
const skillsText =
|
|
79
|
+
task.skill === false
|
|
80
|
+
? "(disabled)"
|
|
81
|
+
: Array.isArray(task.skill)
|
|
82
|
+
? (task.skill.length > 0 ? task.skill.join(", ") : "(none)")
|
|
83
|
+
: task.skill;
|
|
84
|
+
lines.push(truncateToWidth(` skills: ${skillsText}`, contentWidth));
|
|
85
|
+
}
|
|
86
|
+
if (task.progress !== undefined) lines.push(truncateToWidth(` progress: ${task.progress ? "on" : "off"}`, contentWidth));
|
|
87
|
+
}
|
|
88
|
+
lines.push("");
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
45
91
|
lines.push(truncateToWidth(` ${i + 1} ${step.agent}`, contentWidth));
|
|
46
92
|
const taskPreview = step.task.split("\n")[0] ?? "";
|
|
47
93
|
lines.push(truncateToWidth(` task: ${taskPreview || "(none)"}`, contentWidth));
|
|
48
94
|
if (Array.isArray(step.reads) && step.reads.length > 0) {
|
|
49
|
-
const sources = dependencyMap.get(i);
|
|
50
|
-
const fromText = sources && sources.length > 0 ? ` (from ${sources.map((s) => s + 1).join(", ")})` : "";
|
|
51
95
|
lines.push(truncateToWidth(` ← reads: ${step.reads.join(", ")}${fromText}`, contentWidth));
|
|
52
96
|
} else if (step.reads === false) {
|
|
53
97
|
lines.push(truncateToWidth(" ← reads: (disabled)", contentWidth));
|
package/agent-manager-detail.ts
CHANGED
|
@@ -180,12 +180,19 @@ export function renderDetail(
|
|
|
180
180
|
return lines;
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
+
export interface LaunchToggleState {
|
|
184
|
+
fork: boolean;
|
|
185
|
+
background: boolean;
|
|
186
|
+
worktree?: boolean;
|
|
187
|
+
}
|
|
188
|
+
|
|
183
189
|
export function renderTaskInput(
|
|
184
190
|
title: string,
|
|
185
191
|
editor: TextEditorState,
|
|
186
192
|
skipClarify: boolean,
|
|
187
193
|
width: number,
|
|
188
194
|
theme: Theme,
|
|
195
|
+
launchToggles?: LaunchToggleState,
|
|
189
196
|
): string[] {
|
|
190
197
|
const lines: string[] = [];
|
|
191
198
|
lines.push(renderHeader(` ${title} `, width, theme));
|
|
@@ -209,8 +216,14 @@ export function renderTaskInput(
|
|
|
209
216
|
lines.push(row(` ${bottom}`, width, theme));
|
|
210
217
|
|
|
211
218
|
lines.push(row("", width, theme));
|
|
212
|
-
const enterLabel = skipClarify ? "quick run" : "run";
|
|
213
219
|
const quickLabel = skipClarify ? "on" : "off";
|
|
214
|
-
|
|
220
|
+
const footerParts = ["[enter] run", `[tab] quick:${quickLabel}`];
|
|
221
|
+
if (launchToggles) {
|
|
222
|
+
footerParts.push(`[ctrl+f] fork:${launchToggles.fork ? "on" : "off"}`);
|
|
223
|
+
footerParts.push(`[ctrl+b] bg:${launchToggles.background ? "on" : "off"}`);
|
|
224
|
+
if (launchToggles.worktree !== undefined) footerParts.push(`[ctrl+w] worktree:${launchToggles.worktree ? "on" : "off"}`);
|
|
225
|
+
}
|
|
226
|
+
footerParts.push("[esc]");
|
|
227
|
+
lines.push(renderFooter(` ${footerParts.join(" ")} `, width, theme));
|
|
215
228
|
return lines;
|
|
216
229
|
}
|
package/agent-manager.ts
CHANGED
|
@@ -20,19 +20,20 @@ import { TEMPLATE_ITEMS, type AgentTemplate, type TemplateItem } from "./agent-t
|
|
|
20
20
|
import { parseChain, serializeChain } from "./chain-serializer.ts";
|
|
21
21
|
import { renderList, handleListInput, type ListAgent, type ListState, type ListAction } from "./agent-manager-list.ts";
|
|
22
22
|
import { createParallelState, handleParallelInput, renderParallel, formatParallelTitle, type ParallelState, type AgentOption } from "./agent-manager-parallel.ts";
|
|
23
|
-
import { renderDetail, handleDetailInput, renderTaskInput, type DetailState, type DetailAction } from "./agent-manager-detail.ts";
|
|
23
|
+
import { renderDetail, handleDetailInput, renderTaskInput, type DetailState, type DetailAction, type LaunchToggleState } from "./agent-manager-detail.ts";
|
|
24
24
|
import { renderChainDetail, handleChainDetailInput, type ChainDetailAction, type ChainDetailState } from "./agent-manager-chain-detail.ts";
|
|
25
25
|
import { createEditState, handleEditInput, renderEdit, type EditField, type EditScreen, type EditState, type ModelInfo, type SkillInfo } from "./agent-manager-edit.ts";
|
|
26
26
|
import { createEditorState, ensureCursorVisible, getCursorDisplayPos, handleEditorInput, renderEditor, wrapText } from "./text-editor.ts";
|
|
27
27
|
import type { TextEditorState } from "./text-editor.ts";
|
|
28
28
|
import { loadRunsForAgent } from "./run-history.ts";
|
|
29
29
|
import { pad, row, renderHeader, renderFooter } from "./render-helpers.ts";
|
|
30
|
+
import { isParallelStep, type ChainStep } from "./settings.ts";
|
|
30
31
|
|
|
31
32
|
export type ManagerResult =
|
|
32
|
-
| { action: "launch"; agent: string; task: string; skipClarify?: boolean }
|
|
33
|
-
| { action: "chain"; agents: string[]; task: string; skipClarify?: boolean }
|
|
34
|
-
| { action: "parallel"; tasks: Array<{ agent: string; task: string }>; skipClarify?: boolean }
|
|
35
|
-
| { action: "launch-chain"; chain: ChainConfig; task: string; skipClarify?: boolean }
|
|
33
|
+
| { action: "launch"; agent: string; task: string; skipClarify?: boolean; fork?: boolean; background?: boolean }
|
|
34
|
+
| { action: "chain"; agents: string[]; task: string; skipClarify?: boolean; fork?: boolean; background?: boolean }
|
|
35
|
+
| { action: "parallel"; tasks: Array<{ agent: string; task: string }>; skipClarify?: boolean; fork?: boolean; background?: boolean; worktree?: boolean }
|
|
36
|
+
| { action: "launch-chain"; chain: ChainConfig; task: string; skipClarify?: boolean; fork?: boolean; background?: boolean; worktree?: boolean }
|
|
36
37
|
| undefined;
|
|
37
38
|
|
|
38
39
|
export interface AgentData { builtin: AgentConfig[]; user: AgentConfig[]; project: AgentConfig[]; chains: ChainConfig[]; userDir: string; projectDir: string | null; userSettingsPath: string; projectSettingsPath: string | null; cwd: string; }
|
|
@@ -69,7 +70,27 @@ function cloneConfig(config: AgentConfig): AgentConfig {
|
|
|
69
70
|
: undefined,
|
|
70
71
|
};
|
|
71
72
|
}
|
|
72
|
-
function cloneChainConfig(config: ChainConfig): ChainConfig {
|
|
73
|
+
function cloneChainConfig(config: ChainConfig): ChainConfig {
|
|
74
|
+
const steps = (config.steps as unknown as ChainStep[]).map((step) => {
|
|
75
|
+
if (isParallelStep(step)) {
|
|
76
|
+
return {
|
|
77
|
+
...step,
|
|
78
|
+
parallel: step.parallel.map((task) => ({
|
|
79
|
+
...task,
|
|
80
|
+
reads: Array.isArray(task.reads) ? [...task.reads] : task.reads,
|
|
81
|
+
skill: Array.isArray(task.skill) ? [...task.skill] : task.skill,
|
|
82
|
+
})),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
...step,
|
|
87
|
+
reads: Array.isArray(step.reads) ? [...step.reads] : step.reads,
|
|
88
|
+
...(Array.isArray((step as typeof step & { skills?: string[] | false }).skills) ? { skills: [...(step as typeof step & { skills: string[] }).skills] } : { skills: (step as typeof step & { skills?: false }).skills }),
|
|
89
|
+
...(Array.isArray(step.skill) ? { skill: [...step.skill] } : { skill: step.skill }),
|
|
90
|
+
};
|
|
91
|
+
});
|
|
92
|
+
return { ...config, steps: steps as unknown as ChainConfig["steps"], extraFields: config.extraFields ? { ...config.extraFields } : undefined };
|
|
93
|
+
}
|
|
73
94
|
function slugTemplateName(name: string): string { return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, ""); }
|
|
74
95
|
function nextSelectableIndex(items: TemplateItem[], current: number, direction: 1 | -1): number { let next = current + direction; while (next >= 0 && next < items.length && items[next]!.type === "separator") next += direction; if (next < 0 || next >= items.length) return current; return next; }
|
|
75
96
|
const CHAIN_EDIT_VIEWPORT = 10;
|
|
@@ -90,6 +111,9 @@ export class AgentManagerComponent implements Component {
|
|
|
90
111
|
private chainEditState: { editor: TextEditorState; error?: string } | null = null;
|
|
91
112
|
private taskEditor: TextEditorState = createEditorState();
|
|
92
113
|
private skipClarify = false;
|
|
114
|
+
private launchFork = false;
|
|
115
|
+
private launchBackground = false;
|
|
116
|
+
private launchWorktree = false;
|
|
93
117
|
private chainAgentIds: string[] = [];
|
|
94
118
|
private chainLaunchId: string | null = null;
|
|
95
119
|
private parallelMode = false;
|
|
@@ -187,10 +211,21 @@ export class AgentManagerComponent implements Component {
|
|
|
187
211
|
this.parallelState = createParallelState(names);
|
|
188
212
|
this.screen = "parallel-builder";
|
|
189
213
|
}
|
|
214
|
+
private resetLaunchToggles(): void { this.launchFork = false; this.launchBackground = false; this.launchWorktree = false; }
|
|
215
|
+
private enterParallelTaskInput(): void {
|
|
216
|
+
this.chainAgentIds = [];
|
|
217
|
+
this.chainLaunchId = null;
|
|
218
|
+
this.parallelMode = true;
|
|
219
|
+
this.taskBackScreen = "parallel-builder";
|
|
220
|
+
this.taskEditor = createEditorState();
|
|
221
|
+
this.skipClarify = true;
|
|
222
|
+
this.resetLaunchToggles();
|
|
223
|
+
this.screen = "task-input";
|
|
224
|
+
}
|
|
190
225
|
private enterTaskInput(ids: string[], backScreen: ManagerScreen = "list"): void {
|
|
191
|
-
this.chainAgentIds = ids; this.chainLaunchId = null; this.parallelMode = false; this.taskBackScreen = backScreen; this.taskEditor = createEditorState(); this.skipClarify = true; this.screen = "task-input";
|
|
226
|
+
this.chainAgentIds = ids; this.chainLaunchId = null; this.parallelMode = false; this.taskBackScreen = backScreen; this.taskEditor = createEditorState(); this.skipClarify = true; this.resetLaunchToggles(); this.screen = "task-input";
|
|
192
227
|
}
|
|
193
|
-
private enterSavedChainLaunch(entry: ChainEntry): void { this.chainLaunchId = entry.id; this.chainAgentIds = []; this.parallelMode = false; this.taskBackScreen = "chain-detail"; this.taskEditor = createEditorState(); this.skipClarify = true; this.screen = "task-input"; }
|
|
228
|
+
private enterSavedChainLaunch(entry: ChainEntry): void { this.chainLaunchId = entry.id; this.chainAgentIds = []; this.parallelMode = false; this.taskBackScreen = "chain-detail"; this.taskEditor = createEditorState(); this.skipClarify = true; this.resetLaunchToggles(); this.screen = "task-input"; }
|
|
194
229
|
private enterTemplateSelect(): void { this.templateCursor = TEMPLATE_ITEMS.findIndex((item) => item.type !== "separator"); if (this.templateCursor < 0) this.templateCursor = 0; this.screen = "template-select"; }
|
|
195
230
|
|
|
196
231
|
private enterChainEdit(entry: ChainEntry): void {
|
|
@@ -276,6 +311,27 @@ export class AgentManagerComponent implements Component {
|
|
|
276
311
|
catch (err) { state.error = err instanceof Error ? err.message : "Failed to save chain."; return false; }
|
|
277
312
|
}
|
|
278
313
|
|
|
314
|
+
private canToggleLaunchWorktree(): boolean {
|
|
315
|
+
if (this.parallelMode && this.parallelState) return true;
|
|
316
|
+
if (!this.chainLaunchId) return false;
|
|
317
|
+
const chainEntry = this.getChainEntry(this.chainLaunchId);
|
|
318
|
+
return chainEntry ? (chainEntry.config.steps as unknown as ChainStep[]).some(isParallelStep) : false;
|
|
319
|
+
}
|
|
320
|
+
private launchFlags(): { fork?: boolean; background?: boolean; worktree?: boolean } {
|
|
321
|
+
return {
|
|
322
|
+
...(this.launchFork ? { fork: true } : {}),
|
|
323
|
+
...(this.launchBackground ? { background: true } : {}),
|
|
324
|
+
...(this.launchWorktree && this.canToggleLaunchWorktree() ? { worktree: true } : {}),
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
private launchToggleState(): LaunchToggleState {
|
|
328
|
+
return {
|
|
329
|
+
fork: this.launchFork,
|
|
330
|
+
background: this.launchBackground,
|
|
331
|
+
...(this.canToggleLaunchWorktree() ? { worktree: this.launchWorktree } : {}),
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
279
335
|
private handleTemplateSelectInput(data: string): void {
|
|
280
336
|
if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) { this.screen = "list"; this.tui.requestRender(); return; }
|
|
281
337
|
if (matchesKey(data, "up")) { this.templateCursor = nextSelectableIndex(TEMPLATE_ITEMS, this.templateCursor, -1); this.tui.requestRender(); return; }
|
|
@@ -476,13 +532,7 @@ export class AgentManagerComponent implements Component {
|
|
|
476
532
|
const agentOptions: AgentOption[] = this.agents.map((e) => ({ name: e.config.name, description: e.config.description, model: e.config.model }));
|
|
477
533
|
const pAction = handleParallelInput(this.parallelState, agentOptions, data, this.overlayWidth);
|
|
478
534
|
if (pAction?.type === "proceed") {
|
|
479
|
-
this.
|
|
480
|
-
this.chainLaunchId = null;
|
|
481
|
-
this.parallelMode = true;
|
|
482
|
-
this.taskBackScreen = "parallel-builder";
|
|
483
|
-
this.taskEditor = createEditorState();
|
|
484
|
-
this.skipClarify = true;
|
|
485
|
-
this.screen = "task-input";
|
|
535
|
+
this.enterParallelTaskInput();
|
|
486
536
|
} else if (pAction?.type === "back") {
|
|
487
537
|
this.parallelState = null;
|
|
488
538
|
this.parallelMode = false;
|
|
@@ -493,28 +543,31 @@ export class AgentManagerComponent implements Component {
|
|
|
493
543
|
}
|
|
494
544
|
case "task-input": {
|
|
495
545
|
if (matchesKey(data, "tab")) { this.skipClarify = !this.skipClarify; this.tui.requestRender(); return; }
|
|
546
|
+
if (matchesKey(data, "ctrl+f")) { this.launchFork = !this.launchFork; this.tui.requestRender(); return; }
|
|
547
|
+
if (matchesKey(data, "ctrl+b")) { this.launchBackground = !this.launchBackground; this.tui.requestRender(); return; }
|
|
548
|
+
if (matchesKey(data, "ctrl+w") && this.canToggleLaunchWorktree()) { this.launchWorktree = !this.launchWorktree; this.tui.requestRender(); return; }
|
|
496
549
|
const innerW = this.overlayWidth - 2; const boxInnerWidth = Math.max(10, innerW - 4); const nextState = handleEditorInput(this.taskEditor, data, boxInnerWidth);
|
|
497
550
|
if (nextState) { this.taskEditor = nextState; this.tui.requestRender(); return; }
|
|
498
551
|
if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) { this.screen = this.taskBackScreen; this.tui.requestRender(); return; }
|
|
499
552
|
if (matchesKey(data, "return")) {
|
|
500
553
|
if (this.chainLaunchId) {
|
|
501
554
|
const chainEntry = this.getChainEntry(this.chainLaunchId); if (!chainEntry) { this.screen = "list"; this.tui.requestRender(); return; }
|
|
502
|
-
this.done({ action: "launch-chain", chain: cloneChainConfig(chainEntry.config), task: this.taskEditor.buffer, skipClarify: this.skipClarify }); return;
|
|
555
|
+
this.done({ action: "launch-chain", chain: cloneChainConfig(chainEntry.config), task: this.taskEditor.buffer, skipClarify: this.skipClarify, ...this.launchFlags() }); return;
|
|
503
556
|
} else if (this.parallelMode && this.parallelState) {
|
|
504
557
|
const sharedTask = this.taskEditor.buffer;
|
|
505
558
|
const tasks = this.parallelState.slots.map((slot) => ({ agent: slot.agentName, task: slot.customTask || sharedTask }));
|
|
506
|
-
this.done({ action: "parallel", tasks, skipClarify: this.skipClarify }); return;
|
|
559
|
+
this.done({ action: "parallel", tasks, skipClarify: this.skipClarify, ...this.launchFlags() }); return;
|
|
507
560
|
}
|
|
508
561
|
if (this.chainAgentIds.length > 1) {
|
|
509
562
|
const agents = this.chainAgentIds
|
|
510
563
|
.map((id) => this.getAgentEntry(id)?.config.name)
|
|
511
564
|
.filter((name): name is string => Boolean(name));
|
|
512
565
|
if (agents.length !== this.chainAgentIds.length) { this.screen = "list"; this.tui.requestRender(); return; }
|
|
513
|
-
this.done({ action: "chain", agents, task: this.taskEditor.buffer, skipClarify: this.skipClarify }); return;
|
|
566
|
+
this.done({ action: "chain", agents, task: this.taskEditor.buffer, skipClarify: this.skipClarify, ...this.launchFlags() }); return;
|
|
514
567
|
}
|
|
515
568
|
const name = this.getAgentEntry(this.chainAgentIds[0] ?? null)?.config.name;
|
|
516
569
|
if (!name) { this.screen = "list"; this.tui.requestRender(); return; }
|
|
517
|
-
this.done({ action: "launch", agent: name, task: this.taskEditor.buffer, skipClarify: this.skipClarify }); return;
|
|
570
|
+
this.done({ action: "launch", agent: name, task: this.taskEditor.buffer, skipClarify: this.skipClarify, ...this.launchFlags() }); return;
|
|
518
571
|
}
|
|
519
572
|
return;
|
|
520
573
|
}
|
|
@@ -628,16 +681,16 @@ export class AgentManagerComponent implements Component {
|
|
|
628
681
|
return renderParallel(this.parallelState, agentOptions, w, this.theme);
|
|
629
682
|
}
|
|
630
683
|
case "task-input": {
|
|
631
|
-
if (this.chainLaunchId) { const entry = this.getChainEntry(this.chainLaunchId); const title = entry ? `Chain: ${entry.config.name}` : "Chain"; return renderTaskInput(title, this.taskEditor, this.skipClarify, w, this.theme); }
|
|
632
|
-
if (this.parallelMode && this.parallelState) return renderTaskInput(formatParallelTitle(this.parallelState.slots), this.taskEditor, this.skipClarify, w, this.theme);
|
|
684
|
+
if (this.chainLaunchId) { const entry = this.getChainEntry(this.chainLaunchId); const title = entry ? `Chain: ${entry.config.name}` : "Chain"; return renderTaskInput(title, this.taskEditor, this.skipClarify, w, this.theme, this.launchToggleState()); }
|
|
685
|
+
if (this.parallelMode && this.parallelState) return renderTaskInput(formatParallelTitle(this.parallelState.slots), this.taskEditor, this.skipClarify, w, this.theme, this.launchToggleState());
|
|
633
686
|
if (this.chainAgentIds.length > 1) {
|
|
634
687
|
const names = this.chainAgentIds
|
|
635
688
|
.map((id) => this.getAgentEntry(id)?.config.name)
|
|
636
689
|
.filter((name): name is string => Boolean(name));
|
|
637
|
-
return renderTaskInput(`Chain: ${names.join(" → ")}`, this.taskEditor, this.skipClarify, w, this.theme);
|
|
690
|
+
return renderTaskInput(`Chain: ${names.join(" → ")}`, this.taskEditor, this.skipClarify, w, this.theme, this.launchToggleState());
|
|
638
691
|
}
|
|
639
692
|
const name = this.getAgentEntry(this.chainAgentIds[0] ?? null)?.config.name ?? "Agent";
|
|
640
|
-
return renderTaskInput(`Run: ${name}`, this.taskEditor, this.skipClarify, w, this.theme);
|
|
693
|
+
return renderTaskInput(`Run: ${name}`, this.taskEditor, this.skipClarify, w, this.theme, this.launchToggleState());
|
|
641
694
|
}
|
|
642
695
|
case "confirm-delete": return this.renderConfirmDelete(w);
|
|
643
696
|
case "name-input": return this.renderNameInput(w);
|
package/async-execution.ts
CHANGED
|
@@ -12,12 +12,13 @@ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
|
12
12
|
import type { AgentConfig } from "./agents.ts";
|
|
13
13
|
import { applyThinkingSuffix } from "./pi-args.ts";
|
|
14
14
|
import { injectSingleOutputInstruction, resolveSingleOutputPath } from "./single-output.ts";
|
|
15
|
-
import { isParallelStep, resolveStepBehavior, type ChainStep, type SequentialStep, type StepOverrides } from "./settings.ts";
|
|
15
|
+
import { buildChainInstructions, isParallelStep, resolveStepBehavior, writeInitialProgressFile, type ChainStep, type ResolvedStepBehavior, type SequentialStep, type StepOverrides } from "./settings.ts";
|
|
16
16
|
import type { RunnerStep } from "./parallel-utils.ts";
|
|
17
17
|
import { resolvePiPackageRoot } from "./pi-spawn.ts";
|
|
18
18
|
import { buildSkillInjection, normalizeSkillInput, resolveSkillsWithFallback } from "./skills.ts";
|
|
19
19
|
import { resolveChildCwd } from "./utils.ts";
|
|
20
20
|
import { buildModelCandidates, resolveModelCandidate, type AvailableModelInfo } from "./model-fallback.ts";
|
|
21
|
+
import { resolveExpectedWorktreeAgentCwd } from "./worktree.ts";
|
|
21
22
|
import {
|
|
22
23
|
type ArtifactConfig,
|
|
23
24
|
type Details,
|
|
@@ -221,12 +222,22 @@ export function executeAsyncChain(
|
|
|
221
222
|
};
|
|
222
223
|
}
|
|
223
224
|
|
|
224
|
-
|
|
225
|
+
let progressInstructionCreated = false;
|
|
226
|
+
const buildStepOverrides = (s: SequentialStep): StepOverrides => {
|
|
227
|
+
const stepSkillInput = normalizeSkillInput(s.skill);
|
|
228
|
+
return {
|
|
229
|
+
...(s.output !== undefined ? { output: s.output } : {}),
|
|
230
|
+
...(s.reads !== undefined ? { reads: s.reads } : {}),
|
|
231
|
+
...(s.progress !== undefined ? { progress: s.progress } : {}),
|
|
232
|
+
...(stepSkillInput !== undefined ? { skills: stepSkillInput } : {}),
|
|
233
|
+
...(s.model ? { model: s.model } : {}),
|
|
234
|
+
};
|
|
235
|
+
};
|
|
236
|
+
const buildSeqStep = (s: SequentialStep, sessionFile?: string, behaviorCwd?: string, progressPrecreated = false, resolvedBehavior?: ResolvedStepBehavior) => {
|
|
225
237
|
const a = agents.find((x) => x.name === s.agent)!;
|
|
226
238
|
const stepCwd = resolveChildCwd(runnerCwd, s.cwd);
|
|
227
|
-
const
|
|
228
|
-
const
|
|
229
|
-
const behavior = resolveStepBehavior(a, stepOverrides, chainSkills);
|
|
239
|
+
const instructionCwd = behaviorCwd ?? stepCwd;
|
|
240
|
+
const behavior = resolvedBehavior ?? resolveStepBehavior(a, buildStepOverrides(s), chainSkills);
|
|
230
241
|
const skillNames = behavior.skills === false ? [] : behavior.skills;
|
|
231
242
|
const { resolved: resolvedSkills } = resolveSkillsWithFallback(skillNames, stepCwd, ctx.cwd);
|
|
232
243
|
|
|
@@ -236,16 +247,20 @@ export function executeAsyncChain(
|
|
|
236
247
|
systemPrompt = systemPrompt ? `${systemPrompt}\n\n${injection}` : injection;
|
|
237
248
|
}
|
|
238
249
|
|
|
239
|
-
const
|
|
240
|
-
const
|
|
250
|
+
const readInstructions = buildChainInstructions({ ...behavior, output: false, progress: false }, instructionCwd, false);
|
|
251
|
+
const isFirstProgressAgent = behavior.progress && !progressPrecreated && !progressInstructionCreated;
|
|
252
|
+
if (behavior.progress) progressInstructionCreated = true;
|
|
253
|
+
const progressInstructions = buildChainInstructions({ ...behavior, output: false, reads: false }, runnerCwd, isFirstProgressAgent);
|
|
254
|
+
const outputPath = resolveSingleOutputPath(behavior.output, ctx.cwd, instructionCwd);
|
|
255
|
+
const task = injectSingleOutputInstruction(`${readInstructions.prefix}${s.task ?? "{previous}"}${progressInstructions.suffix}`, outputPath);
|
|
241
256
|
|
|
242
|
-
const primaryModel = resolveModelCandidate(
|
|
257
|
+
const primaryModel = resolveModelCandidate(behavior.model ?? a.model, availableModels, ctx.currentModelProvider);
|
|
243
258
|
return {
|
|
244
259
|
agent: s.agent,
|
|
245
260
|
task,
|
|
246
261
|
cwd: stepCwd,
|
|
247
262
|
model: applyThinkingSuffix(primaryModel, a.thinking),
|
|
248
|
-
modelCandidates: buildModelCandidates(
|
|
263
|
+
modelCandidates: buildModelCandidates(behavior.model ?? a.model, a.fallbackModels, availableModels, ctx.currentModelProvider).map((candidate) =>
|
|
249
264
|
applyThinkingSuffix(candidate, a.thinking),
|
|
250
265
|
),
|
|
251
266
|
tools: a.tools,
|
|
@@ -269,17 +284,29 @@ export function executeAsyncChain(
|
|
|
269
284
|
return sessionFile;
|
|
270
285
|
};
|
|
271
286
|
|
|
272
|
-
const steps: RunnerStep[] = chain.map((s) => {
|
|
287
|
+
const steps: RunnerStep[] = chain.map((s, stepIndex) => {
|
|
273
288
|
if (isParallelStep(s)) {
|
|
289
|
+
const parallelBehaviors = s.parallel.map((task) => {
|
|
290
|
+
const agent = agents.find((candidate) => candidate.name === task.agent)!;
|
|
291
|
+
return resolveStepBehavior(agent, buildStepOverrides(task), chainSkills);
|
|
292
|
+
});
|
|
293
|
+
const progressPrecreated = parallelBehaviors.some((behavior) => behavior.progress);
|
|
294
|
+
if (progressPrecreated) {
|
|
295
|
+
if (!s.worktree) writeInitialProgressFile(runnerCwd);
|
|
296
|
+
progressInstructionCreated = true;
|
|
297
|
+
}
|
|
274
298
|
return {
|
|
275
|
-
parallel: s.parallel.map((t) =>
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
299
|
+
parallel: s.parallel.map((t, taskIndex) => {
|
|
300
|
+
let behaviorCwd: string | undefined;
|
|
301
|
+
if (s.worktree) {
|
|
302
|
+
try {
|
|
303
|
+
behaviorCwd = resolveExpectedWorktreeAgentCwd(runnerCwd, `${id}-s${stepIndex}`, taskIndex);
|
|
304
|
+
} catch {
|
|
305
|
+
behaviorCwd = undefined;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return buildSeqStep(t, nextSessionFile(), behaviorCwd, progressPrecreated, parallelBehaviors[taskIndex]);
|
|
309
|
+
}),
|
|
283
310
|
concurrency: s.concurrency,
|
|
284
311
|
failFast: s.failFast,
|
|
285
312
|
worktree: s.worktree,
|
package/chain-execution.ts
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
resolveStepBehavior,
|
|
16
16
|
resolveParallelBehaviors,
|
|
17
17
|
buildChainInstructions,
|
|
18
|
+
writeInitialProgressFile,
|
|
18
19
|
createParallelDirs,
|
|
19
20
|
aggregateParallelOutputs,
|
|
20
21
|
isParallelStep,
|
|
@@ -26,6 +27,7 @@ import {
|
|
|
26
27
|
type ResolvedTemplates,
|
|
27
28
|
} from "./settings.ts";
|
|
28
29
|
import { discoverAvailableSkills, normalizeSkillInput } from "./skills.ts";
|
|
30
|
+
import { INTERCOM_BRIDGE_MARKER } from "./intercom-bridge.ts";
|
|
29
31
|
import { runSync } from "./execution.ts";
|
|
30
32
|
import { buildChainSummary } from "./formatters.ts";
|
|
31
33
|
import { compactForegroundDetails, getSingleResultOutput, mapConcurrent, resolveChildCwd } from "./utils.ts";
|
|
@@ -46,6 +48,7 @@ import {
|
|
|
46
48
|
type ArtifactPaths,
|
|
47
49
|
type ControlEvent,
|
|
48
50
|
type Details,
|
|
51
|
+
type IntercomEventBus,
|
|
49
52
|
type ResolvedControlConfig,
|
|
50
53
|
type SingleResult,
|
|
51
54
|
MAX_CONCURRENCY,
|
|
@@ -75,6 +78,7 @@ interface ParallelChainRunInput {
|
|
|
75
78
|
prev: string;
|
|
76
79
|
originalTask: string;
|
|
77
80
|
ctx: ExtensionContext;
|
|
81
|
+
intercomEvents?: IntercomEventBus;
|
|
78
82
|
cwd?: string;
|
|
79
83
|
runId: string;
|
|
80
84
|
globalTaskIndex: number;
|
|
@@ -134,8 +138,7 @@ function ensureParallelProgressFile(
|
|
|
134
138
|
if (progressCreated || !parallelBehaviors.some((behavior) => behavior.progress)) {
|
|
135
139
|
return progressCreated;
|
|
136
140
|
}
|
|
137
|
-
|
|
138
|
-
fs.writeFileSync(progressPath, "# Progress\n\n## Status\nIn Progress\n\n## Tasks\n\n## Files Changed\n\n## Notes\n");
|
|
141
|
+
writeInitialProgressFile(chainDir);
|
|
139
142
|
return true;
|
|
140
143
|
}
|
|
141
144
|
|
|
@@ -221,6 +224,8 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
|
|
|
221
224
|
cwd: taskCwd,
|
|
222
225
|
signal: input.signal,
|
|
223
226
|
interruptSignal: interruptController.signal,
|
|
227
|
+
allowIntercomDetach: taskAgentConfig?.systemPrompt?.includes(INTERCOM_BRIDGE_MARKER) === true,
|
|
228
|
+
intercomEvents: input.intercomEvents,
|
|
224
229
|
runId: input.runId,
|
|
225
230
|
index: input.globalTaskIndex + taskIndex,
|
|
226
231
|
sessionDir: input.sessionDirForIndex(input.globalTaskIndex + taskIndex),
|
|
@@ -287,6 +292,7 @@ export interface ChainExecutionParams {
|
|
|
287
292
|
task?: string;
|
|
288
293
|
agents: AgentConfig[];
|
|
289
294
|
ctx: ExtensionContext;
|
|
295
|
+
intercomEvents?: IntercomEventBus;
|
|
290
296
|
signal?: AbortSignal;
|
|
291
297
|
runId: string;
|
|
292
298
|
cwd?: string;
|
|
@@ -352,6 +358,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
352
358
|
controlConfig,
|
|
353
359
|
childIntercomTarget,
|
|
354
360
|
foregroundControl,
|
|
361
|
+
intercomEvents,
|
|
355
362
|
chainSkills: chainSkillsParam,
|
|
356
363
|
chainDir: chainDirBase,
|
|
357
364
|
} = params;
|
|
@@ -536,6 +543,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
536
543
|
prev,
|
|
537
544
|
originalTask,
|
|
538
545
|
ctx,
|
|
546
|
+
intercomEvents,
|
|
539
547
|
cwd,
|
|
540
548
|
runId,
|
|
541
549
|
globalTaskIndex,
|
|
@@ -709,6 +717,8 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
709
717
|
cwd: resolveChildCwd(cwd ?? ctx.cwd, seqStep.cwd),
|
|
710
718
|
signal,
|
|
711
719
|
interruptSignal: interruptController.signal,
|
|
720
|
+
allowIntercomDetach: agentConfig.systemPrompt?.includes(INTERCOM_BRIDGE_MARKER) === true,
|
|
721
|
+
intercomEvents,
|
|
712
722
|
runId,
|
|
713
723
|
index: globalTaskIndex,
|
|
714
724
|
sessionDir: sessionDirForIndex(globalTaskIndex),
|
package/execution.ts
CHANGED
|
@@ -243,13 +243,11 @@ async function runSingleAttempt(
|
|
|
243
243
|
};
|
|
244
244
|
|
|
245
245
|
const unsubscribeIntercomDetach = options.intercomEvents?.on?.(INTERCOM_DETACH_REQUEST_EVENT, (payload) => {
|
|
246
|
-
if (!options.allowIntercomDetach || detached || processClosed) return;
|
|
246
|
+
if (!options.allowIntercomDetach || detached || processClosed || !intercomStarted) return;
|
|
247
247
|
if (!payload || typeof payload !== "object") return;
|
|
248
248
|
const requestId = (payload as { requestId?: unknown }).requestId;
|
|
249
249
|
if (typeof requestId !== "string" || requestId.length === 0) return;
|
|
250
|
-
|
|
251
|
-
options.intercomEvents?.emit(INTERCOM_DETACH_RESPONSE_EVENT, { requestId, accepted });
|
|
252
|
-
if (!accepted) return;
|
|
250
|
+
options.intercomEvents?.emit(INTERCOM_DETACH_RESPONSE_EVENT, { requestId, accepted: true });
|
|
253
251
|
detachForIntercom();
|
|
254
252
|
});
|
|
255
253
|
|
package/index.ts
CHANGED
|
@@ -401,7 +401,7 @@ EXECUTION (use exactly ONE mode):
|
|
|
401
401
|
• Before executing, use { action: "list" } to inspect configured agents/chains. Only execute agents listed as executable/non-disabled.
|
|
402
402
|
• SINGLE: { agent, task? } - one task; omit task for self-contained agents
|
|
403
403
|
• CHAIN: { chain: [{agent:"agent-a"}, {parallel:[{agent:"agent-b",count:3}]}] } - sequential pipeline with optional parallel fan-out
|
|
404
|
-
• PARALLEL: { tasks: [{agent,task,count?}, ...], concurrency?: number, worktree?: true } - concurrent execution (worktree: isolate each task in a git worktree)
|
|
404
|
+
• PARALLEL: { tasks: [{agent,task,count?,output?,reads?,progress?}, ...], concurrency?: number, worktree?: true } - concurrent execution (worktree: isolate each task in a git worktree)
|
|
405
405
|
• Optional context: { context: "fresh" | "fork" } (default: "fresh")
|
|
406
406
|
|
|
407
407
|
CHAIN TEMPLATE VARIABLES (use in task strings):
|
package/intercom-bridge.ts
CHANGED
|
@@ -8,14 +8,14 @@ const DEFAULT_INTERCOM_EXTENSION_DIR = path.join(os.homedir(), ".pi", "agent", "
|
|
|
8
8
|
const DEFAULT_INTERCOM_CONFIG_PATH = path.join(os.homedir(), ".pi", "agent", "intercom", "config.json");
|
|
9
9
|
const DEFAULT_SUBAGENT_CONFIG_DIR = path.join(os.homedir(), ".pi", "agent", "extensions", "subagent");
|
|
10
10
|
const DEFAULT_INTERCOM_TARGET_PREFIX = "subagent-chat";
|
|
11
|
-
const INTERCOM_BRIDGE_MARKER = "Intercom orchestration channel:";
|
|
11
|
+
export const INTERCOM_BRIDGE_MARKER = "Intercom orchestration channel:";
|
|
12
12
|
const DEFAULT_INTERCOM_BRIDGE_TEMPLATE = `The inherited thread is reference-only. Do not continue that conversation or send questions, status updates, or completion handoffs to the orchestrator in normal assistant text.
|
|
13
13
|
|
|
14
14
|
Use intercom only for coordination with the orchestrator session "{orchestratorTarget}".
|
|
15
15
|
- Need a decision or blocked: intercom({ action: "ask", to: "{orchestratorTarget}", message: "<question>" })
|
|
16
|
-
-
|
|
16
|
+
- Blocked or explicitly asked to send progress: intercom({ action: "send", to: "{orchestratorTarget}", message: "UPDATE: <summary>" })
|
|
17
17
|
|
|
18
|
-
If no
|
|
18
|
+
Do not send routine completion handoffs through intercom. If no coordination is needed, return a focused task result.`;
|
|
19
19
|
|
|
20
20
|
export interface IntercomBridgeState {
|
|
21
21
|
active: boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-subagents",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"description": "Pi extension for delegating tasks to subagents with chains, parallel execution, and TUI clarification",
|
|
5
5
|
"author": "Nico Bailon",
|
|
6
6
|
"license": "MIT",
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"*.mjs",
|
|
32
32
|
"agents/",
|
|
33
33
|
"skills/**/*",
|
|
34
|
+
"prompts/**/*",
|
|
34
35
|
"README.md",
|
|
35
36
|
"CHANGELOG.md"
|
|
36
37
|
],
|
|
@@ -46,6 +47,9 @@
|
|
|
46
47
|
],
|
|
47
48
|
"skills": [
|
|
48
49
|
"./skills"
|
|
50
|
+
],
|
|
51
|
+
"prompts": [
|
|
52
|
+
"./prompts"
|
|
49
53
|
]
|
|
50
54
|
},
|
|
51
55
|
"peerDependencies": {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Parallel subagents review
|
|
3
|
+
---
|
|
4
|
+
Great. Now let's launch parallel reviewers to conduct an adversarial review.
|
|
5
|
+
|
|
6
|
+
Important: launch reviewers with fresh context, not forked context. Reviewers should inspect the repository and current diff directly from files and commands, without inheriting the main agent chat. Use forked context only if I explicitly ask for it.
|
|
7
|
+
|
|
8
|
+
$@
|
package/schemas.ts
CHANGED
|
@@ -26,6 +26,9 @@ export const TaskItem = Type.Object({
|
|
|
26
26
|
task: Type.String(),
|
|
27
27
|
cwd: Type.Optional(Type.String()),
|
|
28
28
|
count: Type.Optional(Type.Integer({ minimum: 1, description: "Repeat this parallel task N times with the same settings." })),
|
|
29
|
+
output: Type.Optional(OutputOverride),
|
|
30
|
+
reads: Type.Optional(ReadsOverride),
|
|
31
|
+
progress: Type.Optional(Type.Boolean({ description: "Enable progress.md tracking for this task" })),
|
|
29
32
|
model: Type.Optional(Type.String({ description: "Override model for this task (e.g. 'google/gemini-3-pro')" })),
|
|
30
33
|
skill: Type.Optional(SkillOverride),
|
|
31
34
|
});
|
|
@@ -124,7 +127,7 @@ export const SubagentParams = Type.Object({
|
|
|
124
127
|
additionalProperties: true,
|
|
125
128
|
description: "Agent or chain config for create/update. Agent: name, description, scope ('user'|'project', default 'user'), systemPrompt, systemPromptMode, inheritProjectContext, inheritSkills, model, tools (comma-separated), extensions (comma-separated), skills (comma-separated), thinking, output, reads, progress, maxSubagentDepth. Chain: name, description, scope, steps (array of {agent, task?, output?, reads?, model?, skills?, progress?}). Presence of 'steps' creates a chain instead of an agent. String values must be valid JSON."
|
|
126
129
|
})),
|
|
127
|
-
tasks: Type.Optional(Type.Array(TaskItem, { description: "PARALLEL mode: [{agent, task, count?}, ...]" })),
|
|
130
|
+
tasks: Type.Optional(Type.Array(TaskItem, { description: "PARALLEL mode: [{agent, task, count?, output?, reads?, progress?}, ...]" })),
|
|
128
131
|
concurrency: Type.Optional(Type.Integer({ minimum: 1, description: "Top-level PARALLEL mode only: max concurrent tasks. Defaults to config.parallel.concurrency or 4." })),
|
|
129
132
|
worktree: Type.Optional(Type.Boolean({
|
|
130
133
|
description: "Create isolated git worktrees for each parallel task. " +
|