@ferris1225/pi-subagents 0.20.0 → 0.21.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 +7 -0
- package/package.json +1 -1
- package/src/index.ts +10 -9
- package/src/monitor.ts +10 -0
- package/src/setup.ts +96 -38
package/README.md
CHANGED
|
@@ -347,6 +347,13 @@ agent's default — its frontmatter `thinking`, else the global default). The gl
|
|
|
347
347
|
`thinkingLevel` is set first and applies as the final fallback. `notifyOnReviewPass` and
|
|
348
348
|
`maxResultLines` are edited directly in `pi-subagents.json`.
|
|
349
349
|
|
|
350
|
+
When the config already exists, re-running `/subagents-setup` opens a menu whose
|
|
351
|
+
**Configure an agent (model + thinking)** entry lets you pick one agent and set just its
|
|
352
|
+
model and thinking strength — so changing a single agent no longer walks every enabled
|
|
353
|
+
agent. **Change default thinking strength** sets only the global fallback. The rest of the
|
|
354
|
+
menu toggles injection, scope, concurrency, fix rounds, and idle timeout; **Full re-setup**
|
|
355
|
+
re-runs the whole first-time wizard.
|
|
356
|
+
|
|
350
357
|
```json
|
|
351
358
|
{
|
|
352
359
|
"enabledAgents": ["explore", "worker", "reviewer"],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ferris1225/pi-subagents",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.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/index.ts
CHANGED
|
@@ -45,7 +45,7 @@ import {
|
|
|
45
45
|
import { buildFixTaskBrief, buildReReviewBrief, shouldTriggerFixLoop } from "./fixloop.ts";
|
|
46
46
|
import {
|
|
47
47
|
activityStateLabel,
|
|
48
|
-
|
|
48
|
+
compactLine,
|
|
49
49
|
deriveActivityState,
|
|
50
50
|
formatElapsed,
|
|
51
51
|
formatTaskSummary,
|
|
@@ -795,11 +795,12 @@ export default function (pi: ExtensionAPI): void {
|
|
|
795
795
|
const title = formatTaskSummary(r.task, Math.max(16, Math.floor(width * 0.65)), true);
|
|
796
796
|
const left = `${head}${icon} ${theme.fg("dim", `#${r.id}`)} ${theme.bold(name)}`;
|
|
797
797
|
|
|
798
|
-
// Right side:
|
|
798
|
+
// Right side: full model ref (provider/model), token usage (in/out +
|
|
799
799
|
// cache read/write), tool count, elapsed, and the soft activity-state
|
|
800
|
-
// annotation (idle / long-running).
|
|
801
|
-
// the
|
|
802
|
-
|
|
800
|
+
// annotation (idle / long-running). Trailing the header with a single
|
|
801
|
+
// " · " chain keeps the row compact (no center gap); compactLine
|
|
802
|
+
// clips on overflow, never the right side on its own.
|
|
803
|
+
const model = r.model ?? "?";
|
|
803
804
|
const usage = formatUsageCompact(r.usage);
|
|
804
805
|
const tools = r.toolCount ? `${r.toolCount} tool${r.toolCount === 1 ? "" : "s"}` : "";
|
|
805
806
|
const elapsed = formatElapsed(r, now);
|
|
@@ -808,10 +809,10 @@ export default function (pi: ExtensionAPI): void {
|
|
|
808
809
|
// the other states (ready / done / stopped) so they are unambiguous.
|
|
809
810
|
if (r.status !== "running") metaParts.push(statusLabel(r.status));
|
|
810
811
|
const state = deriveActivityState(r, now);
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
const right = theme.fg("dim",
|
|
814
|
-
lines.push(
|
|
812
|
+
if (state) metaParts.push(activityStateLabel(state));
|
|
813
|
+
if (r.annotation) metaParts.push(r.annotation);
|
|
814
|
+
const right = metaParts.length ? theme.fg("dim", ` · ${metaParts.join(" · ")}`) : "";
|
|
815
|
+
lines.push(compactLine(left, right, width));
|
|
815
816
|
|
|
816
817
|
// Task summary sits one indent below the header, on its own line.
|
|
817
818
|
lines.push(rightAlign(`${head} ${theme.fg("accent", title)}`, "", width));
|
package/src/monitor.ts
CHANGED
|
@@ -297,6 +297,16 @@ export function rightAlign(left: string, right: string, width: number): string {
|
|
|
297
297
|
return truncateToWidth(`${leftClipped}${" ".repeat(gap)}${right}`, width);
|
|
298
298
|
}
|
|
299
299
|
|
|
300
|
+
/** Concatenate `left` and `right` (no center padding) and clip the combined line
|
|
301
|
+
* to `width`. Unlike {@link rightAlign}, the right side trails the left side
|
|
302
|
+
* instead of being pinned to the far edge, so a short header leaves no gap in
|
|
303
|
+
* the middle. `right` should carry its own leading separator (e.g. ` \u00b7 `);
|
|
304
|
+
* pass an empty string when there is nothing to append. Styled strings are
|
|
305
|
+
* measured by display width. */
|
|
306
|
+
export function compactLine(left: string, right: string, width: number): string {
|
|
307
|
+
return truncateToWidth(`${left}${right}`, width);
|
|
308
|
+
}
|
|
309
|
+
|
|
300
310
|
/** Max length of the argument target inside a formatted activity line. */
|
|
301
311
|
export const ACTIVITY_TARGET_MAX = 60;
|
|
302
312
|
|
package/src/setup.ts
CHANGED
|
@@ -76,6 +76,30 @@ async function pickEnabledAgents(
|
|
|
76
76
|
);
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
/** Single-agent model pick; the inherit option drops any existing override. */
|
|
80
|
+
async function pickAgentModel(
|
|
81
|
+
ctx: ExtensionCommandContext,
|
|
82
|
+
name: string,
|
|
83
|
+
currentRef: string | undefined,
|
|
84
|
+
refs: readonly string[],
|
|
85
|
+
): Promise<string | typeof INHERIT | undefined> {
|
|
86
|
+
const items = [
|
|
87
|
+
{
|
|
88
|
+
value: INHERIT,
|
|
89
|
+
label: currentRef
|
|
90
|
+
? `(use main session's model — drop override "${currentRef}")`
|
|
91
|
+
: "(use main session's current model — no override)",
|
|
92
|
+
},
|
|
93
|
+
...refs.map((ref) => ({ value: ref, label: ref === currentRef ? `${ref} (current)` : ref })),
|
|
94
|
+
];
|
|
95
|
+
return promptSelectOne(
|
|
96
|
+
ctx,
|
|
97
|
+
`Model for "${name}"`,
|
|
98
|
+
"Type to filter • ↑/↓ • PgUp/PgDn • Enter selects • Esc cancels setup",
|
|
99
|
+
items,
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
79
103
|
async function pickAgentModelsAndStrength(
|
|
80
104
|
ctx: ExtensionCommandContext,
|
|
81
105
|
enabledAgents: readonly string[],
|
|
@@ -93,24 +117,9 @@ async function pickAgentModelsAndStrength(
|
|
|
93
117
|
const models: Record<string, string> = {};
|
|
94
118
|
const strengths: Record<string, ThinkingLevel> = {};
|
|
95
119
|
for (const name of enabledAgents) {
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
value: INHERIT,
|
|
100
|
-
label: currentRef
|
|
101
|
-
? `(use main session's model — drop override "${currentRef}")`
|
|
102
|
-
: "(use main session's current model — no override)",
|
|
103
|
-
},
|
|
104
|
-
...refs.map((ref) => ({ value: ref, label: ref === currentRef ? `${ref} (current)` : ref })),
|
|
105
|
-
];
|
|
106
|
-
const choice = await promptSelectOne(
|
|
107
|
-
ctx,
|
|
108
|
-
`Model for "${name}"`,
|
|
109
|
-
"Type to filter • ↑/↓ • PgUp/PgDn • Enter selects • Esc cancels setup",
|
|
110
|
-
items,
|
|
111
|
-
);
|
|
112
|
-
if (choice === undefined) return undefined; // Esc aborts the whole wizard
|
|
113
|
-
if (choice !== INHERIT) models[name] = choice;
|
|
120
|
+
const modelChoice = await pickAgentModel(ctx, name, currentModels[name], refs);
|
|
121
|
+
if (modelChoice === undefined) return undefined; // Esc aborts the whole wizard
|
|
122
|
+
if (modelChoice !== INHERIT) models[name] = modelChoice;
|
|
114
123
|
|
|
115
124
|
// Convenience: the model pick is immediately followed by the strength pick,
|
|
116
125
|
// so per-agent model + strength are configured in one pass.
|
|
@@ -157,21 +166,65 @@ async function pickAgentStrength(
|
|
|
157
166
|
return choice === INHERIT ? INHERIT : (choice as ThinkingLevel);
|
|
158
167
|
}
|
|
159
168
|
|
|
160
|
-
/**
|
|
161
|
-
async function
|
|
169
|
+
/** Pick one enabled agent to reconfigure. Resolves undefined on Esc. */
|
|
170
|
+
async function pickAgentToConfigure(
|
|
162
171
|
ctx: ExtensionCommandContext,
|
|
163
172
|
enabledAgents: readonly string[],
|
|
173
|
+
): Promise<string | undefined> {
|
|
174
|
+
if (enabledAgents.length === 0) {
|
|
175
|
+
ctx.ui.notify("No agents are enabled. Enable agents first.", "warning");
|
|
176
|
+
return undefined;
|
|
177
|
+
}
|
|
178
|
+
const items = enabledAgents.map((name) => ({ value: name, label: moduleLabel(name) }));
|
|
179
|
+
return promptSelectOne(
|
|
180
|
+
ctx,
|
|
181
|
+
"Configure which agent?",
|
|
182
|
+
"Type to filter • ↑/↓ • PgUp/PgDn • Enter selects • Esc cancels",
|
|
183
|
+
items,
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Configure a single agent picked from the enabled set: choose its model, then
|
|
189
|
+
* its thinking strength. Only that one agent is touched, so re-running the menu
|
|
190
|
+
* to tweak one agent no longer walks every enabled agent. Both picks offer an
|
|
191
|
+
* "inherit" option that drops any existing per-agent override for that field.
|
|
192
|
+
* Resolves undefined when the user cancels (Esc) at any step.
|
|
193
|
+
*/
|
|
194
|
+
async function configureOneAgent(
|
|
195
|
+
ctx: ExtensionCommandContext,
|
|
196
|
+
enabledAgents: readonly string[],
|
|
197
|
+
currentModels: Record<string, string>,
|
|
164
198
|
currentStrengths: Record<string, ThinkingLevel>,
|
|
165
199
|
defaultLevel: ThinkingLevel,
|
|
166
200
|
defaults: ReadonlyMap<string, ThinkingLevel>,
|
|
167
|
-
): Promise<
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
201
|
+
): Promise<
|
|
202
|
+
| {
|
|
203
|
+
name: string;
|
|
204
|
+
model: string | typeof INHERIT;
|
|
205
|
+
modelsAvailable: boolean;
|
|
206
|
+
strength: ThinkingLevel | typeof INHERIT;
|
|
207
|
+
}
|
|
208
|
+
| undefined
|
|
209
|
+
> {
|
|
210
|
+
const name = await pickAgentToConfigure(ctx, enabledAgents);
|
|
211
|
+
if (name === undefined) return undefined; // Esc cancels
|
|
212
|
+
|
|
213
|
+
const refs = availableModelRefs(ctx);
|
|
214
|
+
const modelsAvailable = refs.length > 0;
|
|
215
|
+
let model: string | typeof INHERIT = INHERIT;
|
|
216
|
+
if (!modelsAvailable) {
|
|
217
|
+
ctx.ui.notify("No Pi models are currently available; model override left unchanged.", "warning");
|
|
218
|
+
} else {
|
|
219
|
+
const modelChoice = await pickAgentModel(ctx, name, currentModels[name], refs);
|
|
220
|
+
if (modelChoice === undefined) return undefined; // Esc cancels setup
|
|
221
|
+
model = modelChoice;
|
|
173
222
|
}
|
|
174
|
-
|
|
223
|
+
|
|
224
|
+
const strength = await pickAgentStrength(ctx, name, currentStrengths[name], defaultLevel, defaults);
|
|
225
|
+
if (strength === undefined) return undefined; // Esc cancels setup
|
|
226
|
+
|
|
227
|
+
return { name, model, modelsAvailable, strength };
|
|
175
228
|
}
|
|
176
229
|
|
|
177
230
|
async function pickThinkingLevel(
|
|
@@ -330,8 +383,8 @@ async function runFullSetup(ctx: ExtensionCommandContext, configPath: string, ba
|
|
|
330
383
|
async function runMenu(ctx: ExtensionCommandContext, configPath: string, config: SubagentsConfig): Promise<void> {
|
|
331
384
|
const choice = await ctx.ui.select("pi-subagents is already configured. What would you like to change?", [
|
|
332
385
|
"Enable/disable agents",
|
|
333
|
-
"
|
|
334
|
-
"Change thinking strength",
|
|
386
|
+
"Configure an agent (model + thinking)",
|
|
387
|
+
"Change default thinking strength",
|
|
335
388
|
"Toggle proactive injection",
|
|
336
389
|
"Change agent scope",
|
|
337
390
|
"Change max concurrent sub-agents",
|
|
@@ -349,9 +402,11 @@ async function runMenu(ctx: ExtensionCommandContext, configPath: string, config:
|
|
|
349
402
|
const enabled = await pickEnabledAgents(ctx, config.enabledAgents);
|
|
350
403
|
if (enabled === undefined) return notifyCancelled(ctx);
|
|
351
404
|
next.enabledAgents = enabled;
|
|
352
|
-
} else if (choice.startsWith("
|
|
405
|
+
} else if (choice.startsWith("Configure an agent")) {
|
|
406
|
+
// Per-agent: pick one agent, then its model and thinking strength. Only that
|
|
407
|
+
// agent is touched, so tweaking one no longer walks every enabled agent.
|
|
353
408
|
const defaults = builtinThinkingDefaults();
|
|
354
|
-
const picked = await
|
|
409
|
+
const picked = await configureOneAgent(
|
|
355
410
|
ctx,
|
|
356
411
|
config.enabledAgents,
|
|
357
412
|
config.agentModels,
|
|
@@ -360,16 +415,19 @@ async function runMenu(ctx: ExtensionCommandContext, configPath: string, config:
|
|
|
360
415
|
defaults,
|
|
361
416
|
);
|
|
362
417
|
if (picked === undefined) return notifyCancelled(ctx);
|
|
363
|
-
next.agentModels =
|
|
364
|
-
next.agentThinkingLevels =
|
|
365
|
-
|
|
366
|
-
|
|
418
|
+
next.agentModels = { ...config.agentModels };
|
|
419
|
+
next.agentThinkingLevels = { ...config.agentThinkingLevels };
|
|
420
|
+
if (picked.modelsAvailable) {
|
|
421
|
+
if (picked.model === INHERIT) delete next.agentModels[picked.name];
|
|
422
|
+
else next.agentModels[picked.name] = picked.model;
|
|
423
|
+
next.agentModels = repairStaleModels(ctx, next.agentModels);
|
|
424
|
+
}
|
|
425
|
+
if (picked.strength === INHERIT) delete next.agentThinkingLevels[picked.name];
|
|
426
|
+
else next.agentThinkingLevels[picked.name] = picked.strength;
|
|
427
|
+
} else if (choice.startsWith("Change default thinking")) {
|
|
367
428
|
const thinkingLevel = await pickThinkingLevel(ctx, config.thinkingLevel);
|
|
368
429
|
if (thinkingLevel === undefined) return notifyCancelled(ctx);
|
|
369
430
|
next.thinkingLevel = thinkingLevel;
|
|
370
|
-
const strengths = await pickAgentStrengths(ctx, config.enabledAgents, config.agentThinkingLevels, thinkingLevel, builtinThinkingDefaults());
|
|
371
|
-
if (strengths === undefined) return notifyCancelled(ctx);
|
|
372
|
-
next.agentThinkingLevels = strengths;
|
|
373
431
|
} else if (choice.startsWith("Toggle")) {
|
|
374
432
|
const injection = await pickInjection(ctx, config.proactiveInjection);
|
|
375
433
|
if (injection === undefined) return notifyCancelled(ctx);
|