@ferris1225/pi-subagents 0.10.0 → 0.11.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/README.md +3 -1
- package/package.json +1 -1
- package/src/config.ts +23 -0
- package/src/fixloop.ts +76 -0
- package/src/index.ts +105 -2
- package/src/monitor.ts +14 -1
- package/src/setup.ts +26 -2
package/README.md
CHANGED
|
@@ -145,7 +145,8 @@ agent's default — its frontmatter `thinking`, else the global default). The gl
|
|
|
145
145
|
"agentScope": "user",
|
|
146
146
|
"maxConcurrency": 4,
|
|
147
147
|
"maxParallelTasks": 8,
|
|
148
|
-
"maxSubagentDepth": 1
|
|
148
|
+
"maxSubagentDepth": 1,
|
|
149
|
+
"maxFixRounds": 2
|
|
149
150
|
}
|
|
150
151
|
```
|
|
151
152
|
|
|
@@ -162,6 +163,7 @@ agent's default — its frontmatter `thinking`, else the global default). The gl
|
|
|
162
163
|
| `maxConcurrency` | How many sub-agent processes run at once (1–16, default 4). Extra work waits in the queue. |
|
|
163
164
|
| `maxParallelTasks` | Maximum tasks accepted by one parallel `subagent` call (1–32, default 8). |
|
|
164
165
|
| `maxSubagentDepth` | Depth at which the `subagent` tool is no longer registered (default 1: the main session delegates, children are leaf processes). `0` disables the tool entirely. Read once at extension load. |
|
|
166
|
+
| `maxFixRounds` | Auto-fix rounds when a reviewer returns `REVIEW_FAIL`: the extension dispatches a `worker` (briefed with the review's concrete findings) then a `reviewer` re-review, repeating up to this many times before waking the main agent with the full chain. `0` disables it (the main agent handles fixes itself). Default 2. The reviewer stays read-only and in its own context; the loop is orchestrated by the extension, not by the reviewer. |
|
|
165
167
|
|
|
166
168
|
### Configuration migration
|
|
167
169
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ferris1225/pi-subagents",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Focused sub-agent delegation for pi: explore / worker / reviewer agents in isolated context, with proactive dispatch injection and per-agent model selection.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/src/config.ts
CHANGED
|
@@ -60,6 +60,14 @@ export const MAX_PARALLEL_TASKS_LIMIT = 32;
|
|
|
60
60
|
export const DEFAULT_MAX_SUBAGENT_DEPTH = 1;
|
|
61
61
|
/** Upper bound accepted for maxSubagentDepth (defensive clamp). */
|
|
62
62
|
export const MAX_SUBAGENT_DEPTH_LIMIT = 4;
|
|
63
|
+
/**
|
|
64
|
+
* How many automatic worker→reviewer fix rounds run when a reviewer returns
|
|
65
|
+
* REVIEW_FAIL before waking the main agent. 0 disables the auto-fix loop
|
|
66
|
+
* (the main agent is woken to dispatch fixes itself). Default: 2.
|
|
67
|
+
*/
|
|
68
|
+
export const DEFAULT_MAX_FIX_ROUNDS = 2;
|
|
69
|
+
/** Upper bound accepted for maxFixRounds (defensive clamp). 0 disables the loop. */
|
|
70
|
+
export const MAX_FIX_ROUNDS_LIMIT = 5;
|
|
63
71
|
|
|
64
72
|
export interface SubagentsConfig {
|
|
65
73
|
/** Agent names that are discoverable and injected. Default: explore, worker, reviewer. */
|
|
@@ -91,6 +99,14 @@ export interface SubagentsConfig {
|
|
|
91
99
|
maxParallelTasks: number;
|
|
92
100
|
/** Depth at which the subagent tool is no longer registered. Default: 1. */
|
|
93
101
|
maxSubagentDepth: number;
|
|
102
|
+
/**
|
|
103
|
+
* Auto-fix rounds when a reviewer returns REVIEW_FAIL: the extension dispatches
|
|
104
|
+
* a worker (briefed with the review's concrete findings) then a reviewer
|
|
105
|
+
* re-review, repeating up to this many times before waking the main agent with
|
|
106
|
+
* the full chain. 0 disables it (the main agent handles fixes itself).
|
|
107
|
+
* Default: 2.
|
|
108
|
+
*/
|
|
109
|
+
maxFixRounds: number;
|
|
94
110
|
}
|
|
95
111
|
|
|
96
112
|
export const DEFAULT_CONFIG: SubagentsConfig = {
|
|
@@ -105,6 +121,7 @@ export const DEFAULT_CONFIG: SubagentsConfig = {
|
|
|
105
121
|
maxConcurrency: DEFAULT_MAX_CONCURRENCY,
|
|
106
122
|
maxParallelTasks: DEFAULT_MAX_PARALLEL_TASKS,
|
|
107
123
|
maxSubagentDepth: DEFAULT_MAX_SUBAGENT_DEPTH,
|
|
124
|
+
maxFixRounds: DEFAULT_MAX_FIX_ROUNDS,
|
|
108
125
|
};
|
|
109
126
|
|
|
110
127
|
export function getConfigPath(agentDir: string = getAgentDir()): string {
|
|
@@ -151,6 +168,7 @@ export function normalizeConfig(raw: unknown): SubagentsConfig {
|
|
|
151
168
|
maxConcurrency: DEFAULT_CONFIG.maxConcurrency,
|
|
152
169
|
maxParallelTasks: DEFAULT_CONFIG.maxParallelTasks,
|
|
153
170
|
maxSubagentDepth: DEFAULT_CONFIG.maxSubagentDepth,
|
|
171
|
+
maxFixRounds: DEFAULT_CONFIG.maxFixRounds,
|
|
154
172
|
};
|
|
155
173
|
|
|
156
174
|
if (Array.isArray(raw.enabledAgents)) {
|
|
@@ -213,6 +231,11 @@ export function normalizeConfig(raw: unknown): SubagentsConfig {
|
|
|
213
231
|
config.maxSubagentDepth = Math.max(0, Math.min(MAX_SUBAGENT_DEPTH_LIMIT, Math.round(raw.maxSubagentDepth)));
|
|
214
232
|
}
|
|
215
233
|
|
|
234
|
+
// 0 disables the auto-fix loop (main agent handles fixes itself).
|
|
235
|
+
if (typeof raw.maxFixRounds === "number" && Number.isFinite(raw.maxFixRounds)) {
|
|
236
|
+
config.maxFixRounds = Math.max(0, Math.min(MAX_FIX_ROUNDS_LIMIT, Math.round(raw.maxFixRounds)));
|
|
237
|
+
}
|
|
238
|
+
|
|
216
239
|
return config;
|
|
217
240
|
}
|
|
218
241
|
|
package/src/fixloop.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-fix loop: when a reviewer returns REVIEW_FAIL, the extension dispatches a
|
|
3
|
+
* worker (briefed with the review's concrete findings) and then a reviewer
|
|
4
|
+
* re-review, repeating up to maxFixRounds times before waking the main agent with
|
|
5
|
+
* the full chain. The reviewer stays read-only and in its own context; the loop
|
|
6
|
+
* is orchestrated by the extension layer, not by the reviewer itself, so the
|
|
7
|
+
* independence guarantee (no self-confirmation bias) is preserved.
|
|
8
|
+
*
|
|
9
|
+
* The main agent is never woken mid-loop: the reviewer's FAIL result is intercepted
|
|
10
|
+
* before delivery, the chain runs in the background, and only the final group
|
|
11
|
+
* (initial review → worker fixes → re-reviews) is delivered at the end.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { getResultOutput, isFailedResult, reviewVerdict, type SingleResult } from "./spawn.ts";
|
|
15
|
+
import type { SubagentsConfig } from "./config.ts";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Whether a completed result should trigger the auto-fix loop instead of being
|
|
19
|
+
* delivered to the main agent. Only a REVIEW_FAIL verdict from a healthy
|
|
20
|
+
* reviewer run counts; failed processes and passing reviews are delivered
|
|
21
|
+
* normally. Loop-internal re-review results never reach this path (they are
|
|
22
|
+
* awaited inside the loop, not delivered through the completion flow).
|
|
23
|
+
*/
|
|
24
|
+
export function shouldTriggerFixLoop(result: SingleResult, config: SubagentsConfig): boolean {
|
|
25
|
+
if (config.maxFixRounds <= 0) return false;
|
|
26
|
+
if (result.agent !== "reviewer") return false;
|
|
27
|
+
if (isFailedResult(result)) return false;
|
|
28
|
+
return reviewVerdict(getResultOutput(result)) === "fail";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Build the worker task brief for one fix round from a reviewer's findings.
|
|
33
|
+
* The worker gets the full review text so it can address concrete file:line
|
|
34
|
+
* issues, with instructions to fix only blockers and self-verify.
|
|
35
|
+
*/
|
|
36
|
+
export function buildFixTaskBrief(reviewerResult: SingleResult, round: number, maxRounds: number): string {
|
|
37
|
+
const review = getResultOutput(reviewerResult);
|
|
38
|
+
const remaining = maxRounds - round;
|
|
39
|
+
return [
|
|
40
|
+
`Auto-fix round ${round} of ${maxRounds} (triggered by a failed review).`,
|
|
41
|
+
``,
|
|
42
|
+
`A reviewer ran in an isolated context and returned REQUEST_CHANGES. Its full report:`,
|
|
43
|
+
`---`,
|
|
44
|
+
review,
|
|
45
|
+
`---`,
|
|
46
|
+
``,
|
|
47
|
+
`Fix the concrete blockers the reviewer flagged. Do NOT refactor unrelated code.`,
|
|
48
|
+
`Address every "Critical" item; address "Warnings" only if they are genuine.`,
|
|
49
|
+
`After editing, run the project's format/build/tests when they exist and report`,
|
|
50
|
+
`exactly what you changed (paths + short rationale) so a reviewer can verify.`,
|
|
51
|
+
remaining > 0
|
|
52
|
+
? `A reviewer will re-review your changes automatically after you finish.`
|
|
53
|
+
: `This is the last auto-fix round; the main agent will be woken with the full chain.`,
|
|
54
|
+
].join("\n");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The re-review brief handed to the reviewer after a worker fix round. Includes
|
|
59
|
+
* the prior review so the reviewer can verify the fixes without re-discovering
|
|
60
|
+
* the original issues.
|
|
61
|
+
*/
|
|
62
|
+
export function buildReReviewBrief(reviewerResult: SingleResult, round: number): string {
|
|
63
|
+
const review = getResultOutput(reviewerResult);
|
|
64
|
+
return [
|
|
65
|
+
`Re-review after auto-fix round ${round}.`,
|
|
66
|
+
``,
|
|
67
|
+
`The previous review (REQUEST_CHANGES) found these issues:`,
|
|
68
|
+
`---`,
|
|
69
|
+
review,
|
|
70
|
+
`---`,
|
|
71
|
+
``,
|
|
72
|
+
`Verify the worker's fixes address each blocker. Run \`git diff\` to see what changed.`,
|
|
73
|
+
`Classify honestly: APPROVE if blockers are resolved, REQUEST_CHANGES if not.`,
|
|
74
|
+
`End with your machine-readable verdict line as usual (VERDICT: REVIEW_PASS / REVIEW_FAIL).`,
|
|
75
|
+
].join("\n");
|
|
76
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
getFinalOutput,
|
|
35
35
|
getResultOutput,
|
|
36
36
|
isFailedResult,
|
|
37
|
+
reviewVerdict,
|
|
37
38
|
runSingleAgent,
|
|
38
39
|
truncateResultOutput,
|
|
39
40
|
writeResultArtifact,
|
|
@@ -42,7 +43,8 @@ import {
|
|
|
42
43
|
type SubagentLiveEvent,
|
|
43
44
|
type UsageStats,
|
|
44
45
|
} from "./spawn.ts";
|
|
45
|
-
import {
|
|
46
|
+
import { buildFixTaskBrief, buildReReviewBrief, shouldTriggerFixLoop } from "./fixloop.ts";
|
|
47
|
+
import { formatTaskSummary, formatToolActivity, monitor, statusColor, statusIcon, statusLabel, type RunChainMeta } from "./monitor.ts";
|
|
46
48
|
|
|
47
49
|
const NON_BLANK_TASK_OPTIONS = { minLength: 1, pattern: "\\S" } as const;
|
|
48
50
|
|
|
@@ -333,6 +335,95 @@ export default function (pi: ExtensionAPI): void {
|
|
|
333
335
|
};
|
|
334
336
|
}
|
|
335
337
|
|
|
338
|
+
/**
|
|
339
|
+
* Dispatch one agent inside an auto-fix chain: tracked in the widget with a
|
|
340
|
+
* groupId/relationLabel, but NOT delivered through the completion flow — the
|
|
341
|
+
* chain owner assembles and delivers the whole group at the end.
|
|
342
|
+
*/
|
|
343
|
+
const launchInLoop = async (
|
|
344
|
+
agentName: string,
|
|
345
|
+
task: string,
|
|
346
|
+
signal: AbortSignal,
|
|
347
|
+
meta: RunChainMeta,
|
|
348
|
+
): Promise<SingleResult> => {
|
|
349
|
+
const agent = agents.find((candidate) => candidate.name === agentName);
|
|
350
|
+
if (!agent) return failedStartResult(agentName, task, `Unknown agent: "${agentName}".`);
|
|
351
|
+
const thinkingLevel = config.agentThinkingLevels[agent.name] ?? agent.thinking ?? config.thinkingLevel;
|
|
352
|
+
const runId = monitor.addRun(agent.name, task, agent.model, thinkingLevel, meta);
|
|
353
|
+
const onLive = makeLiveHandler(runId);
|
|
354
|
+
try {
|
|
355
|
+
const result = await runSingleAgent({
|
|
356
|
+
defaultCwd: ctx.cwd,
|
|
357
|
+
agent,
|
|
358
|
+
agentName,
|
|
359
|
+
task,
|
|
360
|
+
thinkingLevel,
|
|
361
|
+
signal,
|
|
362
|
+
onLive,
|
|
363
|
+
makeDetails: makeDetails("single", true),
|
|
364
|
+
});
|
|
365
|
+
finishRun(runId, isFailedResult(result) ? "failed" : "done");
|
|
366
|
+
return result;
|
|
367
|
+
} catch (error) {
|
|
368
|
+
finishRun(runId, "failed");
|
|
369
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
370
|
+
return {
|
|
371
|
+
...queuedResult(agent, task, thinkingLevel),
|
|
372
|
+
exitCode: 1,
|
|
373
|
+
stderr: errorMessage,
|
|
374
|
+
stopReason: signal.aborted ? "aborted" : "error",
|
|
375
|
+
errorMessage,
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Run the auto-fix chain in the background: worker (briefed with the review's
|
|
382
|
+
* findings) → reviewer re-review, up to maxFixRounds times. The main agent is
|
|
383
|
+
* not woken mid-loop; the full chain is delivered as one group at the end.
|
|
384
|
+
* Failures short-circuit: a crashed worker skips its re-review and delivers.
|
|
385
|
+
*/
|
|
386
|
+
const startFixLoop = (initialReviewerResult: SingleResult, parentGroupId: string): void => {
|
|
387
|
+
backgroundQueue.enqueue(
|
|
388
|
+
async (signal) => {
|
|
389
|
+
const chain: SingleResult[] = [initialReviewerResult];
|
|
390
|
+
let lastReviewer = initialReviewerResult;
|
|
391
|
+
for (let round = 1; round <= config.maxFixRounds; round++) {
|
|
392
|
+
if (!sessionActive) break;
|
|
393
|
+
const fixBrief = buildFixTaskBrief(lastReviewer, round, config.maxFixRounds);
|
|
394
|
+
const workerResult = await launchInLoop("worker", fixBrief, signal, {
|
|
395
|
+
groupId: parentGroupId,
|
|
396
|
+
relationLabel: `fix round ${round}`,
|
|
397
|
+
});
|
|
398
|
+
chain.push(workerResult);
|
|
399
|
+
if (!sessionActive || isFailedResult(workerResult)) break;
|
|
400
|
+
const reReviewBrief = buildReReviewBrief(lastReviewer, round);
|
|
401
|
+
const reviewResult = await launchInLoop("reviewer", reReviewBrief, signal, {
|
|
402
|
+
groupId: parentGroupId,
|
|
403
|
+
relationLabel: `re-review round ${round}`,
|
|
404
|
+
});
|
|
405
|
+
chain.push(reviewResult);
|
|
406
|
+
lastReviewer = reviewResult;
|
|
407
|
+
if (!sessionActive) break;
|
|
408
|
+
if (reviewVerdict(getResultOutput(reviewResult)) === "pass") break;
|
|
409
|
+
}
|
|
410
|
+
if (!sessionActive) return;
|
|
411
|
+
// Deliver the whole chain as one group; the loop's outcome always wakes
|
|
412
|
+
// the main agent (a passing chain reports success, a stuck one needs a human).
|
|
413
|
+
const items: CompletionMessageItem[] = chain.map((r) => ({
|
|
414
|
+
agent: r.agent,
|
|
415
|
+
block: formatCompletionBlock(r, config.maxResultLines),
|
|
416
|
+
triggerTurn: true,
|
|
417
|
+
}));
|
|
418
|
+
sendCompletionGroup(items);
|
|
419
|
+
completionBatcher.flush();
|
|
420
|
+
},
|
|
421
|
+
() => {
|
|
422
|
+
// Cancelled: each in-flight run was already finished by its launchInLoop path.
|
|
423
|
+
},
|
|
424
|
+
);
|
|
425
|
+
};
|
|
426
|
+
|
|
336
427
|
const startBackground = (agentName: string, task: string, cwd?: string): SingleResult => {
|
|
337
428
|
const agent = agents.find((candidate) => candidate.name === agentName);
|
|
338
429
|
if (!agent) return failedStartResult(agentName, task, `Unknown agent: "${agentName}".`);
|
|
@@ -371,6 +462,15 @@ export default function (pi: ExtensionAPI): void {
|
|
|
371
462
|
}
|
|
372
463
|
|
|
373
464
|
if (!sessionActive) return;
|
|
465
|
+
// Auto-fix loop: a REVIEW_FAIL from a main-agent-dispatched reviewer
|
|
466
|
+
// triggers a worker→reviewer chain (up to maxFixRounds) without waking
|
|
467
|
+
// the main agent. Loop-internal re-reviews never reach here (they are
|
|
468
|
+
// awaited inside launchInLoop); the initial review is delivered with
|
|
469
|
+
// the chain at the end.
|
|
470
|
+
if (shouldTriggerFixLoop(result, config)) {
|
|
471
|
+
startFixLoop(result, `fix-${runId}`);
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
374
474
|
const failed = isFailedResult(result);
|
|
375
475
|
const completion: CompletionMessageItem = {
|
|
376
476
|
agent: result.agent,
|
|
@@ -518,7 +618,10 @@ export default function (pi: ExtensionAPI): void {
|
|
|
518
618
|
for (const r of runs) {
|
|
519
619
|
const icon = statusIcon(r.status, theme);
|
|
520
620
|
const label = theme.fg(statusColor(r.status), statusLabel(r.status));
|
|
521
|
-
|
|
621
|
+
// Chain-internal runs (auto-fix worker/reviewer) indent under their
|
|
622
|
+
// parent reviewer; summarize() already carries the relationLabel.
|
|
623
|
+
const head = r.groupId ? theme.fg("dim", " ↳ ") : " ";
|
|
624
|
+
lines.push(truncateToWidth(`${head}${icon} ${monitor.summarize(r)} · ${label}`, width, ""));
|
|
522
625
|
if (r.status === "queued" || r.status === "running") {
|
|
523
626
|
lines.push(truncateToWidth(theme.fg("dim", ` task: ${formatTaskSummary(r.task)}`), width, ""));
|
|
524
627
|
}
|
package/src/monitor.ts
CHANGED
|
@@ -36,6 +36,16 @@ export interface RunView {
|
|
|
36
36
|
startedAt?: number;
|
|
37
37
|
/** Epoch ms when the run finished (set on "done"/"failed"). */
|
|
38
38
|
endedAt?: number;
|
|
39
|
+
/** When set, this run belongs to an auto-fix chain (e.g. worker fixing a reviewer's findings). */
|
|
40
|
+
groupId?: string;
|
|
41
|
+
/** Human-readable role within a chain, e.g. "fix round 1" or "re-review round 1". */
|
|
42
|
+
relationLabel?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Optional chain metadata for runs spawned by an auto-fix loop. */
|
|
46
|
+
export interface RunChainMeta {
|
|
47
|
+
groupId?: string;
|
|
48
|
+
relationLabel?: string;
|
|
39
49
|
}
|
|
40
50
|
|
|
41
51
|
// ---------------------------------------------------------------------------
|
|
@@ -168,7 +178,7 @@ export class MonitorStore {
|
|
|
168
178
|
this.notify();
|
|
169
179
|
}
|
|
170
180
|
|
|
171
|
-
addRun(agent: string, task: string, model?: string, thinking?: string): number {
|
|
181
|
+
addRun(agent: string, task: string, model?: string, thinking?: string, meta?: RunChainMeta): number {
|
|
172
182
|
const id = this.nextId++;
|
|
173
183
|
this.runs.push({
|
|
174
184
|
id,
|
|
@@ -178,6 +188,8 @@ export class MonitorStore {
|
|
|
178
188
|
thinking,
|
|
179
189
|
status: "queued",
|
|
180
190
|
usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 },
|
|
191
|
+
...(meta?.groupId ? { groupId: meta.groupId } : {}),
|
|
192
|
+
...(meta?.relationLabel ? { relationLabel: meta.relationLabel } : {}),
|
|
181
193
|
});
|
|
182
194
|
this.notify();
|
|
183
195
|
return id;
|
|
@@ -233,6 +245,7 @@ export class MonitorStore {
|
|
|
233
245
|
summarize(run: RunView): string {
|
|
234
246
|
const usage = formatUsageCompact(run.usage);
|
|
235
247
|
const parts = [run.agent];
|
|
248
|
+
if (run.relationLabel) parts.push(run.relationLabel);
|
|
236
249
|
if (run.model) parts.push(run.model);
|
|
237
250
|
if (run.thinking) parts.push(`thinking ${run.thinking}`);
|
|
238
251
|
if (usage) parts.push(usage);
|
package/src/setup.ts
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
DEFAULT_CONFIG,
|
|
16
16
|
DEFAULT_ENABLED_AGENTS,
|
|
17
17
|
DEFAULT_MAX_CONCURRENCY,
|
|
18
|
+
DEFAULT_MAX_FIX_ROUNDS,
|
|
18
19
|
DEFAULT_MAX_PARALLEL_TASKS,
|
|
19
20
|
THINKING_LEVEL_VALUES,
|
|
20
21
|
type AgentScope,
|
|
@@ -196,6 +197,8 @@ async function pickInjection(ctx: ExtensionCommandContext, current: boolean): Pr
|
|
|
196
197
|
/** Preset steps offered for the two numeric limits (selection-only wizard). */
|
|
197
198
|
const CONCURRENCY_STEPS = [1, 2, 3, 4, 6, 8, 12, 16];
|
|
198
199
|
const PARALLEL_TASK_STEPS = [2, 4, 6, 8, 12, 16, 24, 32];
|
|
200
|
+
/** Preset rounds offered for the auto-fix loop (0 disables it). */
|
|
201
|
+
const FIX_ROUNDS_STEPS = [0, 1, 2, 3, 5];
|
|
199
202
|
|
|
200
203
|
async function pickCount(
|
|
201
204
|
ctx: ExtensionCommandContext,
|
|
@@ -295,9 +298,18 @@ async function runFullSetup(ctx: ExtensionCommandContext, configPath: string, ba
|
|
|
295
298
|
base.maxParallelTasks,
|
|
296
299
|
DEFAULT_MAX_PARALLEL_TASKS,
|
|
297
300
|
);
|
|
298
|
-
|
|
301
|
+
if (maxParallelTasks === undefined) return notifyCancelled(ctx);
|
|
302
|
+
|
|
303
|
+
const maxFixRounds = await pickCount(
|
|
304
|
+
ctx,
|
|
305
|
+
"Auto-fix rounds when a reviewer returns REQUEST_CHANGES? (0 = main agent handles fixes)",
|
|
306
|
+
FIX_ROUNDS_STEPS,
|
|
307
|
+
base.maxFixRounds,
|
|
308
|
+
DEFAULT_MAX_FIX_ROUNDS,
|
|
309
|
+
);
|
|
310
|
+
if (maxFixRounds === undefined) return notifyCancelled(ctx);
|
|
299
311
|
|
|
300
|
-
|
|
312
|
+
const next: SubagentsConfig = {
|
|
301
313
|
enabledAgents: enabled,
|
|
302
314
|
agentModels: repairStaleModels(ctx, picked.models),
|
|
303
315
|
agentThinkingLevels: picked.strengths,
|
|
@@ -309,6 +321,7 @@ async function runFullSetup(ctx: ExtensionCommandContext, configPath: string, ba
|
|
|
309
321
|
maxConcurrency,
|
|
310
322
|
maxParallelTasks,
|
|
311
323
|
maxSubagentDepth: base.maxSubagentDepth,
|
|
324
|
+
maxFixRounds,
|
|
312
325
|
};
|
|
313
326
|
await saveConfig(next, configPath);
|
|
314
327
|
ctx.ui.notify(`pi-subagents configured. Saved to ${configPath}`, "info");
|
|
@@ -323,6 +336,7 @@ async function runMenu(ctx: ExtensionCommandContext, configPath: string, config:
|
|
|
323
336
|
"Change agent scope",
|
|
324
337
|
"Change max concurrent sub-agents",
|
|
325
338
|
"Change max parallel tasks",
|
|
339
|
+
"Change max fix rounds",
|
|
326
340
|
"Full re-setup",
|
|
327
341
|
]);
|
|
328
342
|
if (choice === undefined) return notifyCancelled(ctx);
|
|
@@ -384,6 +398,16 @@ async function runMenu(ctx: ExtensionCommandContext, configPath: string, config:
|
|
|
384
398
|
);
|
|
385
399
|
if (maxParallelTasks === undefined) return notifyCancelled(ctx);
|
|
386
400
|
next.maxParallelTasks = maxParallelTasks;
|
|
401
|
+
} else if (choice.startsWith("Change max fix")) {
|
|
402
|
+
const maxFixRounds = await pickCount(
|
|
403
|
+
ctx,
|
|
404
|
+
"Auto-fix rounds when a reviewer returns REQUEST_CHANGES? (0 = main agent handles fixes)",
|
|
405
|
+
FIX_ROUNDS_STEPS,
|
|
406
|
+
config.maxFixRounds,
|
|
407
|
+
DEFAULT_MAX_FIX_ROUNDS,
|
|
408
|
+
);
|
|
409
|
+
if (maxFixRounds === undefined) return notifyCancelled(ctx);
|
|
410
|
+
next.maxFixRounds = maxFixRounds;
|
|
387
411
|
}
|
|
388
412
|
|
|
389
413
|
await saveConfig(next, configPath);
|