pi-subagents 0.3.3 → 0.4.1
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 +22 -0
- package/README.md +23 -9
- package/chain-clarify.ts +171 -32
- package/index.ts +129 -18
- package/package.json +2 -1
- package/schemas.ts +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [Unreleased]
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
- Added `pi-package` keyword for npm discoverability (pi v0.50.0 package system)
|
|
7
|
+
|
|
8
|
+
## [0.4.0] - 2026-01-25
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **Clarify TUI for single and parallel modes** - Use `clarify: true` to preview/edit before execution
|
|
12
|
+
- Single mode: Edit task, model, thinking level, output file
|
|
13
|
+
- Parallel mode: Edit each task independently, model, thinking level
|
|
14
|
+
- Navigate between parallel tasks with ↑↓
|
|
15
|
+
- **Mode-aware TUI headers** - Header shows "Agent: X" for single, "Parallel Tasks (N)" for parallel, "Chain: X → Y" for chains
|
|
16
|
+
- **Model override for single/parallel** - TUI model selection now works for all modes
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
- **MAX_PARALLEL error mode** - Now correctly returns `mode: 'parallel'` (was incorrectly `mode: 'single'`)
|
|
20
|
+
- **`output: true` handling** - Now correctly treats `true` as "use agent's default output" instead of creating a file literally named "true"
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
- **Schema description** - `clarify` parameter now documents all modes: "default: true for chains, false for single/parallel"
|
|
24
|
+
|
|
3
25
|
## [0.3.3] - 2026-01-25
|
|
4
26
|
|
|
5
27
|
### Added
|
package/README.md
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
Pi extension for delegating tasks to subagents with chains, parallel execution, TUI clarification, and async support.
|
|
8
8
|
|
|
9
|
+
https://github.com/user-attachments/assets/702554ec-faaf-4635-80aa-fb5d6e292fd1
|
|
10
|
+
|
|
9
11
|
## Installation
|
|
10
12
|
|
|
11
13
|
```bash
|
|
@@ -43,18 +45,30 @@ npx pi-subagents --remove
|
|
|
43
45
|
|
|
44
46
|
*Chain defaults to sync with TUI clarification. Use `clarify: false` to enable async (sequential-only chains; parallel-in-chain requires sync mode).
|
|
45
47
|
|
|
46
|
-
**
|
|
48
|
+
**Clarify TUI for single/parallel:**
|
|
49
|
+
|
|
50
|
+
Single and parallel modes also support the clarify TUI for previewing/editing parameters before execution. Unlike chains, they default to no TUI - use `clarify: true` to enable:
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
// Single agent with clarify TUI
|
|
54
|
+
{ agent: "scout", task: "Analyze the codebase", clarify: true }
|
|
55
|
+
|
|
56
|
+
// Parallel tasks with clarify TUI
|
|
57
|
+
{ tasks: [{agent: "scout", task: "Analyze frontend"}, ...], clarify: true }
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**Clarification TUI keybindings:**
|
|
47
61
|
|
|
48
62
|
*Navigation mode:*
|
|
49
|
-
- `Enter` - Run
|
|
63
|
+
- `Enter` - Run
|
|
50
64
|
- `Esc` - Cancel
|
|
51
|
-
- `↑↓` - Navigate between steps
|
|
52
|
-
- `e` - Edit task/template
|
|
53
|
-
- `m` - Select model (
|
|
54
|
-
- `t` - Select thinking level (
|
|
55
|
-
- `w` - Edit writes
|
|
56
|
-
- `r` - Edit reads list
|
|
57
|
-
- `p` - Toggle progress tracking
|
|
65
|
+
- `↑↓` - Navigate between steps/tasks (parallel, chain)
|
|
66
|
+
- `e` - Edit task/template (all modes)
|
|
67
|
+
- `m` - Select model (all modes)
|
|
68
|
+
- `t` - Select thinking level (all modes)
|
|
69
|
+
- `w` - Edit writes/output file (single, chain only)
|
|
70
|
+
- `r` - Edit reads list (chain only)
|
|
71
|
+
- `p` - Toggle progress tracking (chain only)
|
|
58
72
|
|
|
59
73
|
*Model selector mode:*
|
|
60
74
|
- `↑↓` - Navigate model list
|
package/chain-clarify.ts
CHANGED
|
@@ -11,6 +11,9 @@ import { matchesKey, visibleWidth, truncateToWidth } from "@mariozechner/pi-tui"
|
|
|
11
11
|
import type { AgentConfig } from "./agents.js";
|
|
12
12
|
import type { ResolvedStepBehavior } from "./settings.js";
|
|
13
13
|
|
|
14
|
+
/** Clarify TUI mode */
|
|
15
|
+
export type ClarifyMode = 'single' | 'parallel' | 'chain';
|
|
16
|
+
|
|
14
17
|
/** Model info for display */
|
|
15
18
|
export interface ModelInfo {
|
|
16
19
|
provider: string;
|
|
@@ -76,10 +79,11 @@ export class ChainClarifyComponent implements Component {
|
|
|
76
79
|
private agentConfigs: AgentConfig[],
|
|
77
80
|
private templates: string[],
|
|
78
81
|
private originalTask: string,
|
|
79
|
-
private chainDir: string,
|
|
82
|
+
private chainDir: string | undefined, // undefined for single/parallel modes
|
|
80
83
|
private resolvedBehaviors: ResolvedStepBehavior[],
|
|
81
84
|
private availableModels: ModelInfo[],
|
|
82
85
|
private done: (result: ChainClarifyResult) => void,
|
|
86
|
+
private mode: ClarifyMode = 'chain', // Mode: 'single', 'parallel', or 'chain'
|
|
83
87
|
) {
|
|
84
88
|
// Initialize filtered models
|
|
85
89
|
this.filteredModels = [...availableModels];
|
|
@@ -222,11 +226,17 @@ export class ChainClarifyComponent implements Component {
|
|
|
222
226
|
// Header (truncate agent name to prevent overflow)
|
|
223
227
|
const fieldName = this.editMode === "template" ? "task" : this.editMode;
|
|
224
228
|
const rawAgentName = this.agentConfigs[this.editingStep!]?.name ?? "unknown";
|
|
225
|
-
const maxAgentLen = innerW - 30; // Reserve space for " Editing X (Step N: ) "
|
|
229
|
+
const maxAgentLen = innerW - 30; // Reserve space for " Editing X (Step/Task N: ) "
|
|
226
230
|
const agentName = rawAgentName.length > maxAgentLen
|
|
227
231
|
? rawAgentName.slice(0, maxAgentLen - 1) + "…"
|
|
228
232
|
: rawAgentName;
|
|
229
|
-
|
|
233
|
+
// Use mode-appropriate terminology
|
|
234
|
+
const stepLabel = this.mode === 'single'
|
|
235
|
+
? agentName
|
|
236
|
+
: this.mode === 'parallel'
|
|
237
|
+
? `Task ${this.editingStep! + 1}: ${agentName}`
|
|
238
|
+
: `Step ${this.editingStep! + 1}: ${agentName}`;
|
|
239
|
+
const headerText = ` Editing ${fieldName} (${stepLabel}) `;
|
|
230
240
|
lines.push(this.renderHeader(headerText));
|
|
231
241
|
lines.push(this.row(""));
|
|
232
242
|
|
|
@@ -365,26 +375,38 @@ export class ChainClarifyComponent implements Component {
|
|
|
365
375
|
return;
|
|
366
376
|
}
|
|
367
377
|
|
|
368
|
-
// 'e' to edit template
|
|
378
|
+
// 'e' to edit template (all modes)
|
|
369
379
|
if (data === "e") {
|
|
370
380
|
this.enterEditMode("template");
|
|
371
381
|
return;
|
|
372
382
|
}
|
|
373
383
|
|
|
374
|
-
// '
|
|
375
|
-
if (data === "
|
|
384
|
+
// 'm' to select model (all modes)
|
|
385
|
+
if (data === "m") {
|
|
386
|
+
this.enterModelSelector();
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// 't' to select thinking level (all modes)
|
|
391
|
+
if (data === "t") {
|
|
392
|
+
this.enterThinkingSelector();
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// 'w' to edit writes (single and chain only - not parallel)
|
|
397
|
+
if (data === "w" && this.mode !== 'parallel') {
|
|
376
398
|
this.enterEditMode("output");
|
|
377
399
|
return;
|
|
378
400
|
}
|
|
379
401
|
|
|
380
|
-
// 'r' to edit reads
|
|
381
|
-
if (data === "r") {
|
|
402
|
+
// 'r' to edit reads (chain only)
|
|
403
|
+
if (data === "r" && this.mode === 'chain') {
|
|
382
404
|
this.enterEditMode("reads");
|
|
383
405
|
return;
|
|
384
406
|
}
|
|
385
407
|
|
|
386
|
-
// 'p' to toggle progress for ALL steps (chains share a single progress.md)
|
|
387
|
-
if (data === "p") {
|
|
408
|
+
// 'p' to toggle progress for ALL steps (chain only - chains share a single progress.md)
|
|
409
|
+
if (data === "p" && this.mode === 'chain') {
|
|
388
410
|
// Check if any step has progress enabled
|
|
389
411
|
const anyEnabled = this.agentConfigs.some((_, i) => this.getEffectiveBehavior(i).progress);
|
|
390
412
|
// Toggle all steps to the opposite state
|
|
@@ -395,18 +417,6 @@ export class ChainClarifyComponent implements Component {
|
|
|
395
417
|
this.tui.requestRender();
|
|
396
418
|
return;
|
|
397
419
|
}
|
|
398
|
-
|
|
399
|
-
// 'm' to select model
|
|
400
|
-
if (data === "m") {
|
|
401
|
-
this.enterModelSelector();
|
|
402
|
-
return;
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
// 't' to select thinking level
|
|
406
|
-
if (data === "t") {
|
|
407
|
-
this.enterThinkingSelector();
|
|
408
|
-
return;
|
|
409
|
-
}
|
|
410
420
|
}
|
|
411
421
|
|
|
412
422
|
private enterEditMode(mode: EditMode): void {
|
|
@@ -809,7 +819,12 @@ export class ChainClarifyComponent implements Component {
|
|
|
809
819
|
}
|
|
810
820
|
return this.renderFullEditMode();
|
|
811
821
|
}
|
|
812
|
-
|
|
822
|
+
// Mode-based navigation rendering
|
|
823
|
+
switch (this.mode) {
|
|
824
|
+
case 'single': return this.renderSingleMode();
|
|
825
|
+
case 'parallel': return this.renderParallelMode();
|
|
826
|
+
case 'chain': return this.renderChainMode();
|
|
827
|
+
}
|
|
813
828
|
}
|
|
814
829
|
|
|
815
830
|
/** Render the model selector view */
|
|
@@ -818,9 +833,14 @@ export class ChainClarifyComponent implements Component {
|
|
|
818
833
|
const th = this.theme;
|
|
819
834
|
const lines: string[] = [];
|
|
820
835
|
|
|
821
|
-
// Header
|
|
836
|
+
// Header (mode-aware terminology)
|
|
822
837
|
const agentName = this.agentConfigs[this.editingStep!]?.name ?? "unknown";
|
|
823
|
-
const
|
|
838
|
+
const stepLabel = this.mode === 'single'
|
|
839
|
+
? agentName
|
|
840
|
+
: this.mode === 'parallel'
|
|
841
|
+
? `Task ${this.editingStep! + 1}: ${agentName}`
|
|
842
|
+
: `Step ${this.editingStep! + 1}: ${agentName}`;
|
|
843
|
+
const headerText = ` Select Model (${stepLabel}) `;
|
|
824
844
|
lines.push(this.renderHeader(headerText));
|
|
825
845
|
lines.push(this.row(""));
|
|
826
846
|
|
|
@@ -898,9 +918,14 @@ export class ChainClarifyComponent implements Component {
|
|
|
898
918
|
const th = this.theme;
|
|
899
919
|
const lines: string[] = [];
|
|
900
920
|
|
|
901
|
-
// Header
|
|
921
|
+
// Header (mode-aware terminology)
|
|
902
922
|
const agentName = this.agentConfigs[this.editingStep!]?.name ?? "unknown";
|
|
903
|
-
const
|
|
923
|
+
const stepLabel = this.mode === 'single'
|
|
924
|
+
? agentName
|
|
925
|
+
: this.mode === 'parallel'
|
|
926
|
+
? `Task ${this.editingStep! + 1}: ${agentName}`
|
|
927
|
+
: `Step ${this.editingStep! + 1}: ${agentName}`;
|
|
928
|
+
const headerText = ` Thinking Level (${stepLabel}) `;
|
|
904
929
|
lines.push(this.renderHeader(headerText));
|
|
905
930
|
lines.push(this.row(""));
|
|
906
931
|
|
|
@@ -947,8 +972,122 @@ export class ChainClarifyComponent implements Component {
|
|
|
947
972
|
return lines;
|
|
948
973
|
}
|
|
949
974
|
|
|
950
|
-
/**
|
|
951
|
-
private
|
|
975
|
+
/** Get footer text based on mode */
|
|
976
|
+
private getFooterText(): string {
|
|
977
|
+
switch (this.mode) {
|
|
978
|
+
case 'single':
|
|
979
|
+
return ' [Enter] Run • [Esc] Cancel • [e]dit [m]odel [t]hinking [w]rites ';
|
|
980
|
+
case 'parallel':
|
|
981
|
+
return ' [Enter] Run • [Esc] Cancel • [e]dit [m]odel [t]hinking • ↑↓ Navigate ';
|
|
982
|
+
case 'chain':
|
|
983
|
+
return ' [Enter] Run • [Esc] Cancel • [e]dit [m]odel [t]hinking [w]rites [r]eads [p]rogress ';
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
/** Render single agent mode (simplified view) */
|
|
988
|
+
private renderSingleMode(): string[] {
|
|
989
|
+
const innerW = this.width - 2;
|
|
990
|
+
const th = this.theme;
|
|
991
|
+
const lines: string[] = [];
|
|
992
|
+
|
|
993
|
+
// Header with agent name
|
|
994
|
+
const agentName = this.agentConfigs[0]?.name ?? "unknown";
|
|
995
|
+
const maxHeaderLen = innerW - 4;
|
|
996
|
+
const headerText = ` Agent: ${truncateToWidth(agentName, maxHeaderLen - 9)} `;
|
|
997
|
+
lines.push(this.renderHeader(headerText));
|
|
998
|
+
lines.push(this.row(""));
|
|
999
|
+
|
|
1000
|
+
// Single step - always index 0, always selected
|
|
1001
|
+
const config = this.agentConfigs[0]!;
|
|
1002
|
+
const behavior = this.getEffectiveBehavior(0);
|
|
1003
|
+
|
|
1004
|
+
// Agent name with selection indicator
|
|
1005
|
+
const stepLabel = config.name;
|
|
1006
|
+
lines.push(this.row(` ${th.fg("accent", "▶ " + stepLabel)}`));
|
|
1007
|
+
|
|
1008
|
+
// Task line
|
|
1009
|
+
const template = (this.templates[0] ?? "").split("\n")[0] ?? "";
|
|
1010
|
+
const taskLabel = th.fg("dim", "task: ");
|
|
1011
|
+
lines.push(this.row(` ${taskLabel}${truncateToWidth(template, innerW - 12)}`));
|
|
1012
|
+
|
|
1013
|
+
// Model line
|
|
1014
|
+
const effectiveModel = this.getEffectiveModel(0);
|
|
1015
|
+
const override = this.behaviorOverrides.get(0);
|
|
1016
|
+
const isOverridden = override?.model !== undefined;
|
|
1017
|
+
const modelValue = isOverridden
|
|
1018
|
+
? th.fg("warning", effectiveModel) + th.fg("dim", " ✎")
|
|
1019
|
+
: effectiveModel;
|
|
1020
|
+
const modelLabel = th.fg("dim", "model: ");
|
|
1021
|
+
lines.push(this.row(` ${modelLabel}${truncateToWidth(modelValue, innerW - 13)}`));
|
|
1022
|
+
|
|
1023
|
+
// Writes line (output file)
|
|
1024
|
+
const writesValue = behavior.output === false
|
|
1025
|
+
? th.fg("dim", "(disabled)")
|
|
1026
|
+
: (behavior.output || th.fg("dim", "(none)"));
|
|
1027
|
+
const writesLabel = th.fg("dim", "writes: ");
|
|
1028
|
+
lines.push(this.row(` ${writesLabel}${truncateToWidth(writesValue, innerW - 14)}`));
|
|
1029
|
+
|
|
1030
|
+
lines.push(this.row(""));
|
|
1031
|
+
|
|
1032
|
+
// Footer
|
|
1033
|
+
lines.push(this.renderFooter(this.getFooterText()));
|
|
1034
|
+
|
|
1035
|
+
return lines;
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
/** Render parallel mode (multi-task view without chain features) */
|
|
1039
|
+
private renderParallelMode(): string[] {
|
|
1040
|
+
const innerW = this.width - 2;
|
|
1041
|
+
const th = this.theme;
|
|
1042
|
+
const lines: string[] = [];
|
|
1043
|
+
|
|
1044
|
+
// Header with task count
|
|
1045
|
+
const headerText = ` Parallel Tasks (${this.agentConfigs.length}) `;
|
|
1046
|
+
lines.push(this.renderHeader(headerText));
|
|
1047
|
+
lines.push(this.row(""));
|
|
1048
|
+
|
|
1049
|
+
// Each task
|
|
1050
|
+
for (let i = 0; i < this.agentConfigs.length; i++) {
|
|
1051
|
+
const config = this.agentConfigs[i]!;
|
|
1052
|
+
const isSelected = i === this.selectedStep;
|
|
1053
|
+
|
|
1054
|
+
// Task header (truncate agent name to prevent overflow)
|
|
1055
|
+
const color = isSelected ? "accent" : "dim";
|
|
1056
|
+
const prefix = isSelected ? "▶ " : " ";
|
|
1057
|
+
const taskPrefix = `Task ${i + 1}: `;
|
|
1058
|
+
const maxNameLen = innerW - 4 - prefix.length - taskPrefix.length;
|
|
1059
|
+
const agentName = config.name.length > maxNameLen
|
|
1060
|
+
? config.name.slice(0, maxNameLen - 1) + "…"
|
|
1061
|
+
: config.name;
|
|
1062
|
+
const taskLabel = `${taskPrefix}${agentName}`;
|
|
1063
|
+
lines.push(this.row(` ${th.fg(color, prefix + taskLabel)}`));
|
|
1064
|
+
|
|
1065
|
+
// Task line
|
|
1066
|
+
const template = (this.templates[i] ?? "").split("\n")[0] ?? "";
|
|
1067
|
+
const taskTextLabel = th.fg("dim", "task: ");
|
|
1068
|
+
lines.push(this.row(` ${taskTextLabel}${truncateToWidth(template, innerW - 12)}`));
|
|
1069
|
+
|
|
1070
|
+
// Model line
|
|
1071
|
+
const effectiveModel = this.getEffectiveModel(i);
|
|
1072
|
+
const override = this.behaviorOverrides.get(i);
|
|
1073
|
+
const isOverridden = override?.model !== undefined;
|
|
1074
|
+
const modelValue = isOverridden
|
|
1075
|
+
? th.fg("warning", effectiveModel) + th.fg("dim", " ✎")
|
|
1076
|
+
: effectiveModel;
|
|
1077
|
+
const modelLabel = th.fg("dim", "model: ");
|
|
1078
|
+
lines.push(this.row(` ${modelLabel}${truncateToWidth(modelValue, innerW - 13)}`));
|
|
1079
|
+
|
|
1080
|
+
lines.push(this.row(""));
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
// Footer
|
|
1084
|
+
lines.push(this.renderFooter(this.getFooterText()));
|
|
1085
|
+
|
|
1086
|
+
return lines;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
/** Render chain mode (step selection, preview) */
|
|
1090
|
+
private renderChainMode(): string[] {
|
|
952
1091
|
const innerW = this.width - 2;
|
|
953
1092
|
const th = this.theme;
|
|
954
1093
|
const lines: string[] = [];
|
|
@@ -964,7 +1103,8 @@ export class ChainClarifyComponent implements Component {
|
|
|
964
1103
|
// Original task (truncated) and chain dir
|
|
965
1104
|
const taskPreview = truncateToWidth(this.originalTask, innerW - 16);
|
|
966
1105
|
lines.push(this.row(` Original Task: ${taskPreview}`));
|
|
967
|
-
|
|
1106
|
+
// chainDir is guaranteed to be defined in chain mode
|
|
1107
|
+
const chainDirPreview = truncateToWidth(this.chainDir ?? "", innerW - 12);
|
|
968
1108
|
lines.push(this.row(` Chain Dir: ${th.fg("dim", chainDirPreview)}`));
|
|
969
1109
|
|
|
970
1110
|
// Chain-wide progress setting
|
|
@@ -1053,8 +1193,7 @@ export class ChainClarifyComponent implements Component {
|
|
|
1053
1193
|
}
|
|
1054
1194
|
|
|
1055
1195
|
// Footer with keybindings
|
|
1056
|
-
|
|
1057
|
-
lines.push(this.renderFooter(footerText));
|
|
1196
|
+
lines.push(this.renderFooter(this.getFooterText()));
|
|
1058
1197
|
|
|
1059
1198
|
return lines;
|
|
1060
1199
|
}
|
package/index.ts
CHANGED
|
@@ -19,7 +19,8 @@ import * as path from "node:path";
|
|
|
19
19
|
import { type ExtensionAPI, type ExtensionContext, type ToolDefinition } from "@mariozechner/pi-coding-agent";
|
|
20
20
|
import { Text } from "@mariozechner/pi-tui";
|
|
21
21
|
import { type AgentConfig, type AgentScope, discoverAgents } from "./agents.js";
|
|
22
|
-
import { cleanupOldChainDirs, getStepAgents, isParallelStep, type ChainStep, type SequentialStep } from "./settings.js";
|
|
22
|
+
import { cleanupOldChainDirs, getStepAgents, isParallelStep, resolveStepBehavior, type ChainStep, type SequentialStep } from "./settings.js";
|
|
23
|
+
import { ChainClarifyComponent, type ChainClarifyResult, type ModelInfo } from "./chain-clarify.js";
|
|
23
24
|
import { cleanupOldArtifacts, getArtifactsDir } from "./artifacts.js";
|
|
24
25
|
import {
|
|
25
26
|
type AgentProgress,
|
|
@@ -188,8 +189,13 @@ Example: { chain: [{agent:"scout", task:"Analyze {task}"}, {agent:"planner", tas
|
|
|
188
189
|
const requestedAsync = params.async ?? asyncByDefault;
|
|
189
190
|
const parallelDowngraded = hasTasks && requestedAsync;
|
|
190
191
|
// clarify implies sync mode (TUI is blocking)
|
|
191
|
-
//
|
|
192
|
-
|
|
192
|
+
// - Chains default to TUI (clarify: true), so async requires explicit clarify: false
|
|
193
|
+
// - Single defaults to no TUI, so async is allowed unless clarify: true is passed
|
|
194
|
+
const effectiveAsync = requestedAsync && !hasTasks && (
|
|
195
|
+
hasChain
|
|
196
|
+
? params.clarify === false // chains: only async if TUI explicitly disabled
|
|
197
|
+
: params.clarify !== true // single: async unless TUI explicitly enabled
|
|
198
|
+
);
|
|
193
199
|
|
|
194
200
|
const artifactConfig: ArtifactConfig = {
|
|
195
201
|
...DEFAULT_ARTIFACT_CONFIG,
|
|
@@ -336,14 +342,74 @@ Example: { chain: [{agent:"scout", task:"Analyze {task}"}, {agent:"planner", tas
|
|
|
336
342
|
}
|
|
337
343
|
|
|
338
344
|
if (hasTasks && params.tasks) {
|
|
345
|
+
// MAX_PARALLEL check first (fail fast before TUI)
|
|
339
346
|
if (params.tasks.length > MAX_PARALLEL)
|
|
340
347
|
return {
|
|
341
348
|
content: [{ type: "text", text: `Max ${MAX_PARALLEL} tasks` }],
|
|
342
349
|
isError: true,
|
|
343
|
-
details: { mode: "
|
|
350
|
+
details: { mode: "parallel" as const, results: [] },
|
|
344
351
|
};
|
|
352
|
+
|
|
353
|
+
// Validate all agents exist
|
|
354
|
+
const agentConfigs: AgentConfig[] = [];
|
|
355
|
+
for (const t of params.tasks) {
|
|
356
|
+
const config = agents.find(a => a.name === t.agent);
|
|
357
|
+
if (!config) {
|
|
358
|
+
return {
|
|
359
|
+
content: [{ type: "text", text: `Unknown agent: ${t.agent}` }],
|
|
360
|
+
isError: true,
|
|
361
|
+
details: { mode: "parallel" as const, results: [] },
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
agentConfigs.push(config);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Mutable copies for TUI modifications
|
|
368
|
+
let tasks = params.tasks.map(t => t.task);
|
|
369
|
+
const modelOverrides: (string | undefined)[] = new Array(params.tasks.length).fill(undefined);
|
|
370
|
+
|
|
371
|
+
// Show clarify TUI if requested
|
|
372
|
+
if (params.clarify === true && ctx.hasUI) {
|
|
373
|
+
// Get available models (same pattern as chain-execution.ts)
|
|
374
|
+
const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map((m) => ({
|
|
375
|
+
provider: m.provider,
|
|
376
|
+
id: m.id,
|
|
377
|
+
fullId: `${m.provider}/${m.id}`,
|
|
378
|
+
}));
|
|
379
|
+
|
|
380
|
+
const behaviors = agentConfigs.map(c => resolveStepBehavior(c, {}));
|
|
381
|
+
|
|
382
|
+
const result = await ctx.ui.custom<ChainClarifyResult>(
|
|
383
|
+
(tui, theme, _kb, done) =>
|
|
384
|
+
new ChainClarifyComponent(
|
|
385
|
+
tui, theme,
|
|
386
|
+
agentConfigs,
|
|
387
|
+
tasks,
|
|
388
|
+
'', // no originalTask for parallel (each task is independent)
|
|
389
|
+
undefined, // no chainDir for parallel
|
|
390
|
+
behaviors,
|
|
391
|
+
availableModels,
|
|
392
|
+
done,
|
|
393
|
+
'parallel', // mode
|
|
394
|
+
),
|
|
395
|
+
{ overlay: true, overlayOptions: { anchor: 'center', width: 84, maxHeight: '80%' } },
|
|
396
|
+
);
|
|
397
|
+
|
|
398
|
+
if (!result || !result.confirmed) {
|
|
399
|
+
return { content: [{ type: 'text', text: 'Cancelled' }], details: { mode: 'parallel', results: [] } };
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Apply TUI overrides
|
|
403
|
+
tasks = result.templates;
|
|
404
|
+
for (let i = 0; i < result.behaviorOverrides.length; i++) {
|
|
405
|
+
const override = result.behaviorOverrides[i];
|
|
406
|
+
if (override?.model) modelOverrides[i] = override.model;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Execute with overrides (tasks array has same length as params.tasks)
|
|
345
411
|
const results = await mapConcurrent(params.tasks, MAX_CONCURRENCY, async (t, i) =>
|
|
346
|
-
runSync(ctx.cwd, agents, t.agent,
|
|
412
|
+
runSync(ctx.cwd, agents, t.agent, tasks[i]!, {
|
|
347
413
|
cwd: t.cwd ?? params.cwd,
|
|
348
414
|
signal,
|
|
349
415
|
runId,
|
|
@@ -353,6 +419,7 @@ Example: { chain: [{agent:"scout", task:"Analyze {task}"}, {agent:"planner", tas
|
|
|
353
419
|
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
354
420
|
artifactConfig,
|
|
355
421
|
maxOutput: params.maxOutput,
|
|
422
|
+
modelOverride: modelOverrides[i],
|
|
356
423
|
}),
|
|
357
424
|
);
|
|
358
425
|
|
|
@@ -377,24 +444,67 @@ Example: { chain: [{agent:"scout", task:"Analyze {task}"}, {agent:"planner", tas
|
|
|
377
444
|
if (hasSingle) {
|
|
378
445
|
// Look up agent config for output handling
|
|
379
446
|
const agentConfig = agents.find((a) => a.name === params.agent);
|
|
380
|
-
|
|
447
|
+
if (!agentConfig) {
|
|
448
|
+
return {
|
|
449
|
+
content: [{ type: 'text', text: `Unknown agent: ${params.agent}` }],
|
|
450
|
+
isError: true,
|
|
451
|
+
details: { mode: 'single', results: [] },
|
|
452
|
+
};
|
|
453
|
+
}
|
|
381
454
|
|
|
382
455
|
let task = params.task!;
|
|
383
|
-
let
|
|
456
|
+
let modelOverride: string | undefined;
|
|
457
|
+
// Normalize output: true means "use default" (same as undefined), false means disable
|
|
458
|
+
const rawOutput = params.output !== undefined ? params.output : agentConfig.output;
|
|
459
|
+
let effectiveOutput: string | false | undefined = rawOutput === true ? agentConfig.output : rawOutput;
|
|
460
|
+
|
|
461
|
+
// Show clarify TUI if requested
|
|
462
|
+
if (params.clarify === true && ctx.hasUI) {
|
|
463
|
+
// Get available models (same pattern as chain-execution.ts)
|
|
464
|
+
const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map((m) => ({
|
|
465
|
+
provider: m.provider,
|
|
466
|
+
id: m.id,
|
|
467
|
+
fullId: `${m.provider}/${m.id}`,
|
|
468
|
+
}));
|
|
469
|
+
|
|
470
|
+
const behavior = resolveStepBehavior(agentConfig, { output: effectiveOutput });
|
|
471
|
+
|
|
472
|
+
const result = await ctx.ui.custom<ChainClarifyResult>(
|
|
473
|
+
(tui, theme, _kb, done) =>
|
|
474
|
+
new ChainClarifyComponent(
|
|
475
|
+
tui, theme,
|
|
476
|
+
[agentConfig],
|
|
477
|
+
[task],
|
|
478
|
+
task,
|
|
479
|
+
undefined, // no chainDir for single
|
|
480
|
+
[behavior],
|
|
481
|
+
availableModels,
|
|
482
|
+
done,
|
|
483
|
+
'single', // mode
|
|
484
|
+
),
|
|
485
|
+
{ overlay: true, overlayOptions: { anchor: 'center', width: 84, maxHeight: '80%' } },
|
|
486
|
+
);
|
|
487
|
+
|
|
488
|
+
if (!result || !result.confirmed) {
|
|
489
|
+
return { content: [{ type: 'text', text: 'Cancelled' }], details: { mode: 'single', results: [] } };
|
|
490
|
+
}
|
|
384
491
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
const
|
|
388
|
-
|
|
492
|
+
// Apply TUI overrides
|
|
493
|
+
task = result.templates[0]!;
|
|
494
|
+
const override = result.behaviorOverrides[0];
|
|
495
|
+
if (override?.model) modelOverride = override.model;
|
|
496
|
+
if (override?.output !== undefined) effectiveOutput = override.output;
|
|
497
|
+
}
|
|
389
498
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
499
|
+
// Compute output path at runtime (uses effectiveOutput which may be TUI-modified)
|
|
500
|
+
let outputPath: string | undefined;
|
|
501
|
+
if (effectiveOutput && effectiveOutput !== false) {
|
|
502
|
+
const outputDir = `/tmp/pi-${agentConfig.name}-${runId}`;
|
|
503
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
504
|
+
outputPath = `${outputDir}/${effectiveOutput}`;
|
|
394
505
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
}
|
|
506
|
+
// Inject output instruction into task
|
|
507
|
+
task += `\n\n---\n**Output:** Write your findings to: ${outputPath}`;
|
|
398
508
|
}
|
|
399
509
|
|
|
400
510
|
const r = await runSync(ctx.cwd, agents, params.agent!, task, {
|
|
@@ -407,6 +517,7 @@ Example: { chain: [{agent:"scout", task:"Analyze {task}"}, {agent:"planner", tas
|
|
|
407
517
|
artifactConfig,
|
|
408
518
|
maxOutput: params.maxOutput,
|
|
409
519
|
onUpdate,
|
|
520
|
+
modelOverride,
|
|
410
521
|
});
|
|
411
522
|
|
|
412
523
|
if (r.progress) allProgress.push(r.progress);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-subagents",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
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",
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"url": "https://github.com/nicobailon/pi-subagents/issues"
|
|
14
14
|
},
|
|
15
15
|
"keywords": [
|
|
16
|
+
"pi-package",
|
|
16
17
|
"pi",
|
|
17
18
|
"pi-coding-agent",
|
|
18
19
|
"subagents",
|
package/schemas.ts
CHANGED
|
@@ -77,8 +77,8 @@ export const SubagentParams = Type.Object({
|
|
|
77
77
|
sessionDir: Type.Optional(
|
|
78
78
|
Type.String({ description: "Directory to store session logs (default: temp; enables sessions even if share=false)" }),
|
|
79
79
|
),
|
|
80
|
-
//
|
|
81
|
-
clarify: Type.Optional(Type.Boolean({ description: "Show TUI to
|
|
80
|
+
// Clarification TUI
|
|
81
|
+
clarify: Type.Optional(Type.Boolean({ description: "Show TUI to preview/edit before execution (default: true for chains, false for single/parallel). Implies sync mode." })),
|
|
82
82
|
// Solo agent output override
|
|
83
83
|
output: Type.Optional(Type.Union([
|
|
84
84
|
Type.String(),
|