@ferris1225/pi-subagents 4.2.8 → 4.2.13
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 +61 -0
- package/LICENSE +23 -23
- package/README.md +60 -4
- package/agents/executor.md +4 -3
- package/agents/explorer.md +37 -37
- package/package.json +11 -2
- package/src/background.ts +205 -205
- package/src/completion.ts +165 -165
- package/src/config.ts +308 -308
- package/src/dispatch.ts +10 -4
- package/src/format.ts +8 -6
- package/src/models.ts +203 -203
- package/src/monitor.ts +3 -2
- package/src/prompt.ts +1 -0
- package/src/recovery.ts +163 -163
- package/src/session-fork.ts +86 -86
- package/src/setup.ts +341 -341
- package/src/spawn.ts +663 -658
- package/src/status.ts +4 -3
- package/src/temp-hygiene.ts +230 -230
- package/src/tools.ts +1 -1
- package/src/ui.ts +248 -248
package/src/setup.ts
CHANGED
|
@@ -1,341 +1,341 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Interactive configuration wizard for /subagents-setup.
|
|
3
|
-
*
|
|
4
|
-
* The wizard stays one level deep and exposes only what most users touch:
|
|
5
|
-
* which agents run and the model and thinking strength each runs on.
|
|
6
|
-
* Everything else (agent scope, idle timeout, result lines, notifications) is
|
|
7
|
-
* config-file-only; model failures hand directly to the current main model,
|
|
8
|
-
* and thinking defaults to capability-aware Auto.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { stat } from "node:fs/promises";
|
|
12
|
-
import type { Api, Model } from "@earendil-works/pi-ai";
|
|
13
|
-
import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
14
|
-
import { discoverAgents } from "./agents.ts";
|
|
15
|
-
import {
|
|
16
|
-
BUILTIN_AGENT_NAMES,
|
|
17
|
-
DEFAULT_CONFIG,
|
|
18
|
-
DEFAULT_ENABLED_AGENTS,
|
|
19
|
-
DEFAULT_THINKING_LEVEL,
|
|
20
|
-
type SubagentsConfig,
|
|
21
|
-
type ThinkingLevel,
|
|
22
|
-
errorMessage,
|
|
23
|
-
getConfigPath,
|
|
24
|
-
loadConfig,
|
|
25
|
-
saveConfig,
|
|
26
|
-
} from "./config.ts";
|
|
27
|
-
import {
|
|
28
|
-
CURRENT_MAIN_MODEL,
|
|
29
|
-
applyAgentModelChoice,
|
|
30
|
-
availableModelsInScope,
|
|
31
|
-
buildModelPickerItems,
|
|
32
|
-
currentModelRef,
|
|
33
|
-
findModelByRef,
|
|
34
|
-
modelRef,
|
|
35
|
-
resolveThinkingLevel,
|
|
36
|
-
supportedThinkingLevels,
|
|
37
|
-
} from "./models.ts";
|
|
38
|
-
import { promptSelectMany, promptSelectOne } from "./ui.ts";
|
|
39
|
-
|
|
40
|
-
/** Short, selection-friendly descriptions for the built-in agents. */
|
|
41
|
-
const MODULE_HINTS: Record<string, string> = {
|
|
42
|
-
explorer: "read-only codebase recon (fast, read-only tools)",
|
|
43
|
-
executor: "implement / fix / clean up / docs sync / merge results (full tools)",
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
function moduleLabel(name: string): string {
|
|
47
|
-
const hint = MODULE_HINTS[name];
|
|
48
|
-
return hint ? `${name} — ${hint}` : name;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
async function configExists(configPath: string): Promise<boolean> {
|
|
52
|
-
try {
|
|
53
|
-
await stat(configPath);
|
|
54
|
-
return true;
|
|
55
|
-
} catch {
|
|
56
|
-
return false;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
async function pickEnabledAgents(
|
|
61
|
-
ctx: ExtensionCommandContext,
|
|
62
|
-
current: readonly string[],
|
|
63
|
-
): Promise<string[] | undefined> {
|
|
64
|
-
const items = BUILTIN_AGENT_NAMES.map((name) => ({ value: name, label: moduleLabel(name) }));
|
|
65
|
-
return promptSelectMany(
|
|
66
|
-
ctx,
|
|
67
|
-
"Enable which sub-agents?",
|
|
68
|
-
"Space toggles • Enter confirms • Esc returns to settings",
|
|
69
|
-
items,
|
|
70
|
-
current,
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
async function pickConfiguredModel(
|
|
75
|
-
ctx: ExtensionCommandContext,
|
|
76
|
-
title: string,
|
|
77
|
-
configuredRef: string | undefined,
|
|
78
|
-
escNote: string,
|
|
79
|
-
): Promise<string | undefined> {
|
|
80
|
-
const models = availableModelsInScope(ctx);
|
|
81
|
-
const items = buildModelPickerItems({
|
|
82
|
-
models,
|
|
83
|
-
configuredRef,
|
|
84
|
-
mainRef: currentModelRef(ctx),
|
|
85
|
-
});
|
|
86
|
-
return promptSelectOne(
|
|
87
|
-
ctx,
|
|
88
|
-
title,
|
|
89
|
-
`Type to filter by provider, model, capability, or thinking level • ↑/↓ • Enter selects • Esc ${escNote}`,
|
|
90
|
-
items,
|
|
91
|
-
configuredRef ?? CURRENT_MAIN_MODEL,
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
async function pickAgentModel(
|
|
96
|
-
ctx: ExtensionCommandContext,
|
|
97
|
-
agentName: string,
|
|
98
|
-
currentRef: string | undefined,
|
|
99
|
-
escNote = "cancels this setup pass",
|
|
100
|
-
): Promise<string | undefined> {
|
|
101
|
-
return pickConfiguredModel(ctx, `Model for "${agentName}"?`, currentRef, escNote);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const AUTO_THINKING = "__auto_thinking__";
|
|
105
|
-
|
|
106
|
-
function actualAgentThinkingDefault(
|
|
107
|
-
ctx: ExtensionCommandContext,
|
|
108
|
-
config: SubagentsConfig,
|
|
109
|
-
agentName: string,
|
|
110
|
-
): ThinkingLevel {
|
|
111
|
-
const { agents } = discoverAgents(ctx.cwd, {
|
|
112
|
-
scope: config.agentScope,
|
|
113
|
-
enabledNames: config.enabledAgents,
|
|
114
|
-
projectTrusted: ctx.isProjectTrusted(),
|
|
115
|
-
});
|
|
116
|
-
return agents.find((agent) => agent.name === agentName)?.thinking ?? DEFAULT_THINKING_LEVEL;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const THINKING_LEVEL_HINTS: Record<ThinkingLevel, string> = {
|
|
120
|
-
off: "no reasoning tokens",
|
|
121
|
-
minimal: "minimal reasoning",
|
|
122
|
-
low: "light reasoning",
|
|
123
|
-
medium: "balanced reasoning",
|
|
124
|
-
high: "deep reasoning",
|
|
125
|
-
xhigh: "extra-deep reasoning",
|
|
126
|
-
max: "strongest reasoning",
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
function effectiveModelForChoice(
|
|
130
|
-
ctx: ExtensionCommandContext,
|
|
131
|
-
choice: string,
|
|
132
|
-
): Model<Api> | undefined {
|
|
133
|
-
if (choice === CURRENT_MAIN_MODEL) return ctx.model;
|
|
134
|
-
return findModelByRef(availableModelsInScope(ctx), choice);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/** Auto is the default. Manual rows are exactly the levels Pi exposes for the
|
|
138
|
-
* selected model; unsupported xhigh/max entries never appear. */
|
|
139
|
-
async function pickAgentStrength(
|
|
140
|
-
ctx: ExtensionCommandContext,
|
|
141
|
-
agentName: string,
|
|
142
|
-
model: Model<Api> | undefined,
|
|
143
|
-
current: ThinkingLevel | undefined,
|
|
144
|
-
agentDefault: ThinkingLevel,
|
|
145
|
-
escNote = "cancels this setup pass",
|
|
146
|
-
): Promise<ThinkingLevel | typeof AUTO_THINKING | undefined> {
|
|
147
|
-
const supported = supportedThinkingLevels(model);
|
|
148
|
-
const automatic = resolveThinkingLevel(model, agentDefault);
|
|
149
|
-
// No model metadata, or a non-reasoning model whose only valid value is off:
|
|
150
|
-
// Auto is already the complete and least surprising choice.
|
|
151
|
-
if (supported.length <= 1) return AUTO_THINKING;
|
|
152
|
-
|
|
153
|
-
const currentEffective = current ? resolveThinkingLevel(model, current) : undefined;
|
|
154
|
-
const modelName = model ? modelRef(model) : "current main model";
|
|
155
|
-
const options = [
|
|
156
|
-
{
|
|
157
|
-
value: AUTO_THINKING,
|
|
158
|
-
label: `auto — ${automatic} for ${modelName}${current === undefined ? " (current, recommended)" : " (recommended)"}`,
|
|
159
|
-
},
|
|
160
|
-
...supported.map((level) => ({
|
|
161
|
-
value: level,
|
|
162
|
-
label: `${level} — ${THINKING_LEVEL_HINTS[level]}${current !== undefined && currentEffective === level ? " (current)" : ""}`,
|
|
163
|
-
})),
|
|
164
|
-
];
|
|
165
|
-
return promptSelectOne(
|
|
166
|
-
ctx,
|
|
167
|
-
`Thinking for "${agentName}"?`,
|
|
168
|
-
`Only levels supported by ${modelName} are shown • Enter selects • Esc ${escNote}`,
|
|
169
|
-
options,
|
|
170
|
-
current === undefined ? AUTO_THINKING : currentEffective,
|
|
171
|
-
) as Promise<ThinkingLevel | typeof AUTO_THINKING | undefined>;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
async function pickAgentToConfigure(
|
|
175
|
-
ctx: ExtensionCommandContext,
|
|
176
|
-
enabledAgents: readonly string[],
|
|
177
|
-
): Promise<string | undefined> {
|
|
178
|
-
if (enabledAgents.length === 0) {
|
|
179
|
-
ctx.ui.notify("No agents are enabled. Enable agents first.", "warning");
|
|
180
|
-
return undefined;
|
|
181
|
-
}
|
|
182
|
-
return promptSelectOne(
|
|
183
|
-
ctx,
|
|
184
|
-
"Configure which agent?",
|
|
185
|
-
"Type to filter • ↑/↓ • Enter selects • Esc returns to settings",
|
|
186
|
-
enabledAgents.map((name) => ({ value: name, label: moduleLabel(name) })),
|
|
187
|
-
);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
interface ConfiguredAgentChoice {
|
|
191
|
-
name: string;
|
|
192
|
-
model: string;
|
|
193
|
-
strength: ThinkingLevel | typeof AUTO_THINKING;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/** Configure one agent while preserving the UI back stack: thinking → model →
|
|
197
|
-
* agent selection. Esc from agent selection ends this configuration pass. */
|
|
198
|
-
async function configureOneAgent(
|
|
199
|
-
ctx: ExtensionCommandContext,
|
|
200
|
-
config: SubagentsConfig,
|
|
201
|
-
): Promise<ConfiguredAgentChoice | undefined> {
|
|
202
|
-
while (true) {
|
|
203
|
-
const name = await pickAgentToConfigure(ctx, config.enabledAgents);
|
|
204
|
-
if (name === undefined) return undefined;
|
|
205
|
-
|
|
206
|
-
while (true) {
|
|
207
|
-
const modelChoice = await pickAgentModel(
|
|
208
|
-
ctx,
|
|
209
|
-
name,
|
|
210
|
-
config.agentModels[name],
|
|
211
|
-
"returns to agent selection",
|
|
212
|
-
);
|
|
213
|
-
if (modelChoice === undefined) break;
|
|
214
|
-
const model = effectiveModelForChoice(ctx, modelChoice);
|
|
215
|
-
const strength = await pickAgentStrength(
|
|
216
|
-
ctx,
|
|
217
|
-
name,
|
|
218
|
-
model,
|
|
219
|
-
config.agentThinkingLevels[name],
|
|
220
|
-
actualAgentThinkingDefault(ctx, config, name),
|
|
221
|
-
"returns to model selection",
|
|
222
|
-
);
|
|
223
|
-
if (strength === undefined) continue;
|
|
224
|
-
return { name, model: modelChoice, strength };
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
function keepAgentEntries<T>(record: Record<string, T>, enabled: readonly string[]): Record<string, T> {
|
|
230
|
-
const keep = new Set(enabled);
|
|
231
|
-
return Object.fromEntries(Object.entries(record).filter(([name]) => keep.has(name)));
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
async function runFullSetup(ctx: ExtensionCommandContext, configPath: string, base: SubagentsConfig): Promise<boolean> {
|
|
235
|
-
const enabled = await pickEnabledAgents(ctx, base.enabledAgents);
|
|
236
|
-
if (enabled === undefined) return false;
|
|
237
|
-
|
|
238
|
-
let agentModels = keepAgentEntries(base.agentModels, enabled);
|
|
239
|
-
for (const agentName of enabled) {
|
|
240
|
-
const choice = await pickAgentModel(ctx, agentName, agentModels[agentName]);
|
|
241
|
-
if (choice === undefined) return false;
|
|
242
|
-
agentModels = applyAgentModelChoice(agentModels, agentName, choice);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
const next: SubagentsConfig = {
|
|
246
|
-
enabledAgents: enabled,
|
|
247
|
-
// The wizard surfaces every built-in, so an untoggled one was seen and
|
|
248
|
-
// deliberately left off — record them all as known.
|
|
249
|
-
knownAgents: [...BUILTIN_AGENT_NAMES],
|
|
250
|
-
agentModels,
|
|
251
|
-
// Full setup returns every agent to capability-aware Auto thinking.
|
|
252
|
-
agentThinkingLevels: {},
|
|
253
|
-
maxResultLines: base.maxResultLines,
|
|
254
|
-
agentScope: base.agentScope,
|
|
255
|
-
idleTimeoutSec: base.idleTimeoutSec,
|
|
256
|
-
};
|
|
257
|
-
await saveConfig(next, configPath);
|
|
258
|
-
ctx.ui.notify(`pi-subagents configured with Auto thinking. Saved to ${configPath}`, "info");
|
|
259
|
-
return true;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
async function runMenu(ctx: ExtensionCommandContext, configPath: string, config: SubagentsConfig): Promise<void> {
|
|
263
|
-
while (true) {
|
|
264
|
-
const choice = await ctx.ui.select("pi-subagents settings", [
|
|
265
|
-
"Enable/disable agents",
|
|
266
|
-
"Configure an agent (model + thinking)",
|
|
267
|
-
"Full re-setup",
|
|
268
|
-
]);
|
|
269
|
-
if (choice === undefined) return;
|
|
270
|
-
if (choice.startsWith("Full")) {
|
|
271
|
-
if (await runFullSetup(ctx, configPath, config)) return;
|
|
272
|
-
continue;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
let next: SubagentsConfig = {
|
|
276
|
-
...config,
|
|
277
|
-
agentModels: { ...config.agentModels },
|
|
278
|
-
agentThinkingLevels: { ...config.agentThinkingLevels },
|
|
279
|
-
};
|
|
280
|
-
if (choice.startsWith("Enable")) {
|
|
281
|
-
const enabled = await pickEnabledAgents(ctx, config.enabledAgents);
|
|
282
|
-
if (enabled === undefined) continue;
|
|
283
|
-
next.enabledAgents = enabled;
|
|
284
|
-
// A newly enabled role inherits explorer's configured model and
|
|
285
|
-
// thinking level, so the file reflects what it will actually run
|
|
286
|
-
// instead of silently falling back to the current main model: these
|
|
287
|
-
// roles do light migration-grade work on the fast explorer lane.
|
|
288
|
-
for (const agent of enabled) {
|
|
289
|
-
if (agent === "explorer" || config.enabledAgents.includes(agent)) continue;
|
|
290
|
-
if (!next.agentModels[agent] && config.agentModels.explorer) {
|
|
291
|
-
next.agentModels[agent] = config.agentModels.explorer;
|
|
292
|
-
}
|
|
293
|
-
if (!next.agentThinkingLevels[agent] && config.agentThinkingLevels.explorer) {
|
|
294
|
-
next.agentThinkingLevels[agent] = config.agentThinkingLevels.explorer;
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
next.agentModels = keepAgentEntries(next.agentModels, enabled);
|
|
298
|
-
next.agentThinkingLevels = keepAgentEntries(next.agentThinkingLevels, enabled);
|
|
299
|
-
} else {
|
|
300
|
-
// Per-agent loop: thinking Esc returns to that agent's model picker;
|
|
301
|
-
// model Esc returns to the agent picker; agent-picker Esc saves completed
|
|
302
|
-
// choices and returns to this settings menu.
|
|
303
|
-
let configuredAny = false;
|
|
304
|
-
while (true) {
|
|
305
|
-
const picked = await configureOneAgent(ctx, next);
|
|
306
|
-
if (!picked) break;
|
|
307
|
-
configuredAny = true;
|
|
308
|
-
next.agentModels = applyAgentModelChoice(next.agentModels, picked.name, picked.model);
|
|
309
|
-
if (picked.strength === AUTO_THINKING) delete next.agentThinkingLevels[picked.name];
|
|
310
|
-
else next.agentThinkingLevels[picked.name] = picked.strength;
|
|
311
|
-
}
|
|
312
|
-
if (!configuredAny) continue;
|
|
313
|
-
await saveConfig(next, configPath);
|
|
314
|
-
ctx.ui.notify(`pi-subagents updated. Saved to ${configPath}`, "info");
|
|
315
|
-
config = next;
|
|
316
|
-
continue;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
await saveConfig(next, configPath);
|
|
320
|
-
ctx.ui.notify(`pi-subagents updated. Saved to ${configPath}`, "info");
|
|
321
|
-
return;
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
/** Entry point for the /subagents-setup command. */
|
|
326
|
-
export async function runSetup(ctx: ExtensionCommandContext, configPath: string = getConfigPath()): Promise<void> {
|
|
327
|
-
if (ctx.mode !== "tui") {
|
|
328
|
-
ctx.ui.notify("/subagents-setup requires Pi's interactive TUI.", "error");
|
|
329
|
-
return;
|
|
330
|
-
}
|
|
331
|
-
try {
|
|
332
|
-
const exists = await configExists(configPath);
|
|
333
|
-
const config = await loadConfig(configPath);
|
|
334
|
-
if (exists) await runMenu(ctx, configPath, config);
|
|
335
|
-
else if (!(await runFullSetup(ctx, configPath, { ...DEFAULT_CONFIG, enabledAgents: [...DEFAULT_ENABLED_AGENTS] }))) {
|
|
336
|
-
ctx.ui.notify("pi-subagents setup cancelled.", "info");
|
|
337
|
-
}
|
|
338
|
-
} catch (error) {
|
|
339
|
-
ctx.ui.notify(`pi-subagents setup failed: ${errorMessage(error)}`, "error");
|
|
340
|
-
}
|
|
341
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Interactive configuration wizard for /subagents-setup.
|
|
3
|
+
*
|
|
4
|
+
* The wizard stays one level deep and exposes only what most users touch:
|
|
5
|
+
* which agents run and the model and thinking strength each runs on.
|
|
6
|
+
* Everything else (agent scope, idle timeout, result lines, notifications) is
|
|
7
|
+
* config-file-only; model failures hand directly to the current main model,
|
|
8
|
+
* and thinking defaults to capability-aware Auto.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { stat } from "node:fs/promises";
|
|
12
|
+
import type { Api, Model } from "@earendil-works/pi-ai";
|
|
13
|
+
import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
14
|
+
import { discoverAgents } from "./agents.ts";
|
|
15
|
+
import {
|
|
16
|
+
BUILTIN_AGENT_NAMES,
|
|
17
|
+
DEFAULT_CONFIG,
|
|
18
|
+
DEFAULT_ENABLED_AGENTS,
|
|
19
|
+
DEFAULT_THINKING_LEVEL,
|
|
20
|
+
type SubagentsConfig,
|
|
21
|
+
type ThinkingLevel,
|
|
22
|
+
errorMessage,
|
|
23
|
+
getConfigPath,
|
|
24
|
+
loadConfig,
|
|
25
|
+
saveConfig,
|
|
26
|
+
} from "./config.ts";
|
|
27
|
+
import {
|
|
28
|
+
CURRENT_MAIN_MODEL,
|
|
29
|
+
applyAgentModelChoice,
|
|
30
|
+
availableModelsInScope,
|
|
31
|
+
buildModelPickerItems,
|
|
32
|
+
currentModelRef,
|
|
33
|
+
findModelByRef,
|
|
34
|
+
modelRef,
|
|
35
|
+
resolveThinkingLevel,
|
|
36
|
+
supportedThinkingLevels,
|
|
37
|
+
} from "./models.ts";
|
|
38
|
+
import { promptSelectMany, promptSelectOne } from "./ui.ts";
|
|
39
|
+
|
|
40
|
+
/** Short, selection-friendly descriptions for the built-in agents. */
|
|
41
|
+
const MODULE_HINTS: Record<string, string> = {
|
|
42
|
+
explorer: "read-only codebase recon (fast, read-only tools)",
|
|
43
|
+
executor: "implement / fix / clean up / docs sync / merge results (full tools)",
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
function moduleLabel(name: string): string {
|
|
47
|
+
const hint = MODULE_HINTS[name];
|
|
48
|
+
return hint ? `${name} — ${hint}` : name;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function configExists(configPath: string): Promise<boolean> {
|
|
52
|
+
try {
|
|
53
|
+
await stat(configPath);
|
|
54
|
+
return true;
|
|
55
|
+
} catch {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function pickEnabledAgents(
|
|
61
|
+
ctx: ExtensionCommandContext,
|
|
62
|
+
current: readonly string[],
|
|
63
|
+
): Promise<string[] | undefined> {
|
|
64
|
+
const items = BUILTIN_AGENT_NAMES.map((name) => ({ value: name, label: moduleLabel(name) }));
|
|
65
|
+
return promptSelectMany(
|
|
66
|
+
ctx,
|
|
67
|
+
"Enable which sub-agents?",
|
|
68
|
+
"Space toggles • Enter confirms • Esc returns to settings",
|
|
69
|
+
items,
|
|
70
|
+
current,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function pickConfiguredModel(
|
|
75
|
+
ctx: ExtensionCommandContext,
|
|
76
|
+
title: string,
|
|
77
|
+
configuredRef: string | undefined,
|
|
78
|
+
escNote: string,
|
|
79
|
+
): Promise<string | undefined> {
|
|
80
|
+
const models = availableModelsInScope(ctx);
|
|
81
|
+
const items = buildModelPickerItems({
|
|
82
|
+
models,
|
|
83
|
+
configuredRef,
|
|
84
|
+
mainRef: currentModelRef(ctx),
|
|
85
|
+
});
|
|
86
|
+
return promptSelectOne(
|
|
87
|
+
ctx,
|
|
88
|
+
title,
|
|
89
|
+
`Type to filter by provider, model, capability, or thinking level • ↑/↓ • Enter selects • Esc ${escNote}`,
|
|
90
|
+
items,
|
|
91
|
+
configuredRef ?? CURRENT_MAIN_MODEL,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function pickAgentModel(
|
|
96
|
+
ctx: ExtensionCommandContext,
|
|
97
|
+
agentName: string,
|
|
98
|
+
currentRef: string | undefined,
|
|
99
|
+
escNote = "cancels this setup pass",
|
|
100
|
+
): Promise<string | undefined> {
|
|
101
|
+
return pickConfiguredModel(ctx, `Model for "${agentName}"?`, currentRef, escNote);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const AUTO_THINKING = "__auto_thinking__";
|
|
105
|
+
|
|
106
|
+
function actualAgentThinkingDefault(
|
|
107
|
+
ctx: ExtensionCommandContext,
|
|
108
|
+
config: SubagentsConfig,
|
|
109
|
+
agentName: string,
|
|
110
|
+
): ThinkingLevel {
|
|
111
|
+
const { agents } = discoverAgents(ctx.cwd, {
|
|
112
|
+
scope: config.agentScope,
|
|
113
|
+
enabledNames: config.enabledAgents,
|
|
114
|
+
projectTrusted: ctx.isProjectTrusted(),
|
|
115
|
+
});
|
|
116
|
+
return agents.find((agent) => agent.name === agentName)?.thinking ?? DEFAULT_THINKING_LEVEL;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const THINKING_LEVEL_HINTS: Record<ThinkingLevel, string> = {
|
|
120
|
+
off: "no reasoning tokens",
|
|
121
|
+
minimal: "minimal reasoning",
|
|
122
|
+
low: "light reasoning",
|
|
123
|
+
medium: "balanced reasoning",
|
|
124
|
+
high: "deep reasoning",
|
|
125
|
+
xhigh: "extra-deep reasoning",
|
|
126
|
+
max: "strongest reasoning",
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
function effectiveModelForChoice(
|
|
130
|
+
ctx: ExtensionCommandContext,
|
|
131
|
+
choice: string,
|
|
132
|
+
): Model<Api> | undefined {
|
|
133
|
+
if (choice === CURRENT_MAIN_MODEL) return ctx.model;
|
|
134
|
+
return findModelByRef(availableModelsInScope(ctx), choice);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Auto is the default. Manual rows are exactly the levels Pi exposes for the
|
|
138
|
+
* selected model; unsupported xhigh/max entries never appear. */
|
|
139
|
+
async function pickAgentStrength(
|
|
140
|
+
ctx: ExtensionCommandContext,
|
|
141
|
+
agentName: string,
|
|
142
|
+
model: Model<Api> | undefined,
|
|
143
|
+
current: ThinkingLevel | undefined,
|
|
144
|
+
agentDefault: ThinkingLevel,
|
|
145
|
+
escNote = "cancels this setup pass",
|
|
146
|
+
): Promise<ThinkingLevel | typeof AUTO_THINKING | undefined> {
|
|
147
|
+
const supported = supportedThinkingLevels(model);
|
|
148
|
+
const automatic = resolveThinkingLevel(model, agentDefault);
|
|
149
|
+
// No model metadata, or a non-reasoning model whose only valid value is off:
|
|
150
|
+
// Auto is already the complete and least surprising choice.
|
|
151
|
+
if (supported.length <= 1) return AUTO_THINKING;
|
|
152
|
+
|
|
153
|
+
const currentEffective = current ? resolveThinkingLevel(model, current) : undefined;
|
|
154
|
+
const modelName = model ? modelRef(model) : "current main model";
|
|
155
|
+
const options = [
|
|
156
|
+
{
|
|
157
|
+
value: AUTO_THINKING,
|
|
158
|
+
label: `auto — ${automatic} for ${modelName}${current === undefined ? " (current, recommended)" : " (recommended)"}`,
|
|
159
|
+
},
|
|
160
|
+
...supported.map((level) => ({
|
|
161
|
+
value: level,
|
|
162
|
+
label: `${level} — ${THINKING_LEVEL_HINTS[level]}${current !== undefined && currentEffective === level ? " (current)" : ""}`,
|
|
163
|
+
})),
|
|
164
|
+
];
|
|
165
|
+
return promptSelectOne(
|
|
166
|
+
ctx,
|
|
167
|
+
`Thinking for "${agentName}"?`,
|
|
168
|
+
`Only levels supported by ${modelName} are shown • Enter selects • Esc ${escNote}`,
|
|
169
|
+
options,
|
|
170
|
+
current === undefined ? AUTO_THINKING : currentEffective,
|
|
171
|
+
) as Promise<ThinkingLevel | typeof AUTO_THINKING | undefined>;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async function pickAgentToConfigure(
|
|
175
|
+
ctx: ExtensionCommandContext,
|
|
176
|
+
enabledAgents: readonly string[],
|
|
177
|
+
): Promise<string | undefined> {
|
|
178
|
+
if (enabledAgents.length === 0) {
|
|
179
|
+
ctx.ui.notify("No agents are enabled. Enable agents first.", "warning");
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
return promptSelectOne(
|
|
183
|
+
ctx,
|
|
184
|
+
"Configure which agent?",
|
|
185
|
+
"Type to filter • ↑/↓ • Enter selects • Esc returns to settings",
|
|
186
|
+
enabledAgents.map((name) => ({ value: name, label: moduleLabel(name) })),
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
interface ConfiguredAgentChoice {
|
|
191
|
+
name: string;
|
|
192
|
+
model: string;
|
|
193
|
+
strength: ThinkingLevel | typeof AUTO_THINKING;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** Configure one agent while preserving the UI back stack: thinking → model →
|
|
197
|
+
* agent selection. Esc from agent selection ends this configuration pass. */
|
|
198
|
+
async function configureOneAgent(
|
|
199
|
+
ctx: ExtensionCommandContext,
|
|
200
|
+
config: SubagentsConfig,
|
|
201
|
+
): Promise<ConfiguredAgentChoice | undefined> {
|
|
202
|
+
while (true) {
|
|
203
|
+
const name = await pickAgentToConfigure(ctx, config.enabledAgents);
|
|
204
|
+
if (name === undefined) return undefined;
|
|
205
|
+
|
|
206
|
+
while (true) {
|
|
207
|
+
const modelChoice = await pickAgentModel(
|
|
208
|
+
ctx,
|
|
209
|
+
name,
|
|
210
|
+
config.agentModels[name],
|
|
211
|
+
"returns to agent selection",
|
|
212
|
+
);
|
|
213
|
+
if (modelChoice === undefined) break;
|
|
214
|
+
const model = effectiveModelForChoice(ctx, modelChoice);
|
|
215
|
+
const strength = await pickAgentStrength(
|
|
216
|
+
ctx,
|
|
217
|
+
name,
|
|
218
|
+
model,
|
|
219
|
+
config.agentThinkingLevels[name],
|
|
220
|
+
actualAgentThinkingDefault(ctx, config, name),
|
|
221
|
+
"returns to model selection",
|
|
222
|
+
);
|
|
223
|
+
if (strength === undefined) continue;
|
|
224
|
+
return { name, model: modelChoice, strength };
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function keepAgentEntries<T>(record: Record<string, T>, enabled: readonly string[]): Record<string, T> {
|
|
230
|
+
const keep = new Set(enabled);
|
|
231
|
+
return Object.fromEntries(Object.entries(record).filter(([name]) => keep.has(name)));
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async function runFullSetup(ctx: ExtensionCommandContext, configPath: string, base: SubagentsConfig): Promise<boolean> {
|
|
235
|
+
const enabled = await pickEnabledAgents(ctx, base.enabledAgents);
|
|
236
|
+
if (enabled === undefined) return false;
|
|
237
|
+
|
|
238
|
+
let agentModels = keepAgentEntries(base.agentModels, enabled);
|
|
239
|
+
for (const agentName of enabled) {
|
|
240
|
+
const choice = await pickAgentModel(ctx, agentName, agentModels[agentName]);
|
|
241
|
+
if (choice === undefined) return false;
|
|
242
|
+
agentModels = applyAgentModelChoice(agentModels, agentName, choice);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const next: SubagentsConfig = {
|
|
246
|
+
enabledAgents: enabled,
|
|
247
|
+
// The wizard surfaces every built-in, so an untoggled one was seen and
|
|
248
|
+
// deliberately left off — record them all as known.
|
|
249
|
+
knownAgents: [...BUILTIN_AGENT_NAMES],
|
|
250
|
+
agentModels,
|
|
251
|
+
// Full setup returns every agent to capability-aware Auto thinking.
|
|
252
|
+
agentThinkingLevels: {},
|
|
253
|
+
maxResultLines: base.maxResultLines,
|
|
254
|
+
agentScope: base.agentScope,
|
|
255
|
+
idleTimeoutSec: base.idleTimeoutSec,
|
|
256
|
+
};
|
|
257
|
+
await saveConfig(next, configPath);
|
|
258
|
+
ctx.ui.notify(`pi-subagents configured with Auto thinking. Saved to ${configPath}`, "info");
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async function runMenu(ctx: ExtensionCommandContext, configPath: string, config: SubagentsConfig): Promise<void> {
|
|
263
|
+
while (true) {
|
|
264
|
+
const choice = await ctx.ui.select("pi-subagents settings", [
|
|
265
|
+
"Enable/disable agents",
|
|
266
|
+
"Configure an agent (model + thinking)",
|
|
267
|
+
"Full re-setup",
|
|
268
|
+
]);
|
|
269
|
+
if (choice === undefined) return;
|
|
270
|
+
if (choice.startsWith("Full")) {
|
|
271
|
+
if (await runFullSetup(ctx, configPath, config)) return;
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
let next: SubagentsConfig = {
|
|
276
|
+
...config,
|
|
277
|
+
agentModels: { ...config.agentModels },
|
|
278
|
+
agentThinkingLevels: { ...config.agentThinkingLevels },
|
|
279
|
+
};
|
|
280
|
+
if (choice.startsWith("Enable")) {
|
|
281
|
+
const enabled = await pickEnabledAgents(ctx, config.enabledAgents);
|
|
282
|
+
if (enabled === undefined) continue;
|
|
283
|
+
next.enabledAgents = enabled;
|
|
284
|
+
// A newly enabled role inherits explorer's configured model and
|
|
285
|
+
// thinking level, so the file reflects what it will actually run
|
|
286
|
+
// instead of silently falling back to the current main model: these
|
|
287
|
+
// roles do light migration-grade work on the fast explorer lane.
|
|
288
|
+
for (const agent of enabled) {
|
|
289
|
+
if (agent === "explorer" || config.enabledAgents.includes(agent)) continue;
|
|
290
|
+
if (!next.agentModels[agent] && config.agentModels.explorer) {
|
|
291
|
+
next.agentModels[agent] = config.agentModels.explorer;
|
|
292
|
+
}
|
|
293
|
+
if (!next.agentThinkingLevels[agent] && config.agentThinkingLevels.explorer) {
|
|
294
|
+
next.agentThinkingLevels[agent] = config.agentThinkingLevels.explorer;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
next.agentModels = keepAgentEntries(next.agentModels, enabled);
|
|
298
|
+
next.agentThinkingLevels = keepAgentEntries(next.agentThinkingLevels, enabled);
|
|
299
|
+
} else {
|
|
300
|
+
// Per-agent loop: thinking Esc returns to that agent's model picker;
|
|
301
|
+
// model Esc returns to the agent picker; agent-picker Esc saves completed
|
|
302
|
+
// choices and returns to this settings menu.
|
|
303
|
+
let configuredAny = false;
|
|
304
|
+
while (true) {
|
|
305
|
+
const picked = await configureOneAgent(ctx, next);
|
|
306
|
+
if (!picked) break;
|
|
307
|
+
configuredAny = true;
|
|
308
|
+
next.agentModels = applyAgentModelChoice(next.agentModels, picked.name, picked.model);
|
|
309
|
+
if (picked.strength === AUTO_THINKING) delete next.agentThinkingLevels[picked.name];
|
|
310
|
+
else next.agentThinkingLevels[picked.name] = picked.strength;
|
|
311
|
+
}
|
|
312
|
+
if (!configuredAny) continue;
|
|
313
|
+
await saveConfig(next, configPath);
|
|
314
|
+
ctx.ui.notify(`pi-subagents updated. Saved to ${configPath}`, "info");
|
|
315
|
+
config = next;
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
await saveConfig(next, configPath);
|
|
320
|
+
ctx.ui.notify(`pi-subagents updated. Saved to ${configPath}`, "info");
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/** Entry point for the /subagents-setup command. */
|
|
326
|
+
export async function runSetup(ctx: ExtensionCommandContext, configPath: string = getConfigPath()): Promise<void> {
|
|
327
|
+
if (ctx.mode !== "tui") {
|
|
328
|
+
ctx.ui.notify("/subagents-setup requires Pi's interactive TUI.", "error");
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
try {
|
|
332
|
+
const exists = await configExists(configPath);
|
|
333
|
+
const config = await loadConfig(configPath);
|
|
334
|
+
if (exists) await runMenu(ctx, configPath, config);
|
|
335
|
+
else if (!(await runFullSetup(ctx, configPath, { ...DEFAULT_CONFIG, enabledAgents: [...DEFAULT_ENABLED_AGENTS] }))) {
|
|
336
|
+
ctx.ui.notify("pi-subagents setup cancelled.", "info");
|
|
337
|
+
}
|
|
338
|
+
} catch (error) {
|
|
339
|
+
ctx.ui.notify(`pi-subagents setup failed: ${errorMessage(error)}`, "error");
|
|
340
|
+
}
|
|
341
|
+
}
|