@ferris1225/pi-subagents 2.0.0 → 2.0.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 +14 -10
- package/package.json +1 -1
- package/src/dispatch.ts +2 -1
- package/src/format.ts +5 -1
- package/src/rpc-run.ts +5 -6
- package/src/setup.ts +49 -23
package/README.md
CHANGED
|
@@ -88,9 +88,10 @@ index.
|
|
|
88
88
|
- **Direct fallback with real thinking capabilities** — each agent has at most
|
|
89
89
|
one selected model. An unavailable selection, rate limit, invalid key, quota,
|
|
90
90
|
missing model, or provider failure hands directly to the current main model.
|
|
91
|
-
A child-only provider adapter forces request retries to zero
|
|
92
|
-
|
|
93
|
-
settings. Auto thinking clamps the
|
|
91
|
+
A child-only provider adapter forces inner request retries to zero; transient
|
|
92
|
+
stream drops still use Pi's outer turn retry, and only a settled model-level
|
|
93
|
+
failure hands off, without changing user settings. Auto thinking clamps the
|
|
94
|
+
agent preference to the
|
|
94
95
|
effective model's real `thinkingLevelMap`; manual setup shows only levels that
|
|
95
96
|
model supports.
|
|
96
97
|
- **Resumes, retargets, and forks preserve context** — every run is session-backed.
|
|
@@ -277,7 +278,9 @@ sessions show the recovery paths again until the artifacts are removed.
|
|
|
277
278
|
Stored at `~/.pi/agent/pi-subagents.json` (follows `PI_CODING_AGENT_DIR` when
|
|
278
279
|
set). `/subagents-setup` has five top-level choices: enable agents, configure one
|
|
279
280
|
agent's model/thinking, choose a vision model, runtime settings, or full setup.
|
|
280
|
-
|
|
281
|
+
After one agent's model + thinking picks, the wizard returns to the agent picker
|
|
282
|
+
so several agents can be configured in one pass; Esc at any step ends the pass
|
|
283
|
+
and keeps every agent already configured. There is no backup pool or global thinking menu. Model pickers show only in-scope
|
|
281
284
|
models with configured authentication and display their real supported thinking
|
|
282
285
|
levels. Thinking defaults to **Auto**; manual overrides show only levels supported
|
|
283
286
|
by that agent's effective model. `notifyOnReviewPass` and `maxResultLines` remain
|
|
@@ -329,12 +332,13 @@ available catalog is skipped. Any model-level runtime failure — rate limit,
|
|
|
329
332
|
quota, invalid key/auth, missing model, provider error, or idle model stream —
|
|
330
333
|
hands directly to current main, including stream errors that retain partial text.
|
|
331
334
|
A child-only Pi extension wraps the selected provider's registered API stream
|
|
332
|
-
with `maxRetries: 0
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
335
|
+
with `maxRetries: 0` so a deterministic auth/quota miss fails fast. Transient
|
|
336
|
+
stream drops such as xAI `terminated` still use Pi's outer turn retry — the
|
|
337
|
+
parent does not `abort_retry` them — and only a settled model-level failure
|
|
338
|
+
hands off to current main. This uses supported extension/RPC surfaces in Node
|
|
339
|
+
and standalone/Bun builds, never rewrites global or project settings, and does
|
|
340
|
+
not alter descendant tool environments. Tool/test failures stay on the same
|
|
341
|
+
model because they are task failures, not model availability failures. Only a truly
|
|
338
342
|
zero-activity process startup race can retry; an accepted prompt or any
|
|
339
343
|
agent/turn/stream/tool activity forbids replay.
|
|
340
344
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ferris1225/pi-subagents",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2",
|
|
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/dispatch.ts
CHANGED
|
@@ -1483,7 +1483,8 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
|
|
|
1483
1483
|
triggerTurn: completionTriggersTurn(result, runConfig.notifyOnReviewPass),
|
|
1484
1484
|
};
|
|
1485
1485
|
if (modelLevel) {
|
|
1486
|
-
|
|
1486
|
+
const detail = result.errorMessage?.trim() || "model unavailable or broken";
|
|
1487
|
+
runCtx.ui.notify(`✗ ${result.agent} dispatch failed: ${detail} — task handed to the main window`, "error");
|
|
1487
1488
|
} else if (dispatchFailed) {
|
|
1488
1489
|
runCtx.ui.notify(`✗ ${result.agent} dispatch failed: ${result.errorMessage ?? "dispatch crashed"}`, "error");
|
|
1489
1490
|
}
|
package/src/format.ts
CHANGED
|
@@ -155,11 +155,15 @@ export function formatCompletionBlock(
|
|
|
155
155
|
* fresh (which would re-scan everything). */
|
|
156
156
|
export function modelLevelTakeoverNote(result: SingleResult, opts?: { runId?: number }): string {
|
|
157
157
|
const retry = result.modelFallbackFrom ? ", and the current main model also failed" : "";
|
|
158
|
+
const detail = result.errorMessage?.trim();
|
|
159
|
+
const cause = detail
|
|
160
|
+
? `its model/provider call failed (${detail})`
|
|
161
|
+
: "its model was unavailable or failed (or the run stalled)";
|
|
158
162
|
const sessionPreserved = Boolean(result.sessionDir && result.sessionId) && opts?.runId !== undefined;
|
|
159
163
|
const recovery = sessionPreserved
|
|
160
164
|
? ` The sub-agent's earlier work in this run is preserved. Once a model is available again, call subagent_control with { action: "resume", id: ${opts!.runId} } to CONTINUE it in-context (it keeps the same run id and does not re-scan), or execute the task in the main window with your own tools.`
|
|
161
165
|
: ` Please execute this task in the main window with your own tools; do not re-dispatch it as a sub-agent.`;
|
|
162
|
-
return `The sub-agent could not complete this task:
|
|
166
|
+
return `The sub-agent could not complete this task: ${cause}${retry}.${recovery}`;
|
|
163
167
|
}
|
|
164
168
|
|
|
165
169
|
/** Resolve a run-id request to actual ids: an exact numeric match always wins
|
package/src/rpc-run.ts
CHANGED
|
@@ -813,12 +813,11 @@ export async function runRpcAgentAttempt(options: RunRpcAttemptOptions): Promise
|
|
|
813
813
|
result.rpcActivity = true;
|
|
814
814
|
}
|
|
815
815
|
|
|
816
|
-
//
|
|
817
|
-
//
|
|
818
|
-
//
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
}
|
|
816
|
+
// Let Pi's outer turn retry run. Grok/xAI long streams commonly drop with
|
|
817
|
+
// a retryable `terminated` mid-turn; aborting that retry was misread as
|
|
818
|
+
// "model unavailable" and handed a still-working model back to the parent.
|
|
819
|
+
// After retries exhaust, dispatch still classifies a settled model-level
|
|
820
|
+
// failure and hands off.
|
|
822
821
|
|
|
823
822
|
// Child RPC mode exposes extension dialogs. Sub-agents are non-interactive:
|
|
824
823
|
// cancel blocking dialogs so an unrelated child extension cannot deadlock.
|
package/src/setup.ts
CHANGED
|
@@ -204,11 +204,46 @@ async function pickAgentToConfigure(
|
|
|
204
204
|
return promptSelectOne(
|
|
205
205
|
ctx,
|
|
206
206
|
"Configure which agent?",
|
|
207
|
-
"Type to filter • ↑/↓ • Enter selects • Esc
|
|
207
|
+
"Type to filter • ↑/↓ • Enter selects • Esc ends this pass",
|
|
208
208
|
enabledAgents.map((name) => ({ value: name, label: moduleLabel(name) })),
|
|
209
209
|
);
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
+
/** One agent: model, then thinking if the model exposes a choice. Esc at any
|
|
213
|
+
* step ends the caller's pass; earlier agents in that pass stay applied. */
|
|
214
|
+
async function configureOneAgent(
|
|
215
|
+
ctx: ExtensionCommandContext,
|
|
216
|
+
config: SubagentsConfig,
|
|
217
|
+
): Promise<
|
|
218
|
+
| {
|
|
219
|
+
name: string;
|
|
220
|
+
model: string;
|
|
221
|
+
strength: ThinkingLevel | typeof AUTO_THINKING;
|
|
222
|
+
}
|
|
223
|
+
| undefined
|
|
224
|
+
> {
|
|
225
|
+
const name = await pickAgentToConfigure(ctx, config.enabledAgents);
|
|
226
|
+
if (name === undefined) return undefined;
|
|
227
|
+
const modelChoice = await pickAgentModel(
|
|
228
|
+
ctx,
|
|
229
|
+
name,
|
|
230
|
+
config.agentModels[name],
|
|
231
|
+
"stops — earlier agent changes are kept",
|
|
232
|
+
);
|
|
233
|
+
if (modelChoice === undefined) return undefined;
|
|
234
|
+
const model = effectiveModelForChoice(ctx, modelChoice);
|
|
235
|
+
const strength = await pickAgentStrength(
|
|
236
|
+
ctx,
|
|
237
|
+
name,
|
|
238
|
+
model,
|
|
239
|
+
config.agentThinkingLevels[name],
|
|
240
|
+
actualAgentThinkingDefault(ctx, config, name),
|
|
241
|
+
"stops — earlier agent changes are kept",
|
|
242
|
+
);
|
|
243
|
+
if (strength === undefined) return undefined;
|
|
244
|
+
return { name, model: modelChoice, strength };
|
|
245
|
+
}
|
|
246
|
+
|
|
212
247
|
async function pickInjection(ctx: ExtensionCommandContext, current: boolean): Promise<boolean | undefined> {
|
|
213
248
|
const on = "On — inject the delegation directive (recommended)";
|
|
214
249
|
const off = "Off — rely on tool descriptions only";
|
|
@@ -379,28 +414,19 @@ async function runMenu(ctx: ExtensionCommandContext, configPath: string, config:
|
|
|
379
414
|
next.agentModels = keepAgentEntries(next.agentModels, enabled);
|
|
380
415
|
next.agentThinkingLevels = keepAgentEntries(next.agentThinkingLevels, enabled);
|
|
381
416
|
} else if (choice.startsWith("Configure")) {
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
next
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
model,
|
|
396
|
-
next.agentThinkingLevels[agentName],
|
|
397
|
-
actualAgentThinkingDefault(ctx, next, agentName),
|
|
398
|
-
"cancels agent changes",
|
|
399
|
-
);
|
|
400
|
-
if (strength === undefined) return notifyCancelled(ctx);
|
|
401
|
-
next.agentModels = applyAgentModelChoice(next.agentModels, agentName, modelChoice);
|
|
402
|
-
if (strength === AUTO_THINKING) delete next.agentThinkingLevels[agentName];
|
|
403
|
-
else next.agentThinkingLevels[agentName] = strength;
|
|
417
|
+
// Per-agent loop: model (+ thinking when the model exposes a choice), then
|
|
418
|
+
// back to the agent picker so several agents can be set in one pass. Esc
|
|
419
|
+
// at any step ends the loop; agents already configured in this pass are kept.
|
|
420
|
+
let configuredAny = false;
|
|
421
|
+
while (true) {
|
|
422
|
+
const picked = await configureOneAgent(ctx, next);
|
|
423
|
+
if (picked === undefined) break;
|
|
424
|
+
configuredAny = true;
|
|
425
|
+
next.agentModels = applyAgentModelChoice(next.agentModels, picked.name, picked.model);
|
|
426
|
+
if (picked.strength === AUTO_THINKING) delete next.agentThinkingLevels[picked.name];
|
|
427
|
+
else next.agentThinkingLevels[picked.name] = picked.strength;
|
|
428
|
+
}
|
|
429
|
+
if (!configuredAny) return notifyCancelled(ctx);
|
|
404
430
|
} else if (choice.startsWith("Change vision")) {
|
|
405
431
|
const visionModel = await pickVisionModel(ctx, config.visionModel);
|
|
406
432
|
if (visionModel === undefined) return notifyCancelled(ctx);
|