@ferris1225/pi-subagents 0.31.0 → 0.32.2
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 +122 -71
- package/package.json +1 -1
- package/src/agents.ts +8 -13
- package/src/background.ts +59 -6
- package/src/config.ts +14 -2
- package/src/dispatch.ts +1429 -396
- package/src/format.ts +29 -4
- package/src/index.ts +7 -4
- package/src/inspector-panel.ts +363 -0
- package/src/inspector.ts +369 -0
- package/src/models.ts +192 -50
- package/src/monitor.ts +114 -7
- package/src/prompt.ts +3 -2
- package/src/recovery.ts +145 -0
- package/src/rpc-run.ts +1016 -0
- package/src/runtime.ts +167 -27
- package/src/session-fork.ts +84 -0
- package/src/setup.ts +271 -184
- package/src/spawn.ts +562 -977
- package/src/tools.ts +404 -65
- package/src/trajectory.ts +503 -0
- package/src/ui.ts +32 -16
- package/src/widget.ts +17 -4
- package/src/worktree.ts +687 -0
package/src/setup.ts
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Interactive configuration wizard for /subagents-setup.
|
|
3
3
|
*
|
|
4
|
-
* Everything is selection-driven (no free-text answers)
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Everything is selection-driven (no free-text answers). Enabled agents share a
|
|
5
|
+
* compact primary/backup model-pool editor backed by one searchable model list;
|
|
6
|
+
* the remaining settings use small selection menus. Config is written to
|
|
7
|
+
* <agentDir>/pi-subagents.json.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { stat } from "node:fs/promises";
|
|
11
|
-
import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
11
|
+
import type { ExtensionCommandContext, KeybindingsManager } from "@earendil-works/pi-coding-agent";
|
|
12
|
+
import { truncateToWidth, type Component, type TUI } from "@earendil-works/pi-tui";
|
|
12
13
|
import {
|
|
13
14
|
AGENT_SCOPE_VALUES,
|
|
14
15
|
BUILTIN_AGENT_NAMES,
|
|
@@ -26,11 +27,22 @@ import {
|
|
|
26
27
|
loadConfig,
|
|
27
28
|
saveConfig,
|
|
28
29
|
} from "./config.ts";
|
|
29
|
-
import {
|
|
30
|
+
import {
|
|
31
|
+
CURRENT_MAIN_MODEL,
|
|
32
|
+
applyModelPoolChoice,
|
|
33
|
+
availableModelRefs,
|
|
34
|
+
buildAgentModelPoolRows,
|
|
35
|
+
buildModelPickerItems,
|
|
36
|
+
currentModelRef,
|
|
37
|
+
type AgentModelPoolMaps,
|
|
38
|
+
type ModelPickerSlot,
|
|
39
|
+
type ModelPoolSlot,
|
|
40
|
+
} from "./models.ts";
|
|
30
41
|
import { promptSelectMany, promptSelectOne } from "./ui.ts";
|
|
31
42
|
import { loadBuiltinAgents } from "./agents.ts";
|
|
32
43
|
|
|
33
44
|
const INHERIT = "__inherit__";
|
|
45
|
+
const MODEL_POOL_WIDE_MIN = 96;
|
|
34
46
|
|
|
35
47
|
/** Effective per-agent default strength from builtin frontmatter (config overrides win at spawn). */
|
|
36
48
|
function builtinThinkingDefaults(): Map<string, ThinkingLevel> {
|
|
@@ -76,86 +88,193 @@ async function pickEnabledAgents(
|
|
|
76
88
|
);
|
|
77
89
|
}
|
|
78
90
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
91
|
+
type ModelPoolEditorResult =
|
|
92
|
+
| { action: "edit"; agentName: string; slot: ModelPoolSlot }
|
|
93
|
+
| { action: "save" };
|
|
94
|
+
|
|
95
|
+
interface ModelPoolEditorStyles {
|
|
96
|
+
border(text: string): string;
|
|
97
|
+
title(text: string): string;
|
|
98
|
+
dim(text: string): string;
|
|
99
|
+
accent(text: string): string;
|
|
100
|
+
selected(text: string): string;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Compact overview: every enabled agent shows Primary and Backup on one row. */
|
|
104
|
+
class ModelPoolEditor implements Component {
|
|
105
|
+
private row = 0;
|
|
106
|
+
private slot: ModelPoolSlot = "primary";
|
|
107
|
+
|
|
108
|
+
constructor(
|
|
109
|
+
private readonly agentNames: readonly string[],
|
|
110
|
+
private readonly pools: AgentModelPoolMaps,
|
|
111
|
+
private readonly styles: ModelPoolEditorStyles,
|
|
112
|
+
private readonly tui: TUI,
|
|
113
|
+
private readonly keybindings: KeybindingsManager,
|
|
114
|
+
private readonly done: (result: ModelPoolEditorResult | undefined) => void,
|
|
115
|
+
initialCell?: { agentName: string; slot: ModelPoolSlot },
|
|
116
|
+
) {
|
|
117
|
+
const initialRow = initialCell ? agentNames.indexOf(initialCell.agentName) : -1;
|
|
118
|
+
if (initialCell && initialRow >= 0) {
|
|
119
|
+
this.row = initialRow;
|
|
120
|
+
this.slot = initialCell.slot;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
render(width: number): string[] {
|
|
125
|
+
const fit = (line: string): string => truncateToWidth(line, width, "");
|
|
126
|
+
const border = this.styles.border("─".repeat(Math.max(1, width)));
|
|
127
|
+
const rows = buildAgentModelPoolRows(this.agentNames, this.pools);
|
|
128
|
+
const lines = [
|
|
129
|
+
fit(border),
|
|
130
|
+
fit(this.styles.title("Agent model pools")),
|
|
131
|
+
fit(this.styles.dim("↑/↓ agent • ←/→ Primary/Backup • Enter edit/save • Esc cancel")),
|
|
132
|
+
fit(border),
|
|
133
|
+
];
|
|
134
|
+
for (let index = 0; index < rows.length; index++) {
|
|
135
|
+
const pool = rows[index];
|
|
136
|
+
const active = this.row === index;
|
|
137
|
+
const mark = active ? this.styles.accent("❯ ") : " ";
|
|
138
|
+
const primary = active && this.slot === "primary"
|
|
139
|
+
? this.styles.selected(`[Primary: ${pool.primary}]`)
|
|
140
|
+
: `Primary: ${pool.primary}`;
|
|
141
|
+
const backup = active && this.slot === "backup"
|
|
142
|
+
? this.styles.selected(`[Backup: ${pool.backup}]`)
|
|
143
|
+
: `Backup: ${pool.backup}`;
|
|
144
|
+
if (width >= MODEL_POOL_WIDE_MIN) {
|
|
145
|
+
lines.push(fit(`${mark}${this.styles.accent(pool.name)} · ${primary} · ${backup}`));
|
|
146
|
+
} else {
|
|
147
|
+
// Keep both cells visible on narrow terminals; a long primary can no
|
|
148
|
+
// longer push Backup (including its selected state) off-screen.
|
|
149
|
+
lines.push(fit(`${mark}${this.styles.accent(pool.name)}`));
|
|
150
|
+
lines.push(fit(` ${primary}`));
|
|
151
|
+
lines.push(fit(` ${backup}`));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
const saveActive = this.row === rows.length;
|
|
155
|
+
lines.push(fit(`${saveActive ? this.styles.accent("❯ ") : " "}${saveActive ? this.styles.selected("Save model pools and continue") : "Save model pools and continue"}`));
|
|
156
|
+
lines.push(fit(border));
|
|
157
|
+
return lines;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
handleInput(data: string): void {
|
|
161
|
+
const lastRow = this.agentNames.length;
|
|
162
|
+
if (this.keybindings.matches(data, "tui.select.up")) {
|
|
163
|
+
this.row = this.row === 0 ? lastRow : this.row - 1;
|
|
164
|
+
} else if (this.keybindings.matches(data, "tui.select.down")) {
|
|
165
|
+
this.row = this.row === lastRow ? 0 : this.row + 1;
|
|
166
|
+
} else if (
|
|
167
|
+
this.row < lastRow &&
|
|
168
|
+
(this.keybindings.matches(data, "tui.editor.cursorLeft") ||
|
|
169
|
+
this.keybindings.matches(data, "tui.editor.cursorRight"))
|
|
170
|
+
) {
|
|
171
|
+
this.slot = this.slot === "primary" ? "backup" : "primary";
|
|
172
|
+
} else if (this.keybindings.matches(data, "tui.select.confirm")) {
|
|
173
|
+
if (this.row === lastRow) this.done({ action: "save" });
|
|
174
|
+
else this.done({ action: "edit", agentName: this.agentNames[this.row], slot: this.slot });
|
|
175
|
+
return;
|
|
176
|
+
} else if (this.keybindings.matches(data, "tui.select.cancel")) {
|
|
177
|
+
this.done(undefined);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
this.tui.requestRender();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
invalidate(): void {}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async function promptModelPoolOverview(
|
|
83
187
|
ctx: ExtensionCommandContext,
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
:
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
188
|
+
agentNames: readonly string[],
|
|
189
|
+
pools: AgentModelPoolMaps,
|
|
190
|
+
initialCell?: { agentName: string; slot: ModelPoolSlot },
|
|
191
|
+
): Promise<ModelPoolEditorResult | undefined> {
|
|
192
|
+
return ctx.ui.custom<ModelPoolEditorResult | undefined>((tui, theme, keybindings, done) =>
|
|
193
|
+
new ModelPoolEditor(
|
|
194
|
+
agentNames,
|
|
195
|
+
pools,
|
|
196
|
+
{
|
|
197
|
+
border: (text) => theme.fg("accent", text),
|
|
198
|
+
title: (text) => theme.fg("accent", theme.bold(text)),
|
|
199
|
+
dim: (text) => theme.fg("dim", text),
|
|
200
|
+
accent: (text) => theme.fg("accent", text),
|
|
201
|
+
selected: (text) => theme.fg("accent", theme.bold(text)),
|
|
202
|
+
},
|
|
203
|
+
tui,
|
|
204
|
+
keybindings,
|
|
205
|
+
done,
|
|
206
|
+
initialCell,
|
|
207
|
+
),
|
|
103
208
|
);
|
|
104
209
|
}
|
|
105
210
|
|
|
106
|
-
|
|
107
|
-
* leaves it unset, so vision-flagged dispatches fall back to the main session's
|
|
108
|
-
* current model. */
|
|
109
|
-
async function pickVisionModel(
|
|
211
|
+
async function pickConfiguredModel(
|
|
110
212
|
ctx: ExtensionCommandContext,
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
213
|
+
title: string,
|
|
214
|
+
slot: ModelPickerSlot,
|
|
215
|
+
configuredRef: string | undefined,
|
|
216
|
+
escNote: string,
|
|
217
|
+
): Promise<string | undefined> {
|
|
218
|
+
const registry = ctx.modelRegistry as typeof ctx.modelRegistry & { getAll?: typeof ctx.modelRegistry.getAvailable };
|
|
219
|
+
const models = registry.getAll?.() ?? registry.getAvailable();
|
|
220
|
+
const items = buildModelPickerItems({
|
|
221
|
+
models,
|
|
222
|
+
availableRefs: availableModelRefs(ctx),
|
|
223
|
+
slot,
|
|
224
|
+
configuredRef,
|
|
225
|
+
mainRef: currentModelRef(ctx),
|
|
226
|
+
});
|
|
123
227
|
return promptSelectOne(
|
|
124
228
|
ctx,
|
|
125
|
-
|
|
126
|
-
|
|
229
|
+
title,
|
|
230
|
+
`Type to filter by provider, model, capability, or availability • ↑/↓ • Enter selects • Esc ${escNote}`,
|
|
127
231
|
items,
|
|
232
|
+
configuredRef ?? CURRENT_MAIN_MODEL,
|
|
128
233
|
);
|
|
129
234
|
}
|
|
130
235
|
|
|
131
|
-
|
|
236
|
+
/** Shared by full setup and configure-one-agent. Esc in a model list returns to
|
|
237
|
+
* the overview; Esc in the overview cancels the whole pool edit. */
|
|
238
|
+
async function editModelPools(
|
|
132
239
|
ctx: ExtensionCommandContext,
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
240
|
+
agentNames: readonly string[],
|
|
241
|
+
initial: AgentModelPoolMaps,
|
|
242
|
+
): Promise<AgentModelPoolMaps | undefined> {
|
|
243
|
+
let pools: AgentModelPoolMaps = {
|
|
244
|
+
agentModels: { ...initial.agentModels },
|
|
245
|
+
agentBackupModels: { ...initial.agentBackupModels },
|
|
246
|
+
};
|
|
247
|
+
let activeCell: { agentName: string; slot: ModelPoolSlot } | undefined;
|
|
248
|
+
while (true) {
|
|
249
|
+
const action = await promptModelPoolOverview(ctx, agentNames, pools, activeCell);
|
|
250
|
+
if (action === undefined) return undefined;
|
|
251
|
+
if (action.action === "save") return pools;
|
|
252
|
+
activeCell = { agentName: action.agentName, slot: action.slot };
|
|
253
|
+
const current = action.slot === "primary"
|
|
254
|
+
? pools.agentModels[action.agentName]
|
|
255
|
+
: pools.agentBackupModels[action.agentName];
|
|
256
|
+
const choice = await pickConfiguredModel(
|
|
257
|
+
ctx,
|
|
258
|
+
`${action.slot === "primary" ? "Primary" : "Backup"} model for "${action.agentName}"`,
|
|
259
|
+
action.slot,
|
|
260
|
+
current,
|
|
261
|
+
"returns to model pools",
|
|
262
|
+
);
|
|
263
|
+
if (choice !== undefined) pools = applyModelPoolChoice(pools, action.agentName, action.slot, choice);
|
|
143
264
|
}
|
|
265
|
+
}
|
|
144
266
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if (strength !== INHERIT) strengths[name] = strength;
|
|
157
|
-
}
|
|
158
|
-
return { models, strengths };
|
|
267
|
+
async function pickVisionModel(
|
|
268
|
+
ctx: ExtensionCommandContext,
|
|
269
|
+
currentRef: string | undefined,
|
|
270
|
+
): Promise<string | undefined> {
|
|
271
|
+
return pickConfiguredModel(
|
|
272
|
+
ctx,
|
|
273
|
+
"Vision primary for image tasks (then agent backup → current main model)",
|
|
274
|
+
"vision",
|
|
275
|
+
currentRef,
|
|
276
|
+
"cancels setup",
|
|
277
|
+
);
|
|
159
278
|
}
|
|
160
279
|
|
|
161
280
|
const THINKING_LEVEL_HINTS: Record<ThinkingLevel, string> = {
|
|
@@ -215,48 +334,37 @@ async function pickAgentToConfigure(
|
|
|
215
334
|
);
|
|
216
335
|
}
|
|
217
336
|
|
|
218
|
-
/**
|
|
219
|
-
*
|
|
220
|
-
* its thinking strength. Only that one agent is touched, so re-running the menu
|
|
221
|
-
* to tweak one agent no longer walks every enabled agent. Both picks offer an
|
|
222
|
-
* "inherit" option that drops any existing per-agent override for that field.
|
|
223
|
-
* Resolves undefined when the user presses Esc at any step; the caller keeps
|
|
224
|
-
* changes from agents already configured earlier in the same pass.
|
|
225
|
-
*/
|
|
337
|
+
/** Configure one selected agent with the same pool overview/picker used by
|
|
338
|
+
* full setup, then retain the existing focused thinking-strength picker. */
|
|
226
339
|
async function configureOneAgent(
|
|
227
340
|
ctx: ExtensionCommandContext,
|
|
228
341
|
enabledAgents: readonly string[],
|
|
229
|
-
|
|
342
|
+
currentPools: AgentModelPoolMaps,
|
|
230
343
|
currentStrengths: Record<string, ThinkingLevel>,
|
|
231
344
|
defaultLevel: ThinkingLevel,
|
|
232
345
|
defaults: ReadonlyMap<string, ThinkingLevel>,
|
|
233
346
|
): Promise<
|
|
234
347
|
| {
|
|
235
348
|
name: string;
|
|
236
|
-
|
|
237
|
-
modelsAvailable: boolean;
|
|
349
|
+
pools: AgentModelPoolMaps;
|
|
238
350
|
strength: ThinkingLevel | typeof INHERIT;
|
|
239
351
|
}
|
|
240
352
|
| undefined
|
|
241
353
|
> {
|
|
242
354
|
const name = await pickAgentToConfigure(ctx, enabledAgents);
|
|
243
|
-
if (name === undefined) return undefined;
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
const strength = await pickAgentStrength(ctx, name, currentStrengths[name], defaultLevel, defaults, "stops — earlier agent picks are kept");
|
|
257
|
-
if (strength === undefined) return undefined; // Esc ends the loop
|
|
258
|
-
|
|
259
|
-
return { name, model, modelsAvailable, strength };
|
|
355
|
+
if (name === undefined) return undefined;
|
|
356
|
+
const pools = await editModelPools(ctx, [name], currentPools);
|
|
357
|
+
if (pools === undefined) return undefined;
|
|
358
|
+
const strength = await pickAgentStrength(
|
|
359
|
+
ctx,
|
|
360
|
+
name,
|
|
361
|
+
currentStrengths[name],
|
|
362
|
+
defaultLevel,
|
|
363
|
+
defaults,
|
|
364
|
+
"stops — earlier agent changes are kept",
|
|
365
|
+
);
|
|
366
|
+
if (strength === undefined) return undefined;
|
|
367
|
+
return { name, pools, strength };
|
|
260
368
|
}
|
|
261
369
|
|
|
262
370
|
async function pickThinkingLevel(
|
|
@@ -320,60 +428,42 @@ async function pickScope(ctx: ExtensionCommandContext, current: AgentScope): Pro
|
|
|
320
428
|
return scope;
|
|
321
429
|
}
|
|
322
430
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
if (repair.changed) {
|
|
327
|
-
const detail = repair.fallbackRef
|
|
328
|
-
? `Switched ${repair.replaced} unavailable model override(s) to ${repair.fallbackRef}.`
|
|
329
|
-
: `Removed ${repair.removed} unavailable model override(s); no model is currently available.`;
|
|
330
|
-
ctx.ui.notify(detail, "warning");
|
|
331
|
-
}
|
|
332
|
-
return repair.agentModels;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
async function repairConfigModels(
|
|
336
|
-
ctx: ExtensionCommandContext,
|
|
337
|
-
configPath: string,
|
|
338
|
-
config: SubagentsConfig,
|
|
339
|
-
): Promise<SubagentsConfig> {
|
|
340
|
-
const repairedModels = repairUnavailableModelOverrides(ctx, config.agentModels);
|
|
341
|
-
if (!repairedModels.changed) return config;
|
|
342
|
-
|
|
343
|
-
const repaired = { ...config, agentModels: repairedModels.agentModels };
|
|
344
|
-
try {
|
|
345
|
-
await saveConfig(repaired, configPath);
|
|
346
|
-
ctx.ui.notify(`Repaired unavailable model overrides in ${configPath}.`, "warning");
|
|
347
|
-
} catch (error) {
|
|
348
|
-
ctx.ui.notify(`Could not persist repaired model overrides: ${errorMessage(error)}`, "warning");
|
|
349
|
-
}
|
|
350
|
-
return repaired;
|
|
431
|
+
function keepAgentEntries<T>(record: Record<string, T>, enabled: readonly string[]): Record<string, T> {
|
|
432
|
+
const keep = new Set(enabled);
|
|
433
|
+
return Object.fromEntries(Object.entries(record).filter(([name]) => keep.has(name)));
|
|
351
434
|
}
|
|
352
435
|
|
|
353
436
|
async function runFullSetup(ctx: ExtensionCommandContext, configPath: string, base: SubagentsConfig): Promise<void> {
|
|
354
437
|
const enabled = await pickEnabledAgents(ctx, base.enabledAgents);
|
|
355
438
|
if (enabled === undefined) return notifyCancelled(ctx);
|
|
356
439
|
|
|
357
|
-
// Global default first, so per-agent strength picks can show "inherit" against it.
|
|
358
440
|
const thinkingLevel = await pickThinkingLevel(ctx, base.thinkingLevel);
|
|
359
441
|
if (thinkingLevel === undefined) return notifyCancelled(ctx);
|
|
360
442
|
|
|
443
|
+
const pools = await editModelPools(ctx, enabled, {
|
|
444
|
+
agentModels: base.agentModels,
|
|
445
|
+
agentBackupModels: base.agentBackupModels,
|
|
446
|
+
});
|
|
447
|
+
if (pools === undefined) return notifyCancelled(ctx);
|
|
448
|
+
|
|
361
449
|
const defaults = builtinThinkingDefaults();
|
|
362
|
-
const
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
if (visionModel !== INHERIT) nextVisionModel = visionModel;
|
|
450
|
+
const agentThinkingLevels = keepAgentEntries({ ...base.agentThinkingLevels }, enabled);
|
|
451
|
+
for (const agentName of enabled) {
|
|
452
|
+
const strength = await pickAgentStrength(
|
|
453
|
+
ctx,
|
|
454
|
+
agentName,
|
|
455
|
+
agentThinkingLevels[agentName],
|
|
456
|
+
thinkingLevel,
|
|
457
|
+
defaults,
|
|
458
|
+
);
|
|
459
|
+
if (strength === undefined) return notifyCancelled(ctx);
|
|
460
|
+
if (strength === INHERIT) delete agentThinkingLevels[agentName];
|
|
461
|
+
else agentThinkingLevels[agentName] = strength;
|
|
375
462
|
}
|
|
376
463
|
|
|
464
|
+
const visionModel = await pickVisionModel(ctx, base.visionModel);
|
|
465
|
+
if (visionModel === undefined) return notifyCancelled(ctx);
|
|
466
|
+
|
|
377
467
|
const injection = await pickInjection(ctx, base.proactiveInjection);
|
|
378
468
|
if (injection === undefined) return notifyCancelled(ctx);
|
|
379
469
|
|
|
@@ -389,28 +479,29 @@ async function runFullSetup(ctx: ExtensionCommandContext, configPath: string, ba
|
|
|
389
479
|
);
|
|
390
480
|
if (maxConcurrency === undefined) return notifyCancelled(ctx);
|
|
391
481
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
482
|
+
const maxFixRounds = await pickCount(
|
|
483
|
+
ctx,
|
|
484
|
+
"Auto-fix rounds when a reviewer returns REQUEST_CHANGES? (0 = main agent handles fixes)",
|
|
485
|
+
FIX_ROUNDS_STEPS,
|
|
486
|
+
base.maxFixRounds,
|
|
487
|
+
DEFAULT_MAX_FIX_ROUNDS,
|
|
488
|
+
);
|
|
489
|
+
if (maxFixRounds === undefined) return notifyCancelled(ctx);
|
|
400
490
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
491
|
+
const idleTimeoutSec = await pickCount(
|
|
492
|
+
ctx,
|
|
493
|
+
"Idle timeout in seconds? (0 = disabled, kills a sub-agent whose output goes silent)",
|
|
494
|
+
IDLE_TIMEOUT_STEPS,
|
|
495
|
+
base.idleTimeoutSec,
|
|
496
|
+
DEFAULT_IDLE_TIMEOUT_SEC,
|
|
497
|
+
);
|
|
498
|
+
if (idleTimeoutSec === undefined) return notifyCancelled(ctx);
|
|
409
499
|
|
|
410
|
-
|
|
500
|
+
const next: SubagentsConfig = {
|
|
411
501
|
enabledAgents: enabled,
|
|
412
|
-
agentModels:
|
|
413
|
-
|
|
502
|
+
agentModels: keepAgentEntries(pools.agentModels, enabled),
|
|
503
|
+
agentBackupModels: keepAgentEntries(pools.agentBackupModels, enabled),
|
|
504
|
+
agentThinkingLevels,
|
|
414
505
|
thinkingLevel,
|
|
415
506
|
notifyOnReviewPass: base.notifyOnReviewPass,
|
|
416
507
|
maxResultLines: base.maxResultLines,
|
|
@@ -421,7 +512,7 @@ async function runFullSetup(ctx: ExtensionCommandContext, configPath: string, ba
|
|
|
421
512
|
idleTimeoutSec,
|
|
422
513
|
announcedFeatures: base.announcedFeatures,
|
|
423
514
|
};
|
|
424
|
-
if (
|
|
515
|
+
if (visionModel !== CURRENT_MAIN_MODEL) next.visionModel = visionModel;
|
|
425
516
|
await saveConfig(next, configPath);
|
|
426
517
|
ctx.ui.notify(`pi-subagents configured. Saved to ${configPath}`, "info");
|
|
427
518
|
}
|
|
@@ -429,7 +520,7 @@ async function runFullSetup(ctx: ExtensionCommandContext, configPath: string, ba
|
|
|
429
520
|
async function runMenu(ctx: ExtensionCommandContext, configPath: string, config: SubagentsConfig): Promise<void> {
|
|
430
521
|
const choice = await ctx.ui.select("pi-subagents is already configured. What would you like to change?", [
|
|
431
522
|
"Enable/disable agents",
|
|
432
|
-
"Configure an agent (model + thinking)",
|
|
523
|
+
"Configure an agent (model pool + thinking)",
|
|
433
524
|
"Change vision model (image tasks)",
|
|
434
525
|
"Toggle proactive injection",
|
|
435
526
|
"Change agent scope",
|
|
@@ -442,17 +533,19 @@ async function runMenu(ctx: ExtensionCommandContext, configPath: string, config:
|
|
|
442
533
|
|
|
443
534
|
if (choice.startsWith("Full")) return runFullSetup(ctx, configPath, config);
|
|
444
535
|
|
|
445
|
-
let next: SubagentsConfig = {
|
|
536
|
+
let next: SubagentsConfig = {
|
|
537
|
+
...config,
|
|
538
|
+
agentModels: { ...config.agentModels },
|
|
539
|
+
agentBackupModels: { ...config.agentBackupModels },
|
|
540
|
+
};
|
|
446
541
|
|
|
447
542
|
if (choice.startsWith("Enable")) {
|
|
448
543
|
const enabled = await pickEnabledAgents(ctx, config.enabledAgents);
|
|
449
544
|
if (enabled === undefined) return notifyCancelled(ctx);
|
|
450
545
|
next.enabledAgents = enabled;
|
|
451
546
|
} else if (choice.startsWith("Configure an agent")) {
|
|
452
|
-
// Per-agent loop: pick one agent,
|
|
453
|
-
//
|
|
454
|
-
// pass. Esc at any step ends the loop; agents already configured in this
|
|
455
|
-
// pass are kept.
|
|
547
|
+
// Per-agent loop: pick one agent, edit Primary + Backup together, then its
|
|
548
|
+
// thinking strength. Esc ends the loop; earlier saved agent changes remain.
|
|
456
549
|
const defaults = builtinThinkingDefaults();
|
|
457
550
|
let configuredAny = false;
|
|
458
551
|
next.agentThinkingLevels = { ...config.agentThinkingLevels };
|
|
@@ -460,35 +553,30 @@ async function runMenu(ctx: ExtensionCommandContext, configPath: string, config:
|
|
|
460
553
|
const picked = await configureOneAgent(
|
|
461
554
|
ctx,
|
|
462
555
|
next.enabledAgents,
|
|
463
|
-
|
|
556
|
+
{
|
|
557
|
+
agentModels: next.agentModels,
|
|
558
|
+
agentBackupModels: next.agentBackupModels,
|
|
559
|
+
},
|
|
464
560
|
next.agentThinkingLevels,
|
|
465
561
|
next.thinkingLevel,
|
|
466
562
|
defaults,
|
|
467
563
|
);
|
|
468
|
-
if (picked === undefined) break;
|
|
564
|
+
if (picked === undefined) break;
|
|
469
565
|
configuredAny = true;
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
else next.agentModels[picked.name] = picked.model;
|
|
473
|
-
}
|
|
566
|
+
next.agentModels = picked.pools.agentModels;
|
|
567
|
+
next.agentBackupModels = picked.pools.agentBackupModels;
|
|
474
568
|
if (picked.strength === INHERIT) delete next.agentThinkingLevels[picked.name];
|
|
475
569
|
else next.agentThinkingLevels[picked.name] = picked.strength;
|
|
476
570
|
}
|
|
477
571
|
if (!configuredAny) return notifyCancelled(ctx);
|
|
478
|
-
next.agentModels = repairStaleModels(ctx, next.agentModels);
|
|
479
572
|
} else if (choice.startsWith("Toggle")) {
|
|
480
573
|
const injection = await pickInjection(ctx, config.proactiveInjection);
|
|
481
574
|
if (injection === undefined) return notifyCancelled(ctx);
|
|
482
575
|
next.proactiveInjection = injection;
|
|
483
576
|
} else if (choice.startsWith("Change vision")) {
|
|
484
|
-
const
|
|
485
|
-
if (refs.length === 0) {
|
|
486
|
-
ctx.ui.notify("No Pi models are currently available; vision model left unchanged.", "warning");
|
|
487
|
-
return;
|
|
488
|
-
}
|
|
489
|
-
const visionModel = await pickVisionModel(ctx, config.visionModel, refs);
|
|
577
|
+
const visionModel = await pickVisionModel(ctx, config.visionModel);
|
|
490
578
|
if (visionModel === undefined) return notifyCancelled(ctx);
|
|
491
|
-
if (visionModel ===
|
|
579
|
+
if (visionModel === CURRENT_MAIN_MODEL) delete next.visionModel;
|
|
492
580
|
else next.visionModel = visionModel;
|
|
493
581
|
} else if (choice.startsWith("Change agent scope")) {
|
|
494
582
|
const scope = await pickScope(ctx, config.agentScope);
|
|
@@ -542,8 +630,7 @@ export async function runSetup(ctx: ExtensionCommandContext, configPath: string
|
|
|
542
630
|
}
|
|
543
631
|
try {
|
|
544
632
|
const exists = await configExists(configPath);
|
|
545
|
-
const
|
|
546
|
-
const config = await repairConfigModels(ctx, configPath, loaded);
|
|
633
|
+
const config = await loadConfig(configPath);
|
|
547
634
|
if (exists) await runMenu(ctx, configPath, config);
|
|
548
635
|
else await runFullSetup(ctx, configPath, { ...DEFAULT_CONFIG, enabledAgents: [...DEFAULT_ENABLED_AGENTS] });
|
|
549
636
|
} catch (error) {
|