@ferris1225/pi-subagents 2.3.1 → 3.0.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 +25 -36
- package/package.json +1 -1
- package/src/announcements.ts +0 -6
- package/src/config.ts +2 -13
- package/src/dispatch.ts +3 -40
- package/src/index.ts +1 -1
- package/src/models.ts +3 -9
- package/src/prompt.ts +0 -4
- package/src/runtime.ts +0 -1
- package/src/setup.ts +1 -26
- package/src/thread-lifecycle.ts +3 -11
package/README.md
CHANGED
|
@@ -61,11 +61,11 @@ index.
|
|
|
61
61
|
into the main system prompt, so the main model sends broad searches to `explore`,
|
|
62
62
|
self-contained implementations to `worker`, explicit cleanup intent to `cleaner`,
|
|
63
63
|
and pre-commit reviews to `reviewer`. You just use pi; delegation happens by itself.
|
|
64
|
-
- **
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
64
|
+
- **Multimodal work is a model choice, not a mode** — an agent that should see
|
|
65
|
+
screenshots, mockups, or its own rendered pages simply gets a multimodal model
|
|
66
|
+
through `/subagents-setup` (the picker labels each model `vision` or
|
|
67
|
+
`text-only`). The agent reads images with its `read` tool on whatever model it
|
|
68
|
+
runs; no per-task flag, no separate vision override.
|
|
69
69
|
- **Results come back on their own** — completions are delivered as messages that
|
|
70
70
|
wake the main agent automatically, even mid-turn. No polling, no `sleep`, no
|
|
71
71
|
"go check" step. `subagent_wait` is a **non-blocking** in-turn lookup by default
|
|
@@ -201,15 +201,16 @@ subagent({
|
|
|
201
201
|
|
|
202
202
|
### Cleanup routing and lifecycle
|
|
203
203
|
|
|
204
|
-
The injected guidance routes `cleaner` by explicit semantic intent
|
|
205
|
-
|
|
206
|
-
Requested periodic maintenance passes also
|
|
207
|
-
never run automatically as the
|
|
204
|
+
The injected guidance routes `cleaner` by explicit semantic intent in any
|
|
205
|
+
language the conversation uses: **code cleanup**, **dead code**, redundancy,
|
|
206
|
+
simplification, or over-engineering. Requested periodic maintenance passes also
|
|
207
|
+
qualify; PR counts do not, and cleaner is never run automatically as the
|
|
208
|
+
pre-commit gate.
|
|
208
209
|
|
|
209
|
-
- **Audit mode:** audit/find/report/review
|
|
210
|
-
|
|
211
|
-
- **Apply mode:** explicit remove/clean/simplify/refactor
|
|
212
|
-
|
|
210
|
+
- **Audit mode:** audit/find/report/review wording produces read-only ranked
|
|
211
|
+
evidence.
|
|
212
|
+
- **Apply mode:** explicit remove/clean/simplify/refactor wording permits the
|
|
213
|
+
smallest proven edits plus narrow-then-broad verification.
|
|
213
214
|
|
|
214
215
|
```text
|
|
215
216
|
explicit cleanup intent → cleaner (audit or apply)
|
|
@@ -220,33 +221,23 @@ cleaner apply → reviewer gate → worker auto-fix (on REVIEW_FAIL) → reviewe
|
|
|
220
221
|
portion runs only when enabled by `maxFixRounds`; cleaner itself is not a pre-commit
|
|
221
222
|
hook or a PR-count scheduler.
|
|
222
223
|
|
|
223
|
-
###
|
|
224
|
+
### Image work (screenshots / mockups / designs)
|
|
224
225
|
|
|
225
|
-
|
|
226
|
-
|
|
226
|
+
There is no vision flag or separate vision model. Give the agent a multimodal
|
|
227
|
+
model in `/subagents-setup` and name the exact image paths in the task:
|
|
227
228
|
|
|
228
229
|
```ts
|
|
229
230
|
subagent({
|
|
230
231
|
agent: "reviewer",
|
|
231
232
|
task: "Compare the UI in screenshots/settings.png against the mockup design.png; list every visual mismatch.",
|
|
232
|
-
vision: true,
|
|
233
233
|
});
|
|
234
234
|
```
|
|
235
235
|
|
|
236
|
-
The sub-agent reads images with its `read` tool
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
As a deterministic fallback, a brief that names an image file (an extension such
|
|
243
|
-
as `.png`, `.jpg`, `.webp`) or describes frontend/UI work — Vue/React/Svelte
|
|
244
|
-
pages, mockups, screenshots, page styling — is dispatched as
|
|
245
|
-
vision even without the flag: the model cannot be swapped mid-run, so a worker
|
|
246
|
-
that screenshots and visually verifies what it builds must start on a
|
|
247
|
-
vision-capable model. How do you verify which model ran? The live widget line,
|
|
248
|
-
dispatch result row, and `subagent_status` all show each run's effective model
|
|
249
|
-
id, and a selected→main handoff is labeled with its origin.
|
|
236
|
+
The sub-agent reads images with its `read` tool on its configured model; the
|
|
237
|
+
setup picker labels each model `vision` or `text-only` so the choice is visible.
|
|
238
|
+
The live widget line, dispatch result row, and `subagent_status` all show each
|
|
239
|
+
run's effective model id, and a selected→main handoff is labeled with its
|
|
240
|
+
origin.
|
|
250
241
|
|
|
251
242
|
### Controlling and stopping
|
|
252
243
|
|
|
@@ -302,8 +293,8 @@ sessions show the recovery paths again until the artifacts are removed.
|
|
|
302
293
|
## Configuration
|
|
303
294
|
|
|
304
295
|
Stored at `~/.pi/agent/pi-subagents.json` (follows `PI_CODING_AGENT_DIR` when
|
|
305
|
-
set). `/subagents-setup` has
|
|
306
|
-
agent's model/thinking,
|
|
296
|
+
set). `/subagents-setup` has four top-level choices: enable agents, configure one
|
|
297
|
+
agent's model/thinking, runtime settings, or full setup.
|
|
307
298
|
After one agent's model + thinking picks, the wizard returns to the agent picker
|
|
308
299
|
so several agents can be configured in one pass; Esc at any step ends the pass
|
|
309
300
|
and keeps every agent already configured. There is no backup pool or global thinking menu. Model pickers show only in-scope
|
|
@@ -321,7 +312,6 @@ direct-file settings.
|
|
|
321
312
|
"agentThinkingLevels": {
|
|
322
313
|
"reviewer": "high"
|
|
323
314
|
},
|
|
324
|
-
"visionModel": "anthropic/claude-sonnet-4-5",
|
|
325
315
|
"notifyOnReviewPass": false,
|
|
326
316
|
"maxResultLines": 80,
|
|
327
317
|
"proactiveInjection": true,
|
|
@@ -337,7 +327,6 @@ direct-file settings.
|
|
|
337
327
|
| `enabledAgents` | Agent names exposed to discovery and prompt injection. An empty array disables all agents. |
|
|
338
328
|
| `agentModels` | Optional selected `provider/model-id` per agent. Missing = current main model. Model-level failure hands directly to current main. |
|
|
339
329
|
| `agentThinkingLevels` | Optional manual preference per agent. Missing = Auto (agent frontmatter preference, or `high`, clamped to the effective model's supported levels). |
|
|
340
|
-
| `visionModel` | Optional vision-capable model for `vision: true` tasks (screenshots/mockups/designs). Unset = falls back to the main session's current model. |
|
|
341
330
|
| `notifyOnReviewPass` | When `true`, a passing reviewer result is delivered without waking the main agent (default `false`). |
|
|
342
331
|
| `maxResultLines` | Max lines of a sub-agent result carried in the completion message (default `80`). Longer results are truncated; full text is written to an extension-named temporary `.md`. At session start and on each write, only recognized result files older than 7 days are removed; each canonical project path has its own newest-50 bucket. |
|
|
343
332
|
| `proactiveInjection` | Whether to add the delegation directive to the main system prompt. |
|
|
@@ -349,7 +338,7 @@ direct-file settings.
|
|
|
349
338
|
### Model routing and thinking
|
|
350
339
|
|
|
351
340
|
```text
|
|
352
|
-
selected agent
|
|
341
|
+
selected agent model → current main-window model
|
|
353
342
|
```
|
|
354
343
|
|
|
355
344
|
Without a selected model, current main runs immediately; agent frontmatter model
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ferris1225/pi-subagents",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
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/announcements.ts
CHANGED
|
@@ -13,12 +13,6 @@ const ANNOUNCEMENTS: Array<{
|
|
|
13
13
|
condition: (config: Awaited<ReturnType<typeof loadConfig>>) => boolean;
|
|
14
14
|
message: string;
|
|
15
15
|
}> = [
|
|
16
|
-
{
|
|
17
|
-
key: "visionModel",
|
|
18
|
-
condition: (config) => config.visionModel === undefined,
|
|
19
|
-
message:
|
|
20
|
-
"pi-subagents: new — a vision-capable model can now handle image tasks (screenshots, mockups, designs). Run /subagents-setup to configure it; until set, vision tasks use the main session's current model.",
|
|
21
|
-
},
|
|
22
16
|
{
|
|
23
17
|
key: "cleanerAgent",
|
|
24
18
|
condition: (config) => !config.enabledAgents.includes("cleaner"),
|
package/src/config.ts
CHANGED
|
@@ -99,15 +99,8 @@ export interface SubagentsConfig {
|
|
|
99
99
|
*/
|
|
100
100
|
idleTimeoutSec: number;
|
|
101
101
|
/**
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
* current model.
|
|
105
|
-
*/
|
|
106
|
-
visionModel?: string;
|
|
107
|
-
/**
|
|
108
|
-
* One-time feature announcements already shown to the user (e.g. "vision
|
|
109
|
-
* model" after an update that introduced it). Persisted so the notice never
|
|
110
|
-
* nags again.
|
|
102
|
+
* One-time feature announcements already shown to the user. Persisted so
|
|
103
|
+
* the notice never nags again.
|
|
111
104
|
*/
|
|
112
105
|
announcedFeatures: string[];
|
|
113
106
|
}
|
|
@@ -212,10 +205,6 @@ export function normalizeConfig(raw: unknown): SubagentsConfig {
|
|
|
212
205
|
config.idleTimeoutSec = Math.max(0, Math.min(IDLE_TIMEOUT_SEC_LIMIT, Math.round(raw.idleTimeoutSec)));
|
|
213
206
|
}
|
|
214
207
|
|
|
215
|
-
if (isModelReference(raw.visionModel)) {
|
|
216
|
-
config.visionModel = (raw.visionModel as string).trim();
|
|
217
|
-
}
|
|
218
|
-
|
|
219
208
|
if (Array.isArray(raw.announcedFeatures)) {
|
|
220
209
|
config.announcedFeatures = raw.announcedFeatures.filter(
|
|
221
210
|
(feature): feature is string => typeof feature === "string" && feature.trim().length > 0,
|
package/src/dispatch.ts
CHANGED
|
@@ -3,9 +3,6 @@
|
|
|
3
3
|
* child processes, single or parallel. Owns the public dispatch contract,
|
|
4
4
|
* per-run status tracking, the auto-fix chain (REVIEW_FAIL → worker → re-review),
|
|
5
5
|
* and completion delivery. Stable thread generations live in thread-lifecycle.ts.
|
|
6
|
-
*
|
|
7
|
-
* Vision: a task flagged `vision: true` uses the configured vision model, then
|
|
8
|
-
* hands directly to the current main-window model on model/provider failure.
|
|
9
6
|
*/
|
|
10
7
|
|
|
11
8
|
import { StringEnum } from "@earendil-works/pi-ai";
|
|
@@ -58,9 +55,6 @@ export { FORK_CONTINUATION_PROMPT, isWorktreeCapableAgent } from "./thread-lifec
|
|
|
58
55
|
|
|
59
56
|
const NON_BLANK_TASK_OPTIONS = { minLength: 1, pattern: "\\S" } as const;
|
|
60
57
|
|
|
61
|
-
const VISION_DESCRIPTION =
|
|
62
|
-
"Set true when the task may require viewing images (screenshots, mockups, designs) — the configured vision model is used first, then model-level failures hand directly to the current main-window model. A task that names an image file or describes frontend/UI work (Vue/React, mockups, screenshots, page styling) is dispatched as vision automatically";
|
|
63
|
-
|
|
64
58
|
const ISOLATION_DESCRIPTION =
|
|
65
59
|
"Filesystem isolation: shared uses the caller's working tree; worktree creates a detached temporary Git worktree (write-capable agents, including worker and cleaner, only)";
|
|
66
60
|
|
|
@@ -75,7 +69,6 @@ const TaskItem = Type.Object({
|
|
|
75
69
|
description: "Self-contained task to delegate (the agent has no memory of this conversation)",
|
|
76
70
|
}),
|
|
77
71
|
cwd: Type.Optional(Type.String({ description: "Working directory for the agent process" })),
|
|
78
|
-
vision: Type.Optional(Type.Boolean({ description: VISION_DESCRIPTION })),
|
|
79
72
|
isolation: IsolationSchema,
|
|
80
73
|
});
|
|
81
74
|
|
|
@@ -86,7 +79,6 @@ const SubagentParams = Type.Object({
|
|
|
86
79
|
),
|
|
87
80
|
tasks: Type.Optional(Type.Array(TaskItem, { description: "Array of {agent, task} for parallel execution" })),
|
|
88
81
|
cwd: Type.Optional(Type.String({ description: "Working directory for the agent process (single mode)" })),
|
|
89
|
-
vision: Type.Optional(Type.Boolean({ description: VISION_DESCRIPTION })),
|
|
90
82
|
isolation: IsolationSchema,
|
|
91
83
|
});
|
|
92
84
|
|
|
@@ -95,27 +87,6 @@ export function defaultIsolationMode(mode: "single" | "parallel", agentName: str
|
|
|
95
87
|
return mode === "parallel" && agentName === "worker" ? "worktree" : "shared";
|
|
96
88
|
}
|
|
97
89
|
|
|
98
|
-
const IMAGE_FILE_PATTERN = /\.(?:png|jpe?g|webp|gif|bmp|tiff?|avif)\b/i;
|
|
99
|
-
/** Frontend/UI work: the agent may screenshot and visually verify what it
|
|
100
|
-
* builds, and the model cannot be swapped mid-run — vision must be on at
|
|
101
|
-
* spawn. Only language-neutral signals: code terms and file names stay in
|
|
102
|
-
* English even in non-English briefs; purely semantic wording is the LLM's
|
|
103
|
-
* job via the vision flag, not a per-language keyword list. Word boundaries
|
|
104
|
-
* keep ordinary words (build, guide, reactor) from matching. */
|
|
105
|
-
const FRONTEND_PATTERN = /\b(?:vue|react|svelte|angular|nuxt|next\.?js|vite|tailwind|screenshots?|mockups?|wireframes?|UI)\b/i;
|
|
106
|
-
const FRONTEND_FILE_PATTERN = /\.(?:vue|html?|css|svg)\b/i;
|
|
107
|
-
|
|
108
|
-
/** Deterministic vision fallback: a brief that names an image file or describes
|
|
109
|
-
* frontend/UI work is vision work even when the dispatcher forgot the flag —
|
|
110
|
-
* prompting alone cannot guarantee `vision: true`, and a non-vision model can
|
|
111
|
-
* neither see reference images nor visually verify what it builds. */
|
|
112
|
-
export function taskImpliesVision(task: string): boolean {
|
|
113
|
-
return IMAGE_FILE_PATTERN.test(task) || FRONTEND_FILE_PATTERN.test(task) || FRONTEND_PATTERN.test(task);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const wantsVision = (flag: boolean | undefined, task: string): boolean =>
|
|
117
|
-
flag === true || taskImpliesVision(task);
|
|
118
|
-
|
|
119
90
|
const autoFixRootTails = new Map<string, Promise<void>>();
|
|
120
91
|
|
|
121
92
|
async function canonicalAutoFixRoot(cwd: string): Promise<string> {
|
|
@@ -172,7 +143,6 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
|
|
|
172
143
|
"It starts agents in the background and immediately returns control to the main window; completion messages automatically wake the main agent to continue.",
|
|
173
144
|
"Each agent has no memory of this conversation — brief it fully (goal, exact paths, constraints, expected output).",
|
|
174
145
|
"Results arrive as wake-up messages automatically — you do NOT need to wait. If you must get a result in-turn, subagent_wait is a non-blocking lookup by default (pass timeoutMs to block).",
|
|
175
|
-
"Vision: set vision: true when the task may require viewing images (screenshots, mockups, design files — e.g. frontend work) — the configured vision model is used first, then model-level failures hand directly to the current main-window model.",
|
|
176
146
|
].join(" "),
|
|
177
147
|
promptSnippet:
|
|
178
148
|
"Start background subagents: explore (read-only search), worker (implement), cleaner (explicit evidence-first cleanup), reviewer (pre-commit review); completion automatically resumes the main agent. Simple tasks: use direct tools, not subagents.",
|
|
@@ -188,7 +158,6 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
|
|
|
188
158
|
"Use isolation: worktree only for worker, cleaner, or another write-capable agent and only inside a Git repository with a committed HEAD; parallel worker tasks default to worktree, while cleaner must opt in. Setup or integration failures never silently fall back to shared.",
|
|
189
159
|
"NEVER sleep or poll, and do NOT call subagent_wait to hold the turn — subagent ends the turn immediately and the result arrives as a message that wakes you automatically (even mid-turn). Ending your turn is the default and the only correct way to wait.",
|
|
190
160
|
"If you must keep the turn for a result, call subagent_wait with an explicit timeoutMs (non-blocking by default) — never bash sleep/timeout to wait for a sub-agent.",
|
|
191
|
-
"When a delegated task may require viewing images (frontend screenshots, mockups, design comparisons), pass vision: true and give the sub-agent the exact image paths — it reads them with its read tool. The configured vision model is used first; model-level failures hand directly to the current main-window model.",
|
|
192
161
|
"When a sub-agent result arrives it is already shown to the user — do NOT restate, paraphrase, or summarize it; reply with only your own conclusion or next action (often just one line), since duplicating the result wastes tokens for nothing.",
|
|
193
162
|
],
|
|
194
163
|
parameters: SubagentParams,
|
|
@@ -320,13 +289,10 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
|
|
|
320
289
|
executionCwd: string,
|
|
321
290
|
signal: AbortSignal,
|
|
322
291
|
meta: RunChainMeta,
|
|
323
|
-
vision = false,
|
|
324
292
|
): Promise<{ runId?: number; result: SingleResult }> => {
|
|
325
293
|
const agent = agents.find((candidate) => candidate.name === agentName);
|
|
326
294
|
if (!agent) return { result: failedStartResult(agentName, task, `Unknown agent: "${agentName}".`) };
|
|
327
|
-
|
|
328
|
-
// hands directly to the current main model with re-clamped thinking.
|
|
329
|
-
const route = resolveDispatchModelRoute(agent, config, ctx, vision);
|
|
295
|
+
const route = resolveDispatchModelRoute(agent, config, ctx);
|
|
330
296
|
const thinkingLevel = route.thinkingLevel;
|
|
331
297
|
const runId = monitor.addRun(agent.name, task, route.agent.model, thinkingLevel, meta);
|
|
332
298
|
const onLive = makeLiveHandler(runId);
|
|
@@ -397,7 +363,6 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
|
|
|
397
363
|
parentGroupId: string,
|
|
398
364
|
parentRunId: number,
|
|
399
365
|
executionCwd: string,
|
|
400
|
-
vision = false,
|
|
401
366
|
): void => {
|
|
402
367
|
const parentThreadAtStart = runtime.threads.get(parentRunId);
|
|
403
368
|
if (!parentThreadAtStart) return;
|
|
@@ -446,7 +411,7 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
|
|
|
446
411
|
groupId: parentGroupId,
|
|
447
412
|
relationLabel: `fix round ${round}`,
|
|
448
413
|
parentRunId,
|
|
449
|
-
}
|
|
414
|
+
});
|
|
450
415
|
// Preserve the newest sub-step before checking chain ownership. A
|
|
451
416
|
// destructive stop invalidates ownsParent() while this child is
|
|
452
417
|
// aborting, and its partial output must become the parent's stopped
|
|
@@ -470,7 +435,7 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
|
|
|
470
435
|
groupId: parentGroupId,
|
|
471
436
|
relationLabel: `re-review round ${round}`,
|
|
472
437
|
parentRunId,
|
|
473
|
-
}
|
|
438
|
+
});
|
|
474
439
|
if (
|
|
475
440
|
runtime.threads.get(parentRunId) === parentThreadAtStart &&
|
|
476
441
|
parentThreadAtStart.generation === parentGeneration
|
|
@@ -649,7 +614,6 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
|
|
|
649
614
|
item.agent,
|
|
650
615
|
item.task,
|
|
651
616
|
item.cwd,
|
|
652
|
-
wantsVision(item.vision, item.task),
|
|
653
617
|
defaultIsolationMode("parallel", item.agent, item.isolation as IsolationMode | undefined),
|
|
654
618
|
));
|
|
655
619
|
}
|
|
@@ -687,7 +651,6 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
|
|
|
687
651
|
params.agent as string,
|
|
688
652
|
params.task as string,
|
|
689
653
|
params.cwd,
|
|
690
|
-
wantsVision(params.vision, params.task as string),
|
|
691
654
|
defaultIsolationMode("single", params.agent as string, params.isolation as IsolationMode | undefined),
|
|
692
655
|
);
|
|
693
656
|
if (result.exitCode !== -1) {
|
package/src/index.ts
CHANGED
|
@@ -69,7 +69,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
69
69
|
registerLookupTools(pi, runtime);
|
|
70
70
|
|
|
71
71
|
pi.registerCommand("subagents-setup", {
|
|
72
|
-
description: "Configure pi-subagents: agents, selected models, capability-aware thinking,
|
|
72
|
+
description: "Configure pi-subagents: agents, selected models, capability-aware thinking, and runtime settings",
|
|
73
73
|
handler: async (_args, ctx) => {
|
|
74
74
|
await runSetup(ctx, configPath);
|
|
75
75
|
},
|
package/src/models.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* Model routing, capability-aware thinking, and setup-picker helpers.
|
|
3
3
|
*
|
|
4
|
-
* Runtime has one explicit fallback only: a configured agent
|
|
4
|
+
* Runtime has one explicit fallback only: a configured agent model hands
|
|
5
5
|
* off directly to the current main-window model. Setup lists only currently
|
|
6
6
|
* available models and derives thinking choices from Pi's model metadata.
|
|
7
7
|
*/
|
|
@@ -20,8 +20,6 @@ export type ModelContext = Pick<ExtensionContext, "model" | "modelRegistry"> &
|
|
|
20
20
|
|
|
21
21
|
export const CURRENT_MAIN_MODEL = "__current_main_model__";
|
|
22
22
|
|
|
23
|
-
export type ModelPickerSlot = "agent" | "vision";
|
|
24
|
-
|
|
25
23
|
export interface ModelPickerItem {
|
|
26
24
|
value: string;
|
|
27
25
|
label: string;
|
|
@@ -136,11 +134,10 @@ function modelCapabilities(model: ModelListEntry): string {
|
|
|
136
134
|
return `${input} · thinking: ${thinking}`;
|
|
137
135
|
}
|
|
138
136
|
|
|
139
|
-
/** Build one searchable list for agent
|
|
137
|
+
/** Build one searchable list for agent model selection. Only models Pi
|
|
140
138
|
* currently reports as available are supplied by setup. */
|
|
141
139
|
export function buildModelPickerItems(options: {
|
|
142
140
|
models: readonly ModelListEntry[];
|
|
143
|
-
slot: ModelPickerSlot;
|
|
144
141
|
configuredRef?: string;
|
|
145
142
|
mainRef?: string;
|
|
146
143
|
}): ModelPickerItem[] {
|
|
@@ -153,7 +150,6 @@ export function buildModelPickerItems(options: {
|
|
|
153
150
|
}
|
|
154
151
|
|
|
155
152
|
const refs = [...byRef.keys()]
|
|
156
|
-
.filter((ref) => options.slot !== "vision" || byRef.get(ref)?.input.includes("image") === true)
|
|
157
153
|
.sort((left, right) => {
|
|
158
154
|
const leftRank = left === configuredRef ? 0 : left === mainRef ? 1 : 2;
|
|
159
155
|
const rightRank = right === configuredRef ? 0 : right === mainRef ? 1 : 2;
|
|
@@ -163,9 +159,7 @@ export function buildModelPickerItems(options: {
|
|
|
163
159
|
const dynamic: ModelPickerItem = {
|
|
164
160
|
value: CURRENT_MAIN_MODEL,
|
|
165
161
|
label: "Current main model (dynamic)",
|
|
166
|
-
description:
|
|
167
|
-
? "Clear vision override; use the current main model for image tasks"
|
|
168
|
-
: "Clear agent override; use the current main model dynamically",
|
|
162
|
+
description: "Clear agent override; use the current main model dynamically",
|
|
169
163
|
};
|
|
170
164
|
const items: ModelPickerItem[] = [dynamic];
|
|
171
165
|
for (const ref of refs) {
|
package/src/prompt.ts
CHANGED
|
@@ -66,10 +66,6 @@ ${hasMultiple ? `- Run INDEPENDENT tasks in parallel: one subagent call with a \
|
|
|
66
66
|
- Treat delegated agents as leaf workers: do not ask a sub-agent to dispatch another sub-agent; child processes do not have this tool. Use \`subagent_control fork\` on a parked/settled retained thread when you need an independent continuation with preserved context and a new run id.
|
|
67
67
|
- Trust but verify: a sub-agent's summary describes intent, not outcome. Check the actual changes/results before reporting work done.
|
|
68
68
|
${hasExplore ? "- Treat `explore` findings as a retrieval index, never as sole proof for edits, deletion, security, compatibility, persistence, or dynamic reachability. Re-read load-bearing files before acting. An underpowered model can be false economy on complex dynamic, concurrent, migration, or security-sensitive code; use a stronger model or specialist there.\n" : ""}
|
|
69
|
-
Vision tasks:
|
|
70
|
-
- Judge whether a delegated task may require viewing images (frontend screenshots, mockups, design files, visual regression comparisons). If it might, pass \`vision: true\` in the subagent call and give the sub-agent the exact image paths — it reads them with its read tool. Naming an image file or describing frontend/UI work (Vue/React page, mockup, page styling) dispatches as vision automatically, but keep passing the flag for image work described without those signals.
|
|
71
|
-
- \`vision: true\` runs the sub-agent on the vision-capable model configured in /subagents-setup; when none is configured it falls back to the main session's current model. Do not skip the flag because the agent's default model looks fast — a non-vision model cannot see the images.
|
|
72
|
-
|
|
73
69
|
Result handoff (do not re-state):
|
|
74
70
|
- A sub-agent's result arrives as a message that is already shown to the user. Do NOT restate, paraphrase, or re-summarize its findings in your reply — that just burns tokens duplicating what is already visible. The user can read the result above.
|
|
75
71
|
- Reply only with what you ADD: your own conclusion, the next action you are taking, or a one-line acknowledgement. When the result already answers the user, a single sentence is enough — then end your turn or proceed.
|
package/src/runtime.ts
CHANGED
|
@@ -52,7 +52,6 @@ export interface SubagentThread {
|
|
|
52
52
|
cwd: string;
|
|
53
53
|
/** Actual child cwd (the equivalent path inside an isolated worktree). */
|
|
54
54
|
executionCwd: string;
|
|
55
|
-
vision: boolean;
|
|
56
55
|
thinkingLevel?: ThinkingLevel;
|
|
57
56
|
isolation: IsolationMode;
|
|
58
57
|
worktree?: WorktreeIsolation;
|
package/src/setup.ts
CHANGED
|
@@ -37,7 +37,6 @@ import {
|
|
|
37
37
|
modelRef,
|
|
38
38
|
resolveThinkingLevel,
|
|
39
39
|
supportedThinkingLevels,
|
|
40
|
-
type ModelPickerSlot,
|
|
41
40
|
} from "./models.ts";
|
|
42
41
|
import { promptSelectMany, promptSelectOne } from "./ui.ts";
|
|
43
42
|
import { discoverAgents } from "./agents.ts";
|
|
@@ -96,14 +95,12 @@ async function pickEnabledAgents(
|
|
|
96
95
|
async function pickConfiguredModel(
|
|
97
96
|
ctx: ExtensionCommandContext,
|
|
98
97
|
title: string,
|
|
99
|
-
slot: ModelPickerSlot,
|
|
100
98
|
configuredRef: string | undefined,
|
|
101
99
|
escNote: string,
|
|
102
100
|
): Promise<string | undefined> {
|
|
103
101
|
const models = availableModelsInScope(ctx);
|
|
104
102
|
const items = buildModelPickerItems({
|
|
105
103
|
models,
|
|
106
|
-
slot,
|
|
107
104
|
configuredRef,
|
|
108
105
|
mainRef: currentModelRef(ctx),
|
|
109
106
|
});
|
|
@@ -122,20 +119,7 @@ async function pickAgentModel(
|
|
|
122
119
|
currentRef: string | undefined,
|
|
123
120
|
escNote = "cancels setup",
|
|
124
121
|
): Promise<string | undefined> {
|
|
125
|
-
return pickConfiguredModel(ctx, `Model for "${agentName}"?`,
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
async function pickVisionModel(
|
|
129
|
-
ctx: ExtensionCommandContext,
|
|
130
|
-
currentRef: string | undefined,
|
|
131
|
-
): Promise<string | undefined> {
|
|
132
|
-
return pickConfiguredModel(
|
|
133
|
-
ctx,
|
|
134
|
-
"Vision model for image tasks? (failure → current main model)",
|
|
135
|
-
"vision",
|
|
136
|
-
currentRef,
|
|
137
|
-
"cancels setup",
|
|
138
|
-
);
|
|
122
|
+
return pickConfiguredModel(ctx, `Model for "${agentName}"?`, currentRef, escNote);
|
|
139
123
|
}
|
|
140
124
|
|
|
141
125
|
const THINKING_LEVEL_HINTS: Record<ThinkingLevel, string> = {
|
|
@@ -304,8 +288,6 @@ async function runFullSetup(ctx: ExtensionCommandContext, configPath: string, ba
|
|
|
304
288
|
agentModels = applyAgentModelChoice(agentModels, agentName, choice);
|
|
305
289
|
}
|
|
306
290
|
|
|
307
|
-
const visionModel = await pickVisionModel(ctx, base.visionModel);
|
|
308
|
-
if (visionModel === undefined) return notifyCancelled(ctx);
|
|
309
291
|
const injection = await pickInjection(ctx, base.proactiveInjection);
|
|
310
292
|
if (injection === undefined) return notifyCancelled(ctx);
|
|
311
293
|
const scope = await pickScope(ctx, base.agentScope);
|
|
@@ -349,7 +331,6 @@ async function runFullSetup(ctx: ExtensionCommandContext, configPath: string, ba
|
|
|
349
331
|
idleTimeoutSec,
|
|
350
332
|
announcedFeatures: base.announcedFeatures,
|
|
351
333
|
};
|
|
352
|
-
if (visionModel !== CURRENT_MAIN_MODEL) next.visionModel = visionModel;
|
|
353
334
|
await saveConfig(next, configPath);
|
|
354
335
|
ctx.ui.notify(`pi-subagents configured with Auto thinking. Saved to ${configPath}`, "info");
|
|
355
336
|
}
|
|
@@ -395,7 +376,6 @@ async function runMenu(ctx: ExtensionCommandContext, configPath: string, config:
|
|
|
395
376
|
const choice = await ctx.ui.select("pi-subagents settings", [
|
|
396
377
|
"Enable/disable agents",
|
|
397
378
|
"Configure an agent (model + thinking)",
|
|
398
|
-
"Change vision model",
|
|
399
379
|
"Runtime settings",
|
|
400
380
|
"Full re-setup",
|
|
401
381
|
]);
|
|
@@ -427,11 +407,6 @@ async function runMenu(ctx: ExtensionCommandContext, configPath: string, config:
|
|
|
427
407
|
else next.agentThinkingLevels[picked.name] = picked.strength;
|
|
428
408
|
}
|
|
429
409
|
if (!configuredAny) return notifyCancelled(ctx);
|
|
430
|
-
} else if (choice.startsWith("Change vision")) {
|
|
431
|
-
const visionModel = await pickVisionModel(ctx, config.visionModel);
|
|
432
|
-
if (visionModel === undefined) return notifyCancelled(ctx);
|
|
433
|
-
if (visionModel === CURRENT_MAIN_MODEL) delete next.visionModel;
|
|
434
|
-
else next.visionModel = visionModel;
|
|
435
410
|
} else {
|
|
436
411
|
const updated = await updateRuntimeSetting(ctx, next);
|
|
437
412
|
if (updated === undefined) return notifyCancelled(ctx);
|
package/src/thread-lifecycle.ts
CHANGED
|
@@ -92,12 +92,11 @@ export function resolveDispatchModelRoute(
|
|
|
92
92
|
agent: AgentConfig,
|
|
93
93
|
config: SubagentsConfig,
|
|
94
94
|
ctx: ExtensionContext,
|
|
95
|
-
vision: boolean,
|
|
96
95
|
): DispatchModelRoute {
|
|
97
96
|
const availableModels = availableModelsInScope(ctx);
|
|
98
97
|
const mainRef = currentModelRef(ctx);
|
|
99
98
|
const route = resolveAgentModelRoute({
|
|
100
|
-
selectedRef:
|
|
99
|
+
selectedRef: config.agentModels[agent.name],
|
|
101
100
|
mainRef,
|
|
102
101
|
declaredDefaultRef: agent.model,
|
|
103
102
|
availableRefs: availableModels.map(modelRef),
|
|
@@ -137,7 +136,6 @@ interface BackgroundDispatcherOptions extends DispatchEnvironment {
|
|
|
137
136
|
parentGroupId: string,
|
|
138
137
|
parentRunId: number,
|
|
139
138
|
executionCwd: string,
|
|
140
|
-
vision?: boolean,
|
|
141
139
|
) => void;
|
|
142
140
|
}
|
|
143
141
|
|
|
@@ -145,7 +143,6 @@ type BackgroundStarter = (
|
|
|
145
143
|
agentName: string,
|
|
146
144
|
task: string,
|
|
147
145
|
cwd: string | undefined,
|
|
148
|
-
vision?: boolean,
|
|
149
146
|
isolation?: IsolationMode,
|
|
150
147
|
) => Promise<SingleResult>;
|
|
151
148
|
|
|
@@ -204,7 +201,6 @@ export function createBackgroundDispatcher(options: BackgroundDispatcherOptions)
|
|
|
204
201
|
agentName: string,
|
|
205
202
|
task: string,
|
|
206
203
|
cwd: string | undefined,
|
|
207
|
-
vision = false,
|
|
208
204
|
isolation: IsolationMode = "shared",
|
|
209
205
|
existingThread?: SubagentThread,
|
|
210
206
|
newObjectiveOnResume = false,
|
|
@@ -253,7 +249,7 @@ export function createBackgroundDispatcher(options: BackgroundDispatcherOptions)
|
|
|
253
249
|
}
|
|
254
250
|
}
|
|
255
251
|
const executionCwd = worktree?.cwd ?? originalCwd;
|
|
256
|
-
const resolvedRoute = resolveDispatchModelRoute(agent, runConfig, runCtx
|
|
252
|
+
const resolvedRoute = resolveDispatchModelRoute(agent, runConfig, runCtx);
|
|
257
253
|
// Isolation is a persistent system-level invariant, not a one-shot task
|
|
258
254
|
// prefix: queued retargets, live retargets, resumes, and main-model
|
|
259
255
|
// handoffs all keep the same worktree boundary.
|
|
@@ -319,7 +315,6 @@ export function createBackgroundDispatcher(options: BackgroundDispatcherOptions)
|
|
|
319
315
|
thread.task = task;
|
|
320
316
|
thread.cwd = originalCwd;
|
|
321
317
|
thread.executionCwd = executionCwd;
|
|
322
|
-
thread.vision = vision;
|
|
323
318
|
thread.thinkingLevel = thinkingLevel;
|
|
324
319
|
thread.isolation = isolation;
|
|
325
320
|
thread.worktree = worktree;
|
|
@@ -343,7 +338,6 @@ export function createBackgroundDispatcher(options: BackgroundDispatcherOptions)
|
|
|
343
338
|
task,
|
|
344
339
|
cwd: originalCwd,
|
|
345
340
|
executionCwd,
|
|
346
|
-
vision,
|
|
347
341
|
thinkingLevel,
|
|
348
342
|
isolation,
|
|
349
343
|
worktree,
|
|
@@ -622,7 +616,6 @@ export function createBackgroundDispatcher(options: BackgroundDispatcherOptions)
|
|
|
622
616
|
thread.agentName,
|
|
623
617
|
nextTask,
|
|
624
618
|
thread.cwd,
|
|
625
|
-
thread.vision,
|
|
626
619
|
thread.isolation,
|
|
627
620
|
thread,
|
|
628
621
|
objective !== undefined,
|
|
@@ -784,7 +777,6 @@ export function createBackgroundDispatcher(options: BackgroundDispatcherOptions)
|
|
|
784
777
|
thread.agentName,
|
|
785
778
|
childTask,
|
|
786
779
|
thread.cwd,
|
|
787
|
-
thread.vision,
|
|
788
780
|
thread.isolation,
|
|
789
781
|
undefined,
|
|
790
782
|
false,
|
|
@@ -926,7 +918,7 @@ export function createBackgroundDispatcher(options: BackgroundDispatcherOptions)
|
|
|
926
918
|
// the same row now represents the chain until it resolves.
|
|
927
919
|
monitor.setStatus(runId, "running");
|
|
928
920
|
monitor.setActivity(runId, "auto-fix chain running");
|
|
929
|
-
startFixLoop(result, `fix-${runId}`, runId, thread.executionCwd
|
|
921
|
+
startFixLoop(result, `fix-${runId}`, runId, thread.executionCwd);
|
|
930
922
|
return;
|
|
931
923
|
}
|
|
932
924
|
// Claim terminal settlement synchronously before the first slow await.
|