@ferris1225/pi-subagents 4.0.0 → 4.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +15 -12
  2. package/package.json +1 -1
  3. package/src/config.ts +36 -6
package/README.md CHANGED
@@ -11,13 +11,15 @@ Focused background delegation for [pi](https://pi.dev): `explorer` / `worker` /
11
11
  results back to the main agent automatically. Install it, and the main model
12
12
  starts using it on its own — no prompt engineering, no babysitting.
13
13
 
14
- ## 4.0.0consistent agent names and direct cleanup
14
+ ## 4.0.1automatic explorer config migration
15
15
 
16
16
  Version 4 renames the built-in reconnaissance role from `explore` to `explorer`
17
- and deliberately removes the old alias. Existing explicit configuration must use
18
- the new key. `cleaner` is now apply-only: explicit cleanup intent authorizes it to
19
- prove and perform every safe in-scope cut, while generic or read-only assessments
20
- go to `reviewer` without triggering auto-fix.
17
+ without retaining a runtime alias. Version 4.0.1 automatically migrates the old
18
+ name in `enabledAgents`, `agentModels`, and `agentThinkingLevels`, then persists
19
+ the normalized configuration; an already configured `explorer` value wins a
20
+ conflict. `cleaner` is apply-only: explicit cleanup intent authorizes it to prove
21
+ and perform every safe in-scope cut, while generic or read-only assessments go to
22
+ `reviewer` without triggering auto-fix.
21
23
 
22
24
  The main delegation directive is now the single authoritative routing policy;
23
25
  duplicated tool guidelines were removed to cut the default parent injection by
@@ -418,13 +420,14 @@ Config loading normalizes schema fields and removes invalid or obsolete keys,
418
420
  including `agentBackupModels`, global `thinkingLevel`, `maxParallelTasks`, and
419
421
  `maxSubagentDepth`. Per-agent thinking preferences remain capability-clamped.
420
422
 
421
- The built-in reconnaissance role is now `explorer`. There is deliberately no
422
- `explore` alias and no automatic key migration. Existing configurations must change
423
- that role name in `enabledAgents`, `agentModels`, and `agentThinkingLevels` (or run
424
- `/subagents-setup`). Configured non-empty names are otherwise preserved, so a stale
425
- name is not silently rewritten. A pre-existing explicit `enabledAgents` list is
426
- also still preserved without appending `cleaner`; configs without cleaner receive
427
- the existing one-time setup notice.
423
+ The built-in reconnaissance role is now `explorer`, with no runtime `explore`
424
+ alias. Config loading automatically renames the old key in `enabledAgents`,
425
+ `agentModels`, and `agentThinkingLevels`, deduplicates an old/new pair, and persists
426
+ the normalized file. When both model or thinking keys are valid, the explicit
427
+ `explorer` value wins. Other configured non-empty names are preserved. A
428
+ pre-existing explicit `enabledAgents` list is also still preserved without
429
+ appending `cleaner`; configs without cleaner receive the existing one-time setup
430
+ notice.
428
431
 
429
432
  ## Agent discovery and overrides
430
433
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ferris1225/pi-subagents",
3
- "version": "4.0.0",
3
+ "version": "4.0.1",
4
4
  "description": "Controllable background sub-agent threads for pi: specialized roles, capability-aware thinking, direct main-model fallback, auto-fix chains, and Git worktree isolation.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/config.ts CHANGED
@@ -25,6 +25,13 @@ export const DEFAULT_ENABLED_AGENTS: readonly string[] = ["explorer", "worker",
25
25
  export const AGENT_SCOPE_VALUES = ["user", "project", "both"] as const;
26
26
  export type AgentScope = (typeof AGENT_SCOPE_VALUES)[number];
27
27
 
28
+ const LEGACY_EXPLORER_NAME = "explore";
29
+ const EXPLORER_NAME = "explorer";
30
+
31
+ function migrateAgentName(name: string): string {
32
+ return name === LEGACY_EXPLORER_NAME ? EXPLORER_NAME : name;
33
+ }
34
+
28
35
  /** Thinking levels accepted by pi's `--thinking` option. */
29
36
  export const THINKING_LEVEL_VALUES = ["off", "minimal", "low", "medium", "high", "xhigh", "max"] as const;
30
37
  export type ThinkingLevel = (typeof THINKING_LEVEL_VALUES)[number];
@@ -156,25 +163,48 @@ export function normalizeConfig(raw: unknown): SubagentsConfig {
156
163
  const names = raw.enabledAgents.filter(
157
164
  (name): name is string => typeof name === "string" && name.trim().length > 0,
158
165
  );
159
- // An explicitly empty array is honored (disables all agents); otherwise keep valid names.
160
- config.enabledAgents = [...new Set(names.map((name) => name.trim()))];
166
+ // An explicitly empty array is honored. Rename the former built-in key and
167
+ // deduplicate when a config already contains both spellings.
168
+ config.enabledAgents = [...new Set(names.map((name) => migrateAgentName(name.trim())))];
161
169
  }
162
170
 
163
171
  if (isRecord(raw.agentModels)) {
164
- for (const [key, value] of Object.entries(raw.agentModels)) {
165
- if (isModelReference(value)) config.agentModels[key.trim()] = value.trim();
172
+ const entries = Object.entries(raw.agentModels);
173
+ // A valid explicit new key wins regardless of JSON property order.
174
+ for (const [rawKey, value] of entries) {
175
+ const key = rawKey.trim();
176
+ if (key !== LEGACY_EXPLORER_NAME && isModelReference(value)) {
177
+ config.agentModels[key] = value.trim();
178
+ }
179
+ }
180
+ if (!Object.hasOwn(config.agentModels, EXPLORER_NAME)) {
181
+ const legacy = entries.find(([key, value]) =>
182
+ key.trim() === LEGACY_EXPLORER_NAME && isModelReference(value)
183
+ );
184
+ if (legacy && isModelReference(legacy[1])) config.agentModels[EXPLORER_NAME] = legacy[1].trim();
166
185
  }
167
186
  }
168
187
 
169
188
  if (isRecord(raw.agentThinkingLevels)) {
170
- for (const [key, value] of Object.entries(raw.agentThinkingLevels)) {
189
+ const entries = Object.entries(raw.agentThinkingLevels);
190
+ for (const [rawKey, value] of entries) {
191
+ const key = rawKey.trim();
171
192
  if (
193
+ key !== LEGACY_EXPLORER_NAME &&
172
194
  typeof value === "string" &&
173
195
  (THINKING_LEVEL_VALUES as readonly string[]).includes(value)
174
196
  ) {
175
- config.agentThinkingLevels[key.trim()] = value as ThinkingLevel;
197
+ config.agentThinkingLevels[key] = value as ThinkingLevel;
176
198
  }
177
199
  }
200
+ if (!Object.hasOwn(config.agentThinkingLevels, EXPLORER_NAME)) {
201
+ const legacy = entries.find(([key, value]) =>
202
+ key.trim() === LEGACY_EXPLORER_NAME &&
203
+ typeof value === "string" &&
204
+ (THINKING_LEVEL_VALUES as readonly string[]).includes(value)
205
+ );
206
+ if (legacy) config.agentThinkingLevels[EXPLORER_NAME] = legacy[1] as ThinkingLevel;
207
+ }
178
208
  }
179
209
 
180
210
  if (typeof raw.notifyOnReviewPass === "boolean") {