pi-subagents 0.3.0 → 0.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-subagents",
3
- "version": "0.3.0",
3
+ "version": "0.3.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",
@@ -21,7 +21,7 @@
21
21
  "cli"
22
22
  ],
23
23
  "bin": {
24
- "pi-subagents": "./install.mjs"
24
+ "pi-subagents": "install.mjs"
25
25
  },
26
26
  "files": [
27
27
  "*.ts",
package/render.ts ADDED
@@ -0,0 +1,283 @@
1
+ /**
2
+ * Rendering functions for subagent results
3
+ */
4
+
5
+ import type { AgentToolResult } from "@mariozechner/pi-agent-core";
6
+ import { getMarkdownTheme, type ExtensionContext } from "@mariozechner/pi-coding-agent";
7
+ import { Container, Markdown, Spacer, Text, type Widget } from "@mariozechner/pi-tui";
8
+ import {
9
+ type AsyncJobState,
10
+ type Details,
11
+ MAX_WIDGET_JOBS,
12
+ WIDGET_KEY,
13
+ } from "./types.js";
14
+ import { formatTokens, formatUsage, formatDuration, formatToolCall, shortenPath } from "./formatters.js";
15
+ import { getFinalOutput, getDisplayItems, getOutputTail, getLastActivity } from "./utils.js";
16
+
17
+ type Theme = ExtensionContext["ui"]["theme"];
18
+
19
+ /**
20
+ * Render the async jobs widget
21
+ */
22
+ export function renderWidget(ctx: ExtensionContext, jobs: AsyncJobState[]): void {
23
+ if (!ctx.hasUI) return;
24
+ if (jobs.length === 0) {
25
+ ctx.ui.setWidget(WIDGET_KEY, undefined);
26
+ return;
27
+ }
28
+
29
+ const theme = ctx.ui.theme;
30
+ const lines: string[] = [];
31
+ lines.push(theme.fg("accent", "Async subagents"));
32
+
33
+ for (const job of jobs.slice(0, MAX_WIDGET_JOBS)) {
34
+ const id = job.asyncId.slice(0, 6);
35
+ const status =
36
+ job.status === "complete"
37
+ ? theme.fg("success", "complete")
38
+ : job.status === "failed"
39
+ ? theme.fg("error", "failed")
40
+ : theme.fg("warning", "running");
41
+
42
+ const stepsTotal = job.stepsTotal ?? (job.agents?.length ?? 1);
43
+ const stepIndex = job.currentStep !== undefined ? job.currentStep + 1 : undefined;
44
+ const stepText = stepIndex !== undefined ? `step ${stepIndex}/${stepsTotal}` : `steps ${stepsTotal}`;
45
+ const endTime = (job.status === "complete" || job.status === "failed") ? (job.updatedAt ?? Date.now()) : Date.now();
46
+ const elapsed = job.startedAt ? formatDuration(endTime - job.startedAt) : "";
47
+ const agentLabel = job.agents ? job.agents.join(" -> ") : (job.mode ?? "single");
48
+
49
+ const tokenText = job.totalTokens ? ` | ${formatTokens(job.totalTokens.total)} tok` : "";
50
+ const activityText = job.status === "running" ? getLastActivity(job.outputFile) : "";
51
+ const activitySuffix = activityText ? ` | ${theme.fg("dim", activityText)}` : "";
52
+
53
+ lines.push(`- ${id} ${status} | ${agentLabel} | ${stepText}${elapsed ? ` | ${elapsed}` : ""}${tokenText}${activitySuffix}`);
54
+
55
+ if (job.status === "running" && job.outputFile) {
56
+ const tail = getOutputTail(job.outputFile, 3);
57
+ for (const line of tail) {
58
+ lines.push(theme.fg("dim", ` > ${line}`));
59
+ }
60
+ }
61
+ }
62
+
63
+ ctx.ui.setWidget(WIDGET_KEY, lines);
64
+ }
65
+
66
+ /**
67
+ * Render a subagent result
68
+ */
69
+ export function renderSubagentResult(
70
+ result: AgentToolResult<Details>,
71
+ _options: { expanded: boolean },
72
+ theme: Theme,
73
+ ): Widget {
74
+ const d = result.details;
75
+ if (!d || !d.results.length) {
76
+ const t = result.content[0];
77
+ return new Text(t?.type === "text" ? t.text : "(no output)", 0, 0);
78
+ }
79
+
80
+ const mdTheme = getMarkdownTheme();
81
+
82
+ if (d.mode === "single" && d.results.length === 1) {
83
+ const r = d.results[0];
84
+ const isRunning = r.progress?.status === "running";
85
+ const icon = isRunning
86
+ ? theme.fg("warning", "...")
87
+ : r.exitCode === 0
88
+ ? theme.fg("success", "ok")
89
+ : theme.fg("error", "X");
90
+ const output = r.truncation?.text || getFinalOutput(r.messages);
91
+
92
+ const progressInfo = isRunning && r.progress
93
+ ? ` | ${r.progress.toolCount} tools, ${formatTokens(r.progress.tokens)} tok, ${formatDuration(r.progress.durationMs)}`
94
+ : r.progressSummary
95
+ ? ` | ${r.progressSummary.toolCount} tools, ${formatTokens(r.progressSummary.tokens)} tok, ${formatDuration(r.progressSummary.durationMs)}`
96
+ : "";
97
+
98
+ const c = new Container();
99
+ c.addChild(new Text(`${icon} ${theme.fg("toolTitle", theme.bold(r.agent))}${progressInfo}`, 0, 0));
100
+ c.addChild(new Spacer(1));
101
+ c.addChild(
102
+ new Text(theme.fg("dim", `Task: ${r.task.slice(0, 150)}${r.task.length > 150 ? "..." : ""}`), 0, 0),
103
+ );
104
+ c.addChild(new Spacer(1));
105
+
106
+ const items = getDisplayItems(r.messages);
107
+ for (const item of items) {
108
+ if (item.type === "tool")
109
+ c.addChild(new Text(theme.fg("muted", formatToolCall(item.name, item.args)), 0, 0));
110
+ }
111
+ if (items.length) c.addChild(new Spacer(1));
112
+
113
+ if (output) c.addChild(new Markdown(output, 0, 0, mdTheme));
114
+ c.addChild(new Spacer(1));
115
+ c.addChild(new Text(theme.fg("dim", formatUsage(r.usage, r.model)), 0, 0));
116
+ if (r.sessionFile) {
117
+ c.addChild(new Text(theme.fg("dim", `Session: ${shortenPath(r.sessionFile)}`), 0, 0));
118
+ }
119
+
120
+ if (r.artifactPaths) {
121
+ c.addChild(new Spacer(1));
122
+ c.addChild(new Text(theme.fg("dim", `Artifacts: ${shortenPath(r.artifactPaths.outputPath)}`), 0, 0));
123
+ }
124
+ return c;
125
+ }
126
+
127
+ const hasRunning = d.progress?.some((p) => p.status === "running")
128
+ || d.results.some((r) => r.progress?.status === "running");
129
+ const ok = d.results.filter((r) => r.progress?.status === "completed" || (r.exitCode === 0 && r.progress?.status !== "running")).length;
130
+ const icon = hasRunning
131
+ ? theme.fg("warning", "...")
132
+ : ok === d.results.length
133
+ ? theme.fg("success", "ok")
134
+ : theme.fg("error", "X");
135
+
136
+ const totalSummary =
137
+ d.progressSummary ||
138
+ d.results.reduce(
139
+ (acc, r) => {
140
+ const prog = r.progress || r.progressSummary;
141
+ if (prog) {
142
+ acc.toolCount += prog.toolCount;
143
+ acc.tokens += prog.tokens;
144
+ acc.durationMs =
145
+ d.mode === "chain"
146
+ ? acc.durationMs + prog.durationMs
147
+ : Math.max(acc.durationMs, prog.durationMs);
148
+ }
149
+ return acc;
150
+ },
151
+ { toolCount: 0, tokens: 0, durationMs: 0 },
152
+ );
153
+
154
+ const summaryStr =
155
+ totalSummary.toolCount || totalSummary.tokens
156
+ ? ` | ${totalSummary.toolCount} tools, ${formatTokens(totalSummary.tokens)} tok, ${formatDuration(totalSummary.durationMs)}`
157
+ : "";
158
+
159
+ const modeLabel = d.mode === "parallel" ? "parallel (no live progress)" : d.mode;
160
+ // For parallel-in-chain, show task count (results) for consistency with step display
161
+ // For sequential chains, show logical step count
162
+ const hasParallelInChain = d.chainAgents?.some((a) => a.startsWith("["));
163
+ const totalCount = hasParallelInChain ? d.results.length : (d.totalSteps ?? d.results.length);
164
+ const currentStep = d.currentStepIndex !== undefined ? d.currentStepIndex + 1 : ok + 1;
165
+ const stepInfo = hasRunning ? ` ${currentStep}/${totalCount}` : ` ${ok}/${totalCount}`;
166
+
167
+ // Build chain visualization: "scout → planner" with status icons
168
+ // Note: Only works correctly for sequential chains. Chains with parallel steps
169
+ // (indicated by "[agent1+agent2]" format) have multiple results per step,
170
+ // breaking the 1:1 mapping between chainAgents and results.
171
+ const chainVis = d.chainAgents?.length && !hasParallelInChain
172
+ ? d.chainAgents
173
+ .map((agent, i) => {
174
+ const result = d.results[i];
175
+ const isFailed = result && result.exitCode !== 0 && result.progress?.status !== "running";
176
+ const isComplete = result && result.exitCode === 0 && result.progress?.status !== "running";
177
+ const isCurrent = i === (d.currentStepIndex ?? d.results.length);
178
+ const icon = isFailed
179
+ ? theme.fg("error", "✗")
180
+ : isComplete
181
+ ? theme.fg("success", "✓")
182
+ : isCurrent && hasRunning
183
+ ? theme.fg("warning", "●")
184
+ : theme.fg("dim", "○");
185
+ return `${icon} ${agent}`;
186
+ })
187
+ .join(theme.fg("dim", " → "))
188
+ : null;
189
+
190
+ const c = new Container();
191
+ c.addChild(
192
+ new Text(
193
+ `${icon} ${theme.fg("toolTitle", theme.bold(modeLabel))}${stepInfo}${summaryStr}`,
194
+ 0,
195
+ 0,
196
+ ),
197
+ );
198
+ // Show chain visualization
199
+ if (chainVis) {
200
+ c.addChild(new Text(` ${chainVis}`, 0, 0));
201
+ }
202
+
203
+ // === STATIC STEP LAYOUT (like clarification UI) ===
204
+ // Each step gets a fixed section with task/output/status
205
+ // Note: For chains with parallel steps, chainAgents indices don't map 1:1 to results
206
+ // (parallel steps produce multiple results). Fall back to result-based iteration.
207
+ const useResultsDirectly = hasParallelInChain || !d.chainAgents?.length;
208
+ const stepsToShow = useResultsDirectly ? d.results.length : d.chainAgents!.length;
209
+
210
+ c.addChild(new Spacer(1));
211
+
212
+ for (let i = 0; i < stepsToShow; i++) {
213
+ const r = d.results[i];
214
+ const agentName = useResultsDirectly
215
+ ? (r?.agent || `step-${i + 1}`)
216
+ : (d.chainAgents![i] || r?.agent || `step-${i + 1}`);
217
+
218
+ if (!r) {
219
+ // Pending step
220
+ c.addChild(new Text(theme.fg("dim", ` Step ${i + 1}: ${agentName}`), 0, 0));
221
+ c.addChild(new Text(theme.fg("dim", ` status: ○ pending`), 0, 0));
222
+ c.addChild(new Spacer(1));
223
+ continue;
224
+ }
225
+
226
+ const progressFromArray = d.progress?.find((p) => p.index === i)
227
+ || d.progress?.find((p) => p.agent === r.agent && p.status === "running");
228
+ const rProg = r.progress || progressFromArray || r.progressSummary;
229
+ const rRunning = rProg?.status === "running";
230
+
231
+ // Step header with status
232
+ const statusIcon = rRunning
233
+ ? theme.fg("warning", "●")
234
+ : r.exitCode === 0
235
+ ? theme.fg("success", "✓")
236
+ : theme.fg("error", "✗");
237
+ const stats = rProg ? ` | ${rProg.toolCount} tools, ${formatDuration(rProg.durationMs)}` : "";
238
+ const stepHeader = rRunning
239
+ ? `${statusIcon} Step ${i + 1}: ${theme.bold(theme.fg("warning", r.agent))}${stats}`
240
+ : `${statusIcon} Step ${i + 1}: ${theme.bold(r.agent)}${stats}`;
241
+ c.addChild(new Text(stepHeader, 0, 0));
242
+
243
+ // Task (truncated)
244
+ const taskPreview = r.task.slice(0, 120) + (r.task.length > 120 ? "..." : "");
245
+ c.addChild(new Text(theme.fg("dim", ` task: ${taskPreview}`), 0, 0));
246
+
247
+ // Output target (extract from task)
248
+ const outputMatch = r.task.match(/[Oo]utput(?:\s+to)?\s+([^\s]+\.(?:md|txt|json))/);
249
+ if (outputMatch) {
250
+ c.addChild(new Text(theme.fg("dim", ` output: ${outputMatch[1]}`), 0, 0));
251
+ }
252
+
253
+ if (rRunning && rProg) {
254
+ // Current tool for running step
255
+ if (rProg.currentTool) {
256
+ const toolLine = rProg.currentToolArgs
257
+ ? `${rProg.currentTool}: ${rProg.currentToolArgs.slice(0, 100)}${rProg.currentToolArgs.length > 100 ? "..." : ""}`
258
+ : rProg.currentTool;
259
+ c.addChild(new Text(theme.fg("warning", ` > ${toolLine}`), 0, 0));
260
+ }
261
+ // Recent tools
262
+ if (rProg.recentTools?.length) {
263
+ for (const t of rProg.recentTools.slice(0, 3)) {
264
+ const args = t.args.slice(0, 90) + (t.args.length > 90 ? "..." : "");
265
+ c.addChild(new Text(theme.fg("dim", ` ${t.tool}: ${args}`), 0, 0));
266
+ }
267
+ }
268
+ // Recent output (limited)
269
+ const recentLines = (rProg.recentOutput ?? []).slice(-5);
270
+ for (const line of recentLines) {
271
+ c.addChild(new Text(theme.fg("dim", ` ${line.slice(0, 100)}${line.length > 100 ? "..." : ""}`), 0, 0));
272
+ }
273
+ }
274
+
275
+ c.addChild(new Spacer(1));
276
+ }
277
+
278
+ if (d.artifacts) {
279
+ c.addChild(new Spacer(1));
280
+ c.addChild(new Text(theme.fg("dim", `Artifacts dir: ${shortenPath(d.artifacts.dir)}`), 0, 0));
281
+ }
282
+ return c;
283
+ }
package/schemas.ts ADDED
@@ -0,0 +1,90 @@
1
+ /**
2
+ * TypeBox schemas for subagent tool parameters
3
+ */
4
+
5
+ import { Type } from "@sinclair/typebox";
6
+
7
+ export const TaskItem = Type.Object({
8
+ agent: Type.String(),
9
+ task: Type.String(),
10
+ cwd: Type.Optional(Type.String())
11
+ });
12
+
13
+ // Sequential chain step (single agent)
14
+ export const SequentialStepSchema = Type.Object({
15
+ agent: Type.String(),
16
+ task: Type.Optional(Type.String({ description: "Task template. Use {task}, {previous}, {chain_dir}. Required for first step." })),
17
+ cwd: Type.Optional(Type.String()),
18
+ // Chain behavior overrides
19
+ output: Type.Optional(Type.Union([
20
+ Type.String(),
21
+ Type.Boolean(),
22
+ ], { description: "Override output filename (string), or false for text-only" })),
23
+ reads: Type.Optional(Type.Union([
24
+ Type.Array(Type.String()),
25
+ Type.Boolean(),
26
+ ], { description: "Override files to read from {chain_dir} (array), or false to disable" })),
27
+ progress: Type.Optional(Type.Boolean({ description: "Override progress tracking" })),
28
+ });
29
+
30
+ // Parallel task item (within a parallel step)
31
+ export const ParallelTaskSchema = Type.Object({
32
+ agent: Type.String(),
33
+ task: Type.Optional(Type.String({ description: "Task template. Defaults to {previous}." })),
34
+ cwd: Type.Optional(Type.String()),
35
+ output: Type.Optional(Type.Union([
36
+ Type.String(),
37
+ Type.Boolean(),
38
+ ], { description: "Override output filename (string), or false for text-only" })),
39
+ reads: Type.Optional(Type.Union([
40
+ Type.Array(Type.String()),
41
+ Type.Boolean(),
42
+ ], { description: "Override files to read from {chain_dir} (array), or false to disable" })),
43
+ progress: Type.Optional(Type.Boolean({ description: "Override progress tracking" })),
44
+ });
45
+
46
+ // Parallel chain step (multiple agents running concurrently)
47
+ export const ParallelStepSchema = Type.Object({
48
+ parallel: Type.Array(ParallelTaskSchema, { minItems: 1, description: "Tasks to run in parallel" }),
49
+ concurrency: Type.Optional(Type.Number({ description: "Max concurrent tasks (default: 4)" })),
50
+ failFast: Type.Optional(Type.Boolean({ description: "Stop on first failure (default: false)" })),
51
+ });
52
+
53
+ // Chain item can be either sequential or parallel
54
+ export const ChainItem = Type.Union([SequentialStepSchema, ParallelStepSchema]);
55
+
56
+ export const MaxOutputSchema = Type.Optional(
57
+ Type.Object({
58
+ bytes: Type.Optional(Type.Number({ description: "Max bytes (default: 204800)" })),
59
+ lines: Type.Optional(Type.Number({ description: "Max lines (default: 5000)" })),
60
+ }),
61
+ );
62
+
63
+ export const SubagentParams = Type.Object({
64
+ agent: Type.Optional(Type.String({ description: "Agent name (SINGLE mode)" })),
65
+ task: Type.Optional(Type.String({ description: "Task (SINGLE mode)" })),
66
+ tasks: Type.Optional(Type.Array(TaskItem, { description: "PARALLEL mode: [{agent, task}, ...]" })),
67
+ chain: Type.Optional(Type.Array(ChainItem, { description: "CHAIN mode: [{agent}, {agent, task:'{previous}'}] - sequential pipeline" })),
68
+ async: Type.Optional(Type.Boolean({ description: "Run in background (default: false, or per config)" })),
69
+ agentScope: Type.Optional(Type.String({ description: "Agent discovery scope: 'user', 'project', or 'both' (default: 'user')" })),
70
+ cwd: Type.Optional(Type.String()),
71
+ maxOutput: MaxOutputSchema,
72
+ artifacts: Type.Optional(Type.Boolean({ description: "Write debug artifacts (default: true)" })),
73
+ includeProgress: Type.Optional(Type.Boolean({ description: "Include full progress in result (default: false)" })),
74
+ share: Type.Optional(Type.Boolean({ description: "Create shareable session log (default: true)", default: true })),
75
+ sessionDir: Type.Optional(
76
+ Type.String({ description: "Directory to store session logs (default: temp; enables sessions even if share=false)" }),
77
+ ),
78
+ // Chain clarification TUI
79
+ clarify: Type.Optional(Type.Boolean({ description: "Show TUI to clarify chain templates (default: true for chains). Implies sync mode." })),
80
+ // Solo agent output override
81
+ output: Type.Optional(Type.Union([
82
+ Type.String(),
83
+ Type.Boolean(),
84
+ ], { description: "Override output file for single agent (string), or false to disable (uses agent default if omitted)" })),
85
+ });
86
+
87
+ export const StatusParams = Type.Object({
88
+ id: Type.Optional(Type.String({ description: "Async run id or prefix" })),
89
+ dir: Type.Optional(Type.String({ description: "Async run directory (overrides id search)" })),
90
+ });
package/settings.ts CHANGED
@@ -1,30 +1,14 @@
1
1
  /**
2
- * Subagent settings, chain behavior, and template management
2
+ * Chain behavior, template resolution, and directory management
3
3
  */
4
4
 
5
5
  import * as fs from "node:fs";
6
- import * as os from "node:os";
7
6
  import * as path from "node:path";
8
7
  import type { AgentConfig } from "./agents.js";
9
8
 
10
- const SETTINGS_PATH = path.join(os.homedir(), ".pi", "agent", "settings.json");
11
9
  const CHAIN_RUNS_DIR = "/tmp/pi-chain-runs";
12
10
  const CHAIN_DIR_MAX_AGE_MS = 24 * 60 * 60 * 1000; // 24 hours
13
11
 
14
- // =============================================================================
15
- // Settings Types
16
- // =============================================================================
17
-
18
- export interface ChainTemplates {
19
- [chainKey: string]: {
20
- [agentName: string]: string;
21
- };
22
- }
23
-
24
- export interface SubagentSettings {
25
- chains?: ChainTemplates;
26
- }
27
-
28
12
  // =============================================================================
29
13
  // Behavior Resolution Types
30
14
  // =============================================================================
@@ -83,10 +67,6 @@ export function isParallelStep(step: ChainStep): step is ParallelStep {
83
67
  return "parallel" in step && Array.isArray((step as ParallelStep).parallel);
84
68
  }
85
69
 
86
- export function isSequentialStep(step: ChainStep): step is SequentialStep {
87
- return "agent" in step && !("parallel" in step);
88
- }
89
-
90
70
  /** Get all agent names in a step (single for sequential, multiple for parallel) */
91
71
  export function getStepAgents(step: ChainStep): string[] {
92
72
  if (isParallelStep(step)) {
@@ -95,51 +75,6 @@ export function getStepAgents(step: ChainStep): string[] {
95
75
  return [step.agent];
96
76
  }
97
77
 
98
- /** Get total task count in a step */
99
- export function getStepTaskCount(step: ChainStep): number {
100
- if (isParallelStep(step)) {
101
- return step.parallel.length;
102
- }
103
- return 1;
104
- }
105
-
106
- // =============================================================================
107
- // Settings Management
108
- // =============================================================================
109
-
110
- export function loadSubagentSettings(): SubagentSettings {
111
- try {
112
- const data = JSON.parse(fs.readFileSync(SETTINGS_PATH, "utf-8"));
113
- return (data.subagent as SubagentSettings) ?? {};
114
- } catch {
115
- return {};
116
- }
117
- }
118
-
119
- export function saveChainTemplate(chainKey: string, templates: Record<string, string>): void {
120
- let settings: Record<string, unknown> = {};
121
- try {
122
- settings = JSON.parse(fs.readFileSync(SETTINGS_PATH, "utf-8"));
123
- } catch {}
124
-
125
- if (!settings.subagent) settings.subagent = {};
126
- const subagent = settings.subagent as Record<string, unknown>;
127
- if (!subagent.chains) subagent.chains = {};
128
- const chains = subagent.chains as Record<string, unknown>;
129
-
130
- chains[chainKey] = templates;
131
-
132
- const dir = path.dirname(SETTINGS_PATH);
133
- if (!fs.existsSync(dir)) {
134
- fs.mkdirSync(dir, { recursive: true });
135
- }
136
- fs.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2));
137
- }
138
-
139
- export function getChainKey(agents: string[]): string {
140
- return agents.join("->");
141
- }
142
-
143
78
  // =============================================================================
144
79
  // Chain Directory Management
145
80
  // =============================================================================
@@ -183,36 +118,6 @@ export function cleanupOldChainDirs(): void {
183
118
  // Template Resolution
184
119
  // =============================================================================
185
120
 
186
- /**
187
- * Resolve templates for each step in a chain.
188
- * Priority: inline task > saved template > default
189
- * Default for step 0: "{task}", for others: "{previous}"
190
- */
191
- export function resolveChainTemplates(
192
- agentNames: string[],
193
- inlineTasks: (string | undefined)[],
194
- settings: SubagentSettings,
195
- ): string[] {
196
- const chainKey = getChainKey(agentNames);
197
- const savedTemplates = settings.chains?.[chainKey] ?? {};
198
-
199
- return agentNames.map((agent, i) => {
200
- // Priority: inline > saved > default
201
- const inline = inlineTasks[i];
202
- if (inline) return inline;
203
-
204
- const saved = savedTemplates[agent];
205
- if (saved) return saved;
206
-
207
- // Default: first step uses {task}, others use {previous}
208
- return i === 0 ? "{task}" : "{previous}";
209
- });
210
- }
211
-
212
- // =============================================================================
213
- // Parallel-Aware Template Resolution
214
- // =============================================================================
215
-
216
121
  /** Resolved templates for a chain - string for sequential, string[] for parallel */
217
122
  export type ResolvedTemplates = (string | string[])[];
218
123
 
@@ -220,9 +125,8 @@ export type ResolvedTemplates = (string | string[])[];
220
125
  * Resolve templates for a chain with parallel step support.
221
126
  * Returns string for sequential steps, string[] for parallel steps.
222
127
  */
223
- export function resolveChainTemplatesV2(
128
+ export function resolveChainTemplates(
224
129
  steps: ChainStep[],
225
- settings: SubagentSettings,
226
130
  ): ResolvedTemplates {
227
131
  return steps.map((step, i) => {
228
132
  if (isParallelStep(step)) {
@@ -241,43 +145,6 @@ export function resolveChainTemplatesV2(
241
145
  });
242
146
  }
243
147
 
244
- /**
245
- * Flatten templates for display (TUI navigation needs flat list)
246
- */
247
- export function flattenTemplates(templates: ResolvedTemplates): string[] {
248
- const result: string[] = [];
249
- for (const t of templates) {
250
- if (Array.isArray(t)) {
251
- result.push(...t);
252
- } else {
253
- result.push(t);
254
- }
255
- }
256
- return result;
257
- }
258
-
259
- /**
260
- * Unflatten templates back to structured form
261
- */
262
- export function unflattenTemplates(
263
- flat: string[],
264
- steps: ChainStep[],
265
- ): ResolvedTemplates {
266
- const result: ResolvedTemplates = [];
267
- let idx = 0;
268
- for (const step of steps) {
269
- if (isParallelStep(step)) {
270
- const count = step.parallel.length;
271
- result.push(flat.slice(idx, idx + count));
272
- idx += count;
273
- } else {
274
- result.push(flat[idx]!);
275
- idx++;
276
- }
277
- }
278
- return result;
279
- }
280
-
281
148
  // =============================================================================
282
149
  // Behavior Resolution
283
150
  // =============================================================================
@@ -311,20 +178,6 @@ export function resolveStepBehavior(
311
178
  return { output, reads, progress };
312
179
  }
313
180
 
314
- /**
315
- * Find index of first agent in chain that has progress enabled
316
- */
317
- export function findFirstProgressAgentIndex(
318
- agentConfigs: AgentConfig[],
319
- stepOverrides: StepOverrides[],
320
- ): number {
321
- return agentConfigs.findIndex((config, i) => {
322
- const override = stepOverrides[i];
323
- if (override?.progress !== undefined) return override.progress;
324
- return config.defaultProgress ?? false;
325
- });
326
- }
327
-
328
181
  // =============================================================================
329
182
  // Chain Instruction Injection
330
183
  // =============================================================================
@@ -472,21 +325,4 @@ export function aggregateParallelOutputs(results: ParallelTaskResult[]): string
472
325
  .join("\n\n");
473
326
  }
474
327
 
475
- /**
476
- * Check if any parallel task failed
477
- */
478
- export function hasParallelFailures(results: ParallelTaskResult[]): boolean {
479
- return results.some((r) => r.exitCode !== 0);
480
- }
481
328
 
482
- /**
483
- * Get failure summary for parallel step
484
- */
485
- export function getParallelFailureSummary(results: ParallelTaskResult[]): string {
486
- const failures = results.filter((r) => r.exitCode !== 0);
487
- if (failures.length === 0) return "";
488
-
489
- return failures
490
- .map((f) => `- Task ${f.taskIndex + 1} (${f.agent}): ${f.error || "failed"}`)
491
- .join("\n");
492
- }