pi-ui-extend 0.1.38 → 0.1.41
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/dist/app/app.d.ts +0 -1
- package/dist/app/app.js +28 -21
- package/dist/app/constants.js +1 -1
- package/dist/app/input/input-action-controller.d.ts +1 -0
- package/dist/app/input/input-action-controller.js +3 -0
- package/dist/app/input/input-controller.d.ts +1 -0
- package/dist/app/input/input-controller.js +40 -12
- package/dist/app/model/model-usage-status.js +4 -2
- package/dist/app/process.js +11 -0
- package/dist/app/rendering/conversation-tool-renderer.js +4 -6
- package/dist/app/session/request-history.js +2 -0
- package/dist/app/session/session-event-controller.d.ts +13 -0
- package/dist/app/session/session-event-controller.js +27 -0
- package/dist/app/session/tabs-controller.d.ts +8 -0
- package/dist/app/session/tabs-controller.js +37 -6
- package/dist/app/workspace/workspace-actions-controller.d.ts +1 -0
- package/dist/app/workspace/workspace-actions-controller.js +2 -1
- package/dist/bundled-extensions/terminal-bell/index.js +55 -1
- package/dist/config.js +1 -1
- package/dist/default-pix-config.js +1 -1
- package/dist/markdown-format.js +14 -25
- package/dist/terminal-width.d.ts +14 -0
- package/dist/terminal-width.js +31 -2
- package/dist/theme.js +2 -2
- package/external/pi-tools-suite/README.md +34 -9
- package/external/pi-tools-suite/package.json +3 -3
- package/external/pi-tools-suite/src/async-subagents/async-subagents.sample.jsonc +35 -21
- package/external/pi-tools-suite/src/async-subagents/commands.ts +1 -1
- package/external/pi-tools-suite/src/async-subagents/core/agent-strategy.ts +2 -2
- package/external/pi-tools-suite/src/async-subagents/core/config.ts +70 -12
- package/external/pi-tools-suite/src/async-subagents/core/routing.ts +1 -1
- package/external/pi-tools-suite/src/async-subagents/core/spawn.ts +1 -1
- package/external/pi-tools-suite/src/async-subagents/core/types.ts +1 -1
- package/external/pi-tools-suite/src/async-subagents/index.ts +6 -6
- package/external/pi-tools-suite/src/async-subagents/lib.ts +1 -1
- package/external/pi-tools-suite/src/async-subagents/tools/spawn.ts +4 -2
- package/external/pi-tools-suite/src/async-subagents/tools/subagents.ts +2 -2
- package/external/pi-tools-suite/src/{glm-coding-discipline → coding-discipline}/index.ts +17 -8
- package/external/pi-tools-suite/src/config.ts +1 -1
- package/external/pi-tools-suite/src/dcp/auto-compress.ts +368 -0
- package/external/pi-tools-suite/src/dcp/compress-tool.ts +3 -0
- package/external/pi-tools-suite/src/dcp/config.ts +23 -0
- package/external/pi-tools-suite/src/dcp/index.ts +112 -7
- package/external/pi-tools-suite/src/dcp/prompts.ts +8 -0
- package/external/pi-tools-suite/src/dcp/state.ts +41 -0
- package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +30 -22
- package/external/pi-tools-suite/src/index.ts +2 -1
- package/external/pi-tools-suite/src/session-name/index.ts +37 -0
- package/external/pi-tools-suite/src/tool-descriptions.ts +16 -4
- package/package.json +4 -4
- package/skills/skill-creator/SKILL.md +36 -40
- package/skills/skill-creator/eval-viewer/viewer.html +2 -2
- package/skills/skill-creator/references/schemas.md +1 -1
- package/skills/skill-creator/scripts/__pycache__/__init__.cpython-314.pyc +0 -0
- package/skills/skill-creator/scripts/__pycache__/aggregate_benchmark.cpython-314.pyc +0 -0
- package/skills/skill-creator/scripts/__pycache__/generate_report.cpython-314.pyc +0 -0
- package/skills/skill-creator/scripts/__pycache__/improve_description.cpython-314.pyc +0 -0
- package/skills/skill-creator/scripts/__pycache__/package_skill.cpython-314.pyc +0 -0
- package/skills/skill-creator/scripts/__pycache__/run_eval.cpython-314.pyc +0 -0
- package/skills/skill-creator/scripts/__pycache__/run_loop.cpython-314.pyc +0 -0
- package/skills/skill-creator/scripts/__pycache__/utils.cpython-314.pyc +0 -0
- package/skills/skill-creator/scripts/generate_report.py +1 -1
- package/skills/skill-creator/scripts/improve_description.py +14 -24
- package/skills/skill-creator/scripts/run_eval.py +89 -82
package/dist/markdown-format.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { expandTabs, stringDisplayWidth } from "./terminal-width.js";
|
|
1
|
+
import { displayGraphemes, expandTabs, stringDisplayWidth } from "./terminal-width.js";
|
|
2
2
|
import { syntaxHighlightLanguageForMarkdownFence, } from "./syntax-highlight.js";
|
|
3
3
|
const MIN_TRAILING_WORD_WIDTH_TO_REBALANCE = 5;
|
|
4
4
|
export function formatMarkdownTables(text, maxWidth) {
|
|
@@ -211,20 +211,16 @@ function displayTokensWithRanges(text) {
|
|
|
211
211
|
let current = "";
|
|
212
212
|
let currentStart = 0;
|
|
213
213
|
let currentWhitespace;
|
|
214
|
-
for (
|
|
215
|
-
const codePoint = text.codePointAt(index) ?? 0;
|
|
216
|
-
const char = String.fromCodePoint(codePoint);
|
|
214
|
+
for (const { text: char, start } of displayGraphemes(text)) {
|
|
217
215
|
const whitespace = /\s/u.test(char);
|
|
218
216
|
if (current && currentWhitespace !== whitespace) {
|
|
219
|
-
tokens.push({ text: current, start: currentStart, end:
|
|
217
|
+
tokens.push({ text: current, start: currentStart, end: start, whitespace: currentWhitespace ?? false });
|
|
220
218
|
current = "";
|
|
221
|
-
currentStart = index;
|
|
222
219
|
}
|
|
223
220
|
if (!current)
|
|
224
|
-
currentStart =
|
|
221
|
+
currentStart = start;
|
|
225
222
|
current += char;
|
|
226
223
|
currentWhitespace = whitespace;
|
|
227
|
-
index += char.length;
|
|
228
224
|
}
|
|
229
225
|
if (current)
|
|
230
226
|
tokens.push({ text: current, start: currentStart, end: text.length, whitespace: currentWhitespace ?? false });
|
|
@@ -235,19 +231,16 @@ function wrapDisplayTokenByWidth(token, width) {
|
|
|
235
231
|
let chunkText = "";
|
|
236
232
|
let chunkStart = token.start;
|
|
237
233
|
let chunkWidth = 0;
|
|
238
|
-
for (
|
|
239
|
-
const
|
|
240
|
-
const char = String.fromCodePoint(codePoint);
|
|
241
|
-
const charWidth = stringDisplayWidth(char);
|
|
234
|
+
for (const { text: char, width: charWidth, start } of displayGraphemes(token.text)) {
|
|
235
|
+
const absoluteStart = token.start + start;
|
|
242
236
|
if (chunkText && chunkWidth + charWidth > width) {
|
|
243
|
-
chunks.push({ text: chunkText, start: chunkStart, end:
|
|
237
|
+
chunks.push({ text: chunkText, start: chunkStart, end: absoluteStart });
|
|
244
238
|
chunkText = "";
|
|
245
|
-
chunkStart =
|
|
239
|
+
chunkStart = absoluteStart;
|
|
246
240
|
chunkWidth = 0;
|
|
247
241
|
}
|
|
248
242
|
chunkText += char;
|
|
249
243
|
chunkWidth += charWidth;
|
|
250
|
-
index += char.length;
|
|
251
244
|
}
|
|
252
245
|
chunks.push({ text: chunkText, start: chunkStart, end: token.end });
|
|
253
246
|
return chunks;
|
|
@@ -506,20 +499,16 @@ function smartDisplayBreakIndex(text, width) {
|
|
|
506
499
|
let used = 0;
|
|
507
500
|
let fallbackIndex = 0;
|
|
508
501
|
let breakIndex = 0;
|
|
509
|
-
for (
|
|
510
|
-
const
|
|
511
|
-
const char = String.fromCodePoint(codePoint);
|
|
512
|
-
const nextIndex = index + char.length;
|
|
513
|
-
const nextUsed = used + stringDisplayWidth(char);
|
|
502
|
+
for (const { text: char, width: charWidth, start, end } of displayGraphemes(text)) {
|
|
503
|
+
const nextUsed = used + charWidth;
|
|
514
504
|
if (nextUsed > width)
|
|
515
505
|
break;
|
|
516
506
|
used = nextUsed;
|
|
517
|
-
fallbackIndex =
|
|
518
|
-
if (char === "/" &&
|
|
519
|
-
breakIndex =
|
|
507
|
+
fallbackIndex = end;
|
|
508
|
+
if (char === "/" && start > 0)
|
|
509
|
+
breakIndex = start;
|
|
520
510
|
else if (/[._:-]/u.test(char))
|
|
521
|
-
breakIndex =
|
|
522
|
-
index = nextIndex;
|
|
511
|
+
breakIndex = end;
|
|
523
512
|
}
|
|
524
513
|
return breakIndex > 0 ? breakIndex : fallbackIndex;
|
|
525
514
|
}
|
package/dist/terminal-width.d.ts
CHANGED
|
@@ -3,6 +3,20 @@ export declare function stringDisplayWidth(text: string): number;
|
|
|
3
3
|
export declare function sliceByDisplayWidth(text: string, width: number): string;
|
|
4
4
|
export declare function displayIndexForColumn(text: string, column: number): number;
|
|
5
5
|
export declare function sliceByDisplayColumns(text: string, startColumn: number, endColumn: number): string;
|
|
6
|
+
export type DisplayGrapheme = {
|
|
7
|
+
text: string;
|
|
8
|
+
width: number;
|
|
9
|
+
start: number;
|
|
10
|
+
end: number;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Grapheme clusters of `text` with their display width and absolute string
|
|
14
|
+
* indices. Iterating graphemes (instead of code points) is required for correct
|
|
15
|
+
* width accounting: multi-codepoint emoji such as `⚠️` (U+26A0 U+FE0F), keycaps,
|
|
16
|
+
* skin-tone modifiers and regional-indicator flags are one width-2 cluster even
|
|
17
|
+
* though several of their code points have zero width.
|
|
18
|
+
*/
|
|
19
|
+
export declare function displayGraphemes(text: string): DisplayGrapheme[];
|
|
6
20
|
export declare function padOrTrimDisplay(text: string, width: number): string;
|
|
7
21
|
export declare function wrapDisplayLine(text: string, width: number): string[];
|
|
8
22
|
export declare function wrapDisplayLineByWords(text: string, width: number): string[];
|
package/dist/terminal-width.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const TAB_WIDTH = 4;
|
|
2
2
|
const ANSI_RESET = "\x1b[0m";
|
|
3
3
|
const EMOJI_PRESENTATION_REGEX = /\p{Emoji_Presentation}/u;
|
|
4
|
-
const
|
|
4
|
+
const REGIONAL_INDICATOR_REGEX = /[\u{1F1E6}-\u{1F1FF}]/u;
|
|
5
5
|
const GRAPHEME_SEGMENTER = typeof Intl.Segmenter === "function" ? new Intl.Segmenter(undefined, { granularity: "grapheme" }) : undefined;
|
|
6
6
|
export function expandTabs(text, tabWidth = TAB_WIDTH) {
|
|
7
7
|
if (!text.includes("\t"))
|
|
@@ -86,6 +86,20 @@ export function sliceByDisplayColumns(text, startColumn, endColumn) {
|
|
|
86
86
|
const endIndex = Math.max(startIndex, displayIndexForColumn(text, endColumn));
|
|
87
87
|
return text.slice(startIndex, endIndex);
|
|
88
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* Grapheme clusters of `text` with their display width and absolute string
|
|
91
|
+
* indices. Iterating graphemes (instead of code points) is required for correct
|
|
92
|
+
* width accounting: multi-codepoint emoji such as `⚠️` (U+26A0 U+FE0F), keycaps,
|
|
93
|
+
* skin-tone modifiers and regional-indicator flags are one width-2 cluster even
|
|
94
|
+
* though several of their code points have zero width.
|
|
95
|
+
*/
|
|
96
|
+
export function displayGraphemes(text) {
|
|
97
|
+
const graphemes = [];
|
|
98
|
+
for (const cluster of indexedDisplayClusters(text)) {
|
|
99
|
+
graphemes.push({ text: cluster.text, width: cluster.width, start: cluster.start, end: cluster.end });
|
|
100
|
+
}
|
|
101
|
+
return graphemes;
|
|
102
|
+
}
|
|
89
103
|
export function padOrTrimDisplay(text, width) {
|
|
90
104
|
const safeWidth = Math.max(0, width);
|
|
91
105
|
if (isPrintableAscii(text)) {
|
|
@@ -280,7 +294,22 @@ function graphemeDisplayWidth(text) {
|
|
|
280
294
|
return width;
|
|
281
295
|
}
|
|
282
296
|
function isEmojiGrapheme(text) {
|
|
283
|
-
|
|
297
|
+
// Default-presentation emoji (⛔ ✅ 🚀 ❌), supplementary pictographs, and
|
|
298
|
+
// regional-indicator flags render two cells wide in conforming terminals
|
|
299
|
+
// including iTerm2 and Zed, so they are measured at width 2.
|
|
300
|
+
if (EMOJI_PRESENTATION_REGEX.test(text))
|
|
301
|
+
return true;
|
|
302
|
+
// Keycap sequences (base + U+FE0F + U+20E3, e.g. 1️⃣) and regional-indicator
|
|
303
|
+
// pairs (🇷🇺) also occupy two cells.
|
|
304
|
+
if (text.includes("\u20e3"))
|
|
305
|
+
return true;
|
|
306
|
+
if (REGIONAL_INDICATOR_REGEX.test(text) && /[\u{1F1E6}-\u{1F1FF}]{2}/u.test(text))
|
|
307
|
+
return true;
|
|
308
|
+
// Symbols promoted to an emoji glyph only by a variation selector (⚠️ ✔️ ©️
|
|
309
|
+
// ☀️) keep their base width of 1. Their base code point is East-Asian-Width
|
|
310
|
+
// Ambiguous, and iTerm2/Zed/wcwidth render them one cell wide; counting them
|
|
311
|
+
// as 2 would misalign table columns and shorten rendered rows.
|
|
312
|
+
return false;
|
|
284
313
|
}
|
|
285
314
|
function codePointLength(codePoint) {
|
|
286
315
|
return codePoint > 0xffff ? 2 : 1;
|
package/dist/theme.js
CHANGED
|
@@ -9,7 +9,7 @@ export const THEMES = {
|
|
|
9
9
|
muted: "#7d8590",
|
|
10
10
|
headerForeground: "#c9d1d9",
|
|
11
11
|
headerBackground: "#161b22",
|
|
12
|
-
statusForeground: "#
|
|
12
|
+
statusForeground: "#9ba5af",
|
|
13
13
|
statusBackground: "#0f1520",
|
|
14
14
|
inputForeground: "#f0f6fc",
|
|
15
15
|
inputBackground: "#090d13",
|
|
@@ -64,7 +64,7 @@ export const THEMES = {
|
|
|
64
64
|
muted: "#64748b",
|
|
65
65
|
headerForeground: "#0f172a",
|
|
66
66
|
headerBackground: "#e2e8f0",
|
|
67
|
-
statusForeground: "#
|
|
67
|
+
statusForeground: "#566578",
|
|
68
68
|
statusBackground: "#edf0f4",
|
|
69
69
|
inputForeground: "#0f172a",
|
|
70
70
|
inputBackground: "#f8fafc",
|
|
@@ -4,11 +4,12 @@ Local all-in-one Pi extension package.
|
|
|
4
4
|
|
|
5
5
|
This package keeps shared Pi tools as ordinary source folders under `src/` and registers them through one entrypoint.
|
|
6
6
|
|
|
7
|
-
- `src/
|
|
7
|
+
- `src/coding-discipline` — injects a deduplicated silent-mode and quality-discipline block at the very top of the main-session per-turn system prompt for all main-session models immediately before the LLM request; disabled for async sub-agents
|
|
8
8
|
- `src/ast-grep` — `ast_grep` / `ast_apply`
|
|
9
|
-
- `src/async-subagents` — `subagents` tool and sub-agent slash commands, including oh-my-openagent-style `/ultrawork` (`/ulw`) and `/hyperplan` orchestration prompts, plus config-defined sub-agent model/thinking/args presets selected via `/subagent-preset` from `asyncSubagents` in `~/.config/pi/pi-tools-suite.jsonc`; includes the `frontend` profile for Gemini-friendly UI/UX and visual frontend work
|
|
9
|
+
- `src/async-subagents` — `subagents` tool and sub-agent slash commands, including oh-my-openagent-style `/ultrawork` (`/ulw`) and `/hyperplan` orchestration prompts, plus config-defined sub-agent model/thinking/args presets selected via `/subagent-preset` from `asyncSubagents` in `~/.config/pi/pi-tools-suite.jsonc`; includes the `frontend` profile for Gemini-friendly UI/UX and visual frontend work and the `oracle` profile for cross-provider second opinions; enforces a 30-minute per-agent execution timeout, project-wide `maxConcurrent` queueing, optional retry/backoff, and `result.json` structured metadata/chaining fields next to raw `result.md`; stores project-local run files and a registry under `.pi/subagents/` so result/status collection can recover after compaction or reload while the main session remains alive
|
|
10
10
|
- `src/lsp` — shared LSP diagnostics hook/library that enriches mutating tool results with diagnostics and shuts down language servers on session shutdown
|
|
11
11
|
- `src/comment-checker` — AI-slop comment guard that listens to the `tool_result` event for `write` / `edit` / `apply_patch` mutations, extracts net-new code comment lines, classifies them (filler phrasing, restating code, decorative separators, generic paraphrasing, or — under aggressive strictness — any non-valuable comment), and appends a short nudge to the tool result so the agent removes unnecessary comments on its next turn; TODO/FIXME, license headers, docstrings, pragmas, linter directives, shebangs, and decorators are never flagged; language-agnostic across `//` / `/* */` / `#` / `--` / `<!-- -->` / triple-quote comment styles; per-session deduplication (at most one nudge per 30 s) prevents fix/remark loops; configured via the `commentChecker` section (`enabled`, `strictness`: `conservative` | `balanced` | `aggressive`, default `balanced`) or `PI_COMMENT_CHECKER_ENABLED` / `PI_COMMENT_CHECKER_STRICTNESS`
|
|
12
|
+
- `src/session-name` — `session_name` tool for reading or setting the current session title directly from tool calls, without relying on slash-command parsing
|
|
12
13
|
- `src/repo-discovery` — `/idx-init`, `/idx-update`, and indexed-only `repo_architecture` / `repo_structure` / `repo_ast` / `repo_search` / `repo_explain` / `repo_deps`; tools register only when the launch project has `.indexer-cli`
|
|
13
14
|
- `src/antigravity-auth` — `antigravity` custom provider with Google Antigravity OAuth login, startup account list, auth.json-only runtime account loading, `/antigravity-add-account` OAuth append into rotation, `/antigravity-account` status display, account rotation/failover, Antigravity plus Gemini CLI model registration, and streaming through the Cloud Code Assist unified gateway
|
|
14
15
|
- `src/todo` — `todo` tool, `/todos`, `/todos-persist`, and `/todos-scope`; supports parent/subtask hierarchy, blockers, ready-task filtering, deferred out-of-scope items, batch operations, JSON/Markdown import/export, automatic clearing when all visible todos are completed, and optional project persistence via `/todos persist on` or `/todos-persist on`; localization/i18n has been removed
|
|
@@ -20,7 +21,7 @@ This package keeps shared Pi tools as ordinary source folders under `src/` and r
|
|
|
20
21
|
|
|
21
22
|
`index.ts` is intentionally only a thin auto-discovery shim that re-exports `src/index.ts`. There is no `pi.extensions` manifest here, so local Pi auto-discovery loads the suite once via `~/.pi/agent/extensions/pi-tools-suite/index.ts` and does not double-register tools.
|
|
22
23
|
|
|
23
|
-
Registration order is preserved in `src/index.ts`:
|
|
24
|
+
Registration order is preserved in `src/index.ts`: coding-discipline, ast-grep, async-subagents, lsp, comment-checker, session-name, repo-discovery command/tool gate, antigravity-auth provider, todo, model-tools, usage, web-search, dcp, then prompt-commands. Tool metadata and active model-specific tool sets have two modes: standard and repo-aware. When `.indexer-cli` enables `repo_*`, those tools stay active ahead of overlapping lower-level aliases so the indexed discovery surface has priority.
|
|
24
25
|
|
|
25
26
|
## Disabling modules
|
|
26
27
|
|
|
@@ -68,13 +69,14 @@ DCP settings are stored only under `dcp` in the user shared config file `~/.conf
|
|
|
68
69
|
"maxContextLimit": 160000,
|
|
69
70
|
"nudgeFrequency": 1,
|
|
70
71
|
"iterationNudgeThreshold": 6,
|
|
72
|
+
"nudgeForce": "strong",
|
|
71
73
|
"protectedTools": ["compress", "write", "edit", "subagents"]
|
|
72
74
|
},
|
|
73
75
|
"modelOverrides": {
|
|
74
|
-
"openai-codex/gpt-5
|
|
76
|
+
"openai-codex/gpt-5*": {
|
|
75
77
|
"compress": {
|
|
76
|
-
"minContextPercent": "
|
|
77
|
-
"maxContextPercent": "
|
|
78
|
+
"minContextPercent": "26%",
|
|
79
|
+
"maxContextPercent": "46%"
|
|
78
80
|
}
|
|
79
81
|
},
|
|
80
82
|
"openai-codex/gpt-5.4-mini": {
|
|
@@ -208,11 +210,11 @@ Project-local overrides can be added in `.pi/pi-tools-suite.jsonc`; pi-tools-sui
|
|
|
208
210
|
|
|
209
211
|
Sub-agent model routing normally follows task overrides, subagent type config, then `ASYNC_SUBAGENTS_MODEL` / `PI_SUBAGENTS_MODEL` fallbacks. Set `ASYNC_SUBAGENTS_FORCE_CURRENT_MODEL=1` (or `PI_SUBAGENTS_FORCE_CURRENT_MODEL=1`) to ignore task/config/env model choices and launch every sub-agent with the current parent session model. When this flag is enabled, any `--model` entries in sub-agent extra args are stripped so they cannot override the current model.
|
|
210
212
|
|
|
211
|
-
For an oh-my-openagent-style workflow, run `/ultrawork` or `/ulw` to ask the parent agent to split broad work into configured async-subagents roles (`quick`, `scan`, `research`, `docs`, `frontend`, `implement`, `tests`, `review`, `deep`, `
|
|
213
|
+
For an oh-my-openagent-style workflow, run `/ultrawork` or `/ulw` to ask the parent agent to split broad work into configured async-subagents roles (`quick`, `scan`, `research`, `docs`, `frontend`, `implement`, `tests`, `review`, `deep`, `oracle`). Set `ULTRAWORK=1` before launching Pi to apply that compact routing prompt to normal non-slash user inputs automatically. Set `ULTRAWORK_AUTO=1` to ask the lightweight router model to classify only the first normal user input on non-GPT parent models: clear broad/parallel work is transformed into ultrawork, vague potentially-complex work gets a soft delegation hint, and narrow work is left unchanged. GPT-like parent models skip only this automatic transform; they can still use `/ultrawork` and `subagents` normally. `frontend` is for UI/UX, styling, layout, responsive behavior, and visual component polish; `review` covers security/performance/audit tracks; `implement` covers refactors; `deep` covers debugging/root-cause; `oracle` is for sparse cross-provider second opinions on high-stakes uncertainty. Run `/hyperplan` to pressure-test a plan before implementation.
|
|
212
214
|
|
|
213
215
|
Async-subagents also injects a lightweight oh-my-openagent-style system-prompt strategy by model: non-GPT parents get `parallel-first`, an orchestration-first hint that favors ultrawork/subagents for broad work, while GPT-like parents get `deep-work`, a direct deep-worker hint that uses subagents only when clearly useful. Explicit custom system prompts (`--system-prompt`, `SYSTEM.md`, custom templates) are respected and skip this injection by default. Disable it with `PI_AGENT_STRATEGY=off`; force a strategy with `PI_AGENT_STRATEGY=parallel-first` or `PI_AGENT_STRATEGY=deep-work`; set `PI_AGENT_STRATEGY_WITH_CUSTOM_PROMPT=1` to append it even when a custom prompt is present.
|
|
214
216
|
|
|
215
|
-
|
|
217
|
+
For blind-model screenshot/image inspection, use the main-session `coding-discipline` lookup tool. Async-subagents still supports `imagePaths` on tasks when a broader delegated track genuinely needs images, but it no longer ships a dedicated `vision` role. Dynamic provider capabilities can be missing or stale after switching models, so blind parent models can still be configured explicitly with case-insensitive `*` masks under `asyncSubagents.vision.blindModelPatterns` in `~/.config/pi/pi-tools-suite.jsonc`; this keeps guidance honest, not a sub-agent role.
|
|
216
218
|
|
|
217
219
|
When a task omits `subagentType`, async-subagents asks a lightweight router model to choose one configured type for each task from the task text/scope and the `types.<name>.description` metadata. Explicit task `subagentType` still wins. Keep type descriptions short, literal, and distinct because they are inserted into the router prompt for a small model. Router settings live under `asyncSubagents.routing` (`enabled`, `model`, `maxTaskChars`, `maxTokens`, `maxRetries`, `timeoutMs`, `debug`); the default router model is `zai/glm-4.5-air`. If the router is disabled, unavailable, aborted, or returns invalid JSON, omitted types fall back to `defaultType`.
|
|
218
220
|
|
|
@@ -231,7 +233,7 @@ Example shared async-subagents config section:
|
|
|
231
233
|
},
|
|
232
234
|
"presets": {
|
|
233
235
|
"cheap": {
|
|
234
|
-
"description": "Use GLM for text/code roles
|
|
236
|
+
"description": "Use GLM for text/code roles.",
|
|
235
237
|
"types": {
|
|
236
238
|
"quick": { "model": "zai/glm-5.2", "thinking": "off" },
|
|
237
239
|
"frontend": { "model": "antigravity/gemini-3-flash-preview", "fallbackModels": ["zai/glm-5.2"], "thinking": "medium" },
|
|
@@ -253,6 +255,29 @@ Example shared async-subagents config section:
|
|
|
253
255
|
}
|
|
254
256
|
```
|
|
255
257
|
|
|
258
|
+
### Parent-model-aware model selection (`modelByParent`)
|
|
259
|
+
|
|
260
|
+
Any type profile can carry `modelByParent`: a map from glob model refs (matched against the **current parent model**, e.g. `"zai/*"`) to a model for that role. The first matching key wins. Values may be a model string or `{ "model": "...", "fallbackModels": [...] }`. It is resolved after an explicit task `model` / `forcedModel`, but **before** the preset/static profile `model`, so a role can always pick a model based on who the parent is — independent of the active preset.
|
|
261
|
+
|
|
262
|
+
The canonical use case is an **`oracle`** role that consults a flagship model from a *different* provider than the parent for a second opinion:
|
|
263
|
+
|
|
264
|
+
```jsonc
|
|
265
|
+
"oracle": {
|
|
266
|
+
"description": "Cross-provider second opinion: consult a flagship from a different provider than the parent to pressure-test a hard decision. Read-only; advise, do not edit.",
|
|
267
|
+
"model": "openai-codex/gpt-5.5",
|
|
268
|
+
"fallbackModels": ["zai/glm-5.2"],
|
|
269
|
+
"thinking": "xhigh",
|
|
270
|
+
"modelByParent": {
|
|
271
|
+
"zai/*": { "model": "openai-codex/gpt-5.5", "fallbackModels": ["zai/glm-5.2"] },
|
|
272
|
+
"openai-codex/*": "zai/glm-5.2",
|
|
273
|
+
"antigravity/*": { "model": "zai/glm-5.2", "fallbackModels": ["openai-codex/gpt-5.5"] },
|
|
274
|
+
"anthropic/*": { "model": "openai-codex/gpt-5.5", "fallbackModels": ["zai/glm-5.2"] }
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
With this config a GLM parent (`zai/*`) spawns the oracle on `gpt-5.5`, a GPT parent (`openai-codex/*`) spawns it on `glm-5.2`, and so on — automatically, at spawn time, with no `task.model` needed. The parent model ref is read from the spawn context (`ctx.model`) and passed into resolution. Pattern matching is case-insensitive `*` glob (same engine as `vision.blindModelPatterns`). When no key matches (or no parent model is known), the role falls back to its static `model` + `fallbackModels`. An explicit `task.model` or `ASYNC_SUBAGENTS_FORCE_CURRENT_MODEL=1` still overrides the match.
|
|
280
|
+
|
|
256
281
|
Sub-agents run with `--no-session` by default to avoid writing duplicate Pi session JSONL files for fire-and-forget background work. Set `ASYNC_SUBAGENTS_ENABLE_SESSIONS=1` to restore persisted per-agent sessions under each agent's `sessions/` directory; this also registers the session-navigation slash commands (`/sub-open`, `/sub-back`, `/sub-where`) needed for switching and deeper post-mortem navigation.
|
|
257
282
|
|
|
258
283
|
Sub-agent runs are stored in the current project's `.pi/subagents/` directory while the main session is alive. Each spawn updates `.pi/subagents/registry.json` with the latest run and `agentId -> runDir` mappings. Because of that, `subagents({ action: "status" })`, `wait`, and `stop` can omit `runDir` to target the latest run, and `subagents({ action: "result", agentId: "..." })` can resolve the run from the registry even if the exact `runDir` was lost during compaction. Result reads always return a summary-first response with artifact paths; raw `result.md` and `stderr.log` are not inlined, which avoids IPC/socket buffer overflows. Include `runDir` when you need an older or non-latest run, and use `cleanup` with `delete=true` to remove collected old runs before the session ends. On normal main-session shutdown, Pi stops sub-agents and removes the project-local run files/registry to avoid leaving `.pi/subagents/` clutter behind; reload and fork shutdowns preserve them so in-process recovery still works.
|
|
@@ -38,9 +38,9 @@
|
|
|
38
38
|
"vscode-languageserver-protocol": "^3.17.5"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
|
-
"@earendil-works/pi-ai": "0.79.
|
|
42
|
-
"@earendil-works/pi-coding-agent": "0.79.
|
|
43
|
-
"@earendil-works/pi-tui": "0.79.
|
|
41
|
+
"@earendil-works/pi-ai": "0.79.7",
|
|
42
|
+
"@earendil-works/pi-coding-agent": "0.79.7",
|
|
43
|
+
"@earendil-works/pi-tui": "0.79.7",
|
|
44
44
|
"typebox": "*"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
@@ -28,10 +28,11 @@
|
|
|
28
28
|
// for short synthetic tests.
|
|
29
29
|
// "timeoutMs": 1800000,
|
|
30
30
|
|
|
31
|
-
// Parent-model
|
|
32
|
-
// after switching models, so async-subagents treats these glob-like
|
|
33
|
-
// as text-only
|
|
34
|
-
//
|
|
31
|
+
// Parent-model image-capability overrides. Pi provider metadata can be missing
|
|
32
|
+
// or stale after switching models, so async-subagents treats these glob-like
|
|
33
|
+
// model refs as text-only when dynamic detection is inconclusive. Blind-model
|
|
34
|
+
// screenshot inspection should use lookup; this only keeps guidance honest.
|
|
35
|
+
// Matching is case-insensitive and supports `*` wildcards.
|
|
35
36
|
"vision": {
|
|
36
37
|
"blindModelPatterns": ["zai/glm*", "glm*", "*/glm*"]
|
|
37
38
|
},
|
|
@@ -47,6 +48,19 @@
|
|
|
47
48
|
// ],
|
|
48
49
|
// "promptOverride": "You are a specialized {subagentType} sub-agent.\nParent: {parentObjective}\nTask: {task}\nScope: {scope}",
|
|
49
50
|
|
|
51
|
+
// Parent-model-aware model selection (optional, any type profile).
|
|
52
|
+
// modelByParent maps glob model refs (matched against the current parent
|
|
53
|
+
// model, e.g. "zai/*") to a model for this role. The first matching key wins.
|
|
54
|
+
// Values may be a model string or { model, fallbackModels? }. It is resolved
|
|
55
|
+
// after an explicit task.model / forcedModel, but before the preset/static
|
|
56
|
+
// profile model, so a role like an "oracle" can always pick a flagship from a
|
|
57
|
+
// DIFFERENT provider than the parent. Example:
|
|
58
|
+
// "modelByParent": {
|
|
59
|
+
// "zai/*": { "model": "openai-codex/gpt-5.5", "fallbackModels": ["zai/glm-5.2"] },
|
|
60
|
+
// "openai-codex/*": "zai/glm-5.2",
|
|
61
|
+
// "antigravity/*": { "model": "zai/glm-5.2", "fallbackModels": ["openai-codex/gpt-5.5"] }
|
|
62
|
+
// }
|
|
63
|
+
|
|
50
64
|
// Role auto-selection when subagentType is omitted is handled by a small LLM
|
|
51
65
|
// router request. The router receives each task plus the configured type
|
|
52
66
|
// descriptions, then returns one allowed subagentType per task. Explicit
|
|
@@ -95,7 +109,7 @@
|
|
|
95
109
|
|
|
96
110
|
"presets": {
|
|
97
111
|
"cheap": {
|
|
98
|
-
"description": "Use cheap GLM/Gemini Flash models for text/code roles
|
|
112
|
+
"description": "Use cheap GLM/Gemini Flash models for text/code roles.",
|
|
99
113
|
"types": {
|
|
100
114
|
"quick": { "model": "zai/glm-4.5-air", "thinking": "off" },
|
|
101
115
|
"scan": { "model": "zai/glm-4.5-air", "thinking": "off" },
|
|
@@ -105,8 +119,7 @@
|
|
|
105
119
|
"tests": { "model": "zai/glm-5-turbo", "thinking": "medium" },
|
|
106
120
|
"review": { "model": "zai/glm-5.2", "thinking": "high" },
|
|
107
121
|
"implement": { "model": "zai/glm-5.2", "thinking": "high" },
|
|
108
|
-
"deep": { "model": "zai/glm-5.2", "thinking": "high" }
|
|
109
|
-
"vision": { "model": "openai-codex/gpt-5.4-mini", "thinking": "off" }
|
|
122
|
+
"deep": { "model": "zai/glm-5.2", "thinking": "high" }
|
|
110
123
|
}
|
|
111
124
|
},
|
|
112
125
|
|
|
@@ -121,8 +134,7 @@
|
|
|
121
134
|
"tests": { "model": "openai-codex/gpt-5.4-mini", "fallbackModels": ["zai/glm-5-turbo"], "thinking": "medium" },
|
|
122
135
|
"review": { "model": "openai-codex/gpt-5.5", "fallbackModels": ["zai/glm-5.2"], "thinking": "high" },
|
|
123
136
|
"implement": { "model": "openai-codex/gpt-5.5", "fallbackModels": ["zai/glm-5.2"], "thinking": "high" },
|
|
124
|
-
"deep": { "model": "openai-codex/gpt-5.5", "fallbackModels": ["zai/glm-5.2"], "thinking": "high" }
|
|
125
|
-
"vision": { "model": "openai-codex/gpt-5.4-mini", "thinking": "off" }
|
|
137
|
+
"deep": { "model": "openai-codex/gpt-5.5", "fallbackModels": ["zai/glm-5.2"], "thinking": "high" }
|
|
126
138
|
}
|
|
127
139
|
},
|
|
128
140
|
|
|
@@ -137,8 +149,7 @@
|
|
|
137
149
|
"tests": { "model": "antigravity/antigravity-claude-sonnet-4-6", "fallbackModels": ["openai-codex/gpt-5.4-mini", "zai/glm-5-turbo"], "thinking": "high" },
|
|
138
150
|
"review": { "model": "antigravity/antigravity-claude-sonnet-4-6", "fallbackModels": ["openai-codex/gpt-5.5", "zai/glm-5.2"], "thinking": "high" },
|
|
139
151
|
"implement": { "model": "openai-codex/gpt-5.5", "fallbackModels": ["zai/glm-5.2"], "thinking": "high" },
|
|
140
|
-
"deep": { "model": "antigravity/antigravity-claude-opus-4-6-thinking", "fallbackModels": ["openai-codex/gpt-5.5", "zai/glm-5.2"], "thinking": "high" }
|
|
141
|
-
"vision": { "model": "openai-codex/gpt-5.4-mini", "thinking": "off" }
|
|
152
|
+
"deep": { "model": "antigravity/antigravity-claude-opus-4-6-thinking", "fallbackModels": ["openai-codex/gpt-5.5", "zai/glm-5.2"], "thinking": "high" }
|
|
142
153
|
}
|
|
143
154
|
}
|
|
144
155
|
},
|
|
@@ -212,16 +223,19 @@
|
|
|
212
223
|
"thinking": "high"
|
|
213
224
|
},
|
|
214
225
|
|
|
215
|
-
"
|
|
216
|
-
"description": "Use
|
|
217
|
-
"model": "openai-codex/gpt-5.
|
|
218
|
-
"
|
|
219
|
-
"
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
"
|
|
223
|
-
"
|
|
224
|
-
|
|
226
|
+
"oracle": {
|
|
227
|
+
"description": "Oracle: cross-provider flagship second opinion for hard or high-stakes uncertainty. Use sparingly to pressure-test architecture, plans, root-cause hypotheses, risk/security calls, or final recommendations when independent disagreement is valuable. Read-only; advise, do not edit.",
|
|
228
|
+
"model": "openai-codex/gpt-5.5",
|
|
229
|
+
"fallbackModels": ["zai/glm-5.2"],
|
|
230
|
+
"thinking": "xhigh",
|
|
231
|
+
"tools": ["read", "grep", "bash"],
|
|
232
|
+
"modelByParent": {
|
|
233
|
+
"zai/*": { "model": "openai-codex/gpt-5.5", "fallbackModels": ["zai/glm-5.2"] },
|
|
234
|
+
"openai-codex/*": { "model": "zai/glm-5.2", "fallbackModels": ["openai-codex/gpt-5.5"] },
|
|
235
|
+
"antigravity/*": { "model": "zai/glm-5.2", "fallbackModels": ["openai-codex/gpt-5.5"] },
|
|
236
|
+
"anthropic/*": { "model": "openai-codex/gpt-5.5", "fallbackModels": ["zai/glm-5.2"] }
|
|
237
|
+
},
|
|
238
|
+
"promptAppend": "You are an oracle: a flagship model from a different provider giving a second opinion to the parent agent. Give a concise, decisive recommendation with key tradeoffs and risks. Disagree when warranted; do not rubber-stamp. Do not edit unless explicitly asked."
|
|
225
239
|
}
|
|
226
240
|
}
|
|
227
241
|
}
|
|
@@ -53,7 +53,7 @@ const COPY_SAMPLE_CONFIG_LABEL = "Copy sample asyncSubagents config";
|
|
|
53
53
|
|
|
54
54
|
export const ULTRAWORK_PROMPT = `Run ultrawork mode for the current objective.
|
|
55
55
|
|
|
56
|
-
Use subagents when independent parallel tracks help. Pick subagentType from configured roles: quick, scan, research, docs, frontend, implement, tests, review, deep,
|
|
56
|
+
Use subagents when independent parallel tracks help. Pick subagentType from configured roles: quick, scan, research, docs, frontend, implement, tests, review, deep, oracle. Use review for security/performance/audit tracks, implement for refactors, deep for debugging/root-cause. Use frontend for UI/UX and visual frontend implementation; use oracle sparingly for cross-provider second opinions on high-stakes uncertainty.
|
|
57
57
|
|
|
58
58
|
Keep parent context lean: spawn for broad parallel work, read results only when needed, and finish unless genuinely blocked.`;
|
|
59
59
|
|
|
@@ -14,7 +14,7 @@ const TRUE_ENV_PATTERN = /^(1|true|on|yes|auto)$/i;
|
|
|
14
14
|
const PARALLEL_FIRST_STRATEGY_PROMPT = `<agent_strategy name="parallel-first">
|
|
15
15
|
This is an orchestration hint for Pi, not a replacement for the user's instructions.
|
|
16
16
|
|
|
17
|
-
Default posture: orchestration-first for non-GPT models. For broad, multi-file, ambiguous, review/audit, frontend, test-strategy, architecture, or root-cause work, prefer ultrawork mode: split independent tracks and spawn focused async subagents with the configured roles. Keep the parent context lean, collect compact results only when needed, synthesize the findings, then verify before finishing.
|
|
17
|
+
Default posture: orchestration-first for non-GPT models. For broad, multi-file, ambiguous, review/audit, frontend, test-strategy, architecture, or root-cause work, prefer ultrawork mode: split independent tracks and spawn focused async subagents with the configured roles. For high-stakes uncertainty, add one oracle track for a cross-provider second opinion. Keep the parent context lean, collect compact results only when needed, synthesize the findings, then verify before finishing.
|
|
18
18
|
|
|
19
19
|
Before DCP/compress while work is unfinished, keep one in_progress todo with objective + next step; compression summaries must preserve Active objective and Next step.
|
|
20
20
|
|
|
@@ -28,7 +28,7 @@ Default posture: autonomous deep worker. Build context directly, make concrete p
|
|
|
28
28
|
|
|
29
29
|
Before DCP/compress while work is unfinished, keep one in_progress todo with objective + next step; compression summaries must preserve Active objective and Next step.
|
|
30
30
|
|
|
31
|
-
For broad work, keep delegation explicit and bounded: spawn focused review/research/tests/frontend/deep tracks,
|
|
31
|
+
For broad work, keep delegation explicit and bounded: spawn focused review/research/tests/frontend/deep tracks, plus one oracle track only for high-stakes uncertainty or final plan checks. Read compact results, make the final decisions in the parent session, and report only what matters.
|
|
32
32
|
</agent_strategy>`;
|
|
33
33
|
|
|
34
34
|
export function agentStrategyPrompt(options: AgentStrategyOptions = {}): string | undefined {
|
|
@@ -6,11 +6,25 @@ import { applyEdits, modify, parse as parseJsonc } from "jsonc-parser";
|
|
|
6
6
|
import { ensurePiToolsSuiteUserConfig, getPiToolsSuiteUserConfigPath } from "../../config.js";
|
|
7
7
|
import type { AgentTask, RetryConfig } from "./types.js";
|
|
8
8
|
|
|
9
|
+
export interface ModelByParentEntry {
|
|
10
|
+
/** Model ref to use when the parent model matches the entry's pattern. */
|
|
11
|
+
model: string;
|
|
12
|
+
/** Ordered fallbacks used when this entry's model hits quota/rate limits; replaces the normal fallback chain. */
|
|
13
|
+
fallbackModels?: string[];
|
|
14
|
+
}
|
|
15
|
+
|
|
9
16
|
export interface SubagentTypeConfig {
|
|
10
17
|
description?: string;
|
|
11
18
|
model?: string;
|
|
12
19
|
/** Ordered model fallbacks used when the selected model hits quota/rate limits. */
|
|
13
20
|
fallbackModels?: string[];
|
|
21
|
+
/**
|
|
22
|
+
* Parent-model-aware model selection. Keys are glob model refs (e.g. "zai/*")
|
|
23
|
+
* matched against the current parent model; the first matching key wins.
|
|
24
|
+
* Values may be a model ref string or { model, fallbackModels? }.
|
|
25
|
+
* Resolved after explicit task.model / forcedModel, before preset/static model.
|
|
26
|
+
*/
|
|
27
|
+
modelByParent?: Record<string, ModelByParentEntry>;
|
|
14
28
|
thinking?: string;
|
|
15
29
|
tools?: string[];
|
|
16
30
|
extraArgs?: string[];
|
|
@@ -127,6 +141,8 @@ export interface ResolveAgentTaskOptions {
|
|
|
127
141
|
extraArgs?: string[];
|
|
128
142
|
/** Force every sub-agent to use this model, ignoring task/profile/env model selection. */
|
|
129
143
|
forcedModel?: string;
|
|
144
|
+
/** Current parent model ref (provider/model). Enables type.modelByParent matching. */
|
|
145
|
+
parentModel?: string;
|
|
130
146
|
/** Force a wall-clock timeout for every sub-agent spawned by this call. */
|
|
131
147
|
timeoutMs?: number;
|
|
132
148
|
}
|
|
@@ -201,16 +217,19 @@ const BUILTIN_CONFIG: SubagentConfig = {
|
|
|
201
217
|
description: "Use for broad hard reasoning: architecture, root-cause analysis, cross-module impact, complex debugging or tradeoffs.",
|
|
202
218
|
thinking: "high",
|
|
203
219
|
},
|
|
204
|
-
|
|
205
|
-
description: "Use
|
|
206
|
-
model: "openai-codex/gpt-5.
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
"
|
|
210
|
-
"
|
|
211
|
-
"
|
|
212
|
-
"
|
|
213
|
-
|
|
220
|
+
oracle: {
|
|
221
|
+
description: "Oracle: cross-provider flagship second opinion for hard or high-stakes uncertainty. Use sparingly to pressure-test architecture, plans, root-cause hypotheses, risk/security calls, or final recommendations when independent disagreement is valuable. Read-only; advise, do not edit.",
|
|
222
|
+
model: "openai-codex/gpt-5.5",
|
|
223
|
+
fallbackModels: ["zai/glm-5.2"],
|
|
224
|
+
modelByParent: {
|
|
225
|
+
"zai/*": { model: "openai-codex/gpt-5.5", fallbackModels: ["zai/glm-5.2"] },
|
|
226
|
+
"openai-codex/*": { model: "zai/glm-5.2", fallbackModels: ["openai-codex/gpt-5.5"] },
|
|
227
|
+
"antigravity/*": { model: "zai/glm-5.2", fallbackModels: ["openai-codex/gpt-5.5"] },
|
|
228
|
+
"anthropic/*": { model: "openai-codex/gpt-5.5", fallbackModels: ["zai/glm-5.2"] },
|
|
229
|
+
},
|
|
230
|
+
thinking: "xhigh",
|
|
231
|
+
tools: ["read", "grep", "bash"],
|
|
232
|
+
promptAppend: "You are an oracle: a flagship model from a different provider giving a second opinion to the parent agent. Give a concise, decisive recommendation with key tradeoffs and risks. Disagree when warranted; do not rubber-stamp. Do not edit unless explicitly asked.",
|
|
214
233
|
},
|
|
215
234
|
},
|
|
216
235
|
};
|
|
@@ -325,14 +344,19 @@ export function resolveAgentTaskConfig(
|
|
|
325
344
|
const promptAppend = joinTextBlocks(profile?.promptAppend, task.promptAppend);
|
|
326
345
|
const forcedModel = trimString(globalOptions.forcedModel);
|
|
327
346
|
const taskModel = trimString(task.model);
|
|
347
|
+
const parentMatch = resolveModelByParent(profile, trimString(globalOptions.parentModel));
|
|
348
|
+
const parentMatchModel = trimString(parentMatch?.model);
|
|
328
349
|
const presetTypeModel = trimString(presetType?.model);
|
|
329
350
|
const globalModel = trimString(globalOptions.model);
|
|
330
351
|
const presetModel = trimString(preset?.model);
|
|
331
352
|
const profileModel = trimString(profile?.model);
|
|
332
|
-
const model = forcedModel || taskModel || presetTypeModel || globalModel || presetModel || profileModel;
|
|
353
|
+
const model = forcedModel || taskModel || parentMatchModel || presetTypeModel || globalModel || presetModel || profileModel;
|
|
354
|
+
const usedParentMatch = Boolean(parentMatchModel) && model === parentMatchModel;
|
|
333
355
|
const fallbackModels = forcedModel || taskModel
|
|
334
356
|
? []
|
|
335
|
-
:
|
|
357
|
+
: usedParentMatch && parentMatch?.fallbackModels && parentMatch.fallbackModels.length > 0
|
|
358
|
+
? parentMatch.fallbackModels
|
|
359
|
+
: resolveFallbackModels({ model, presetType, preset, profile });
|
|
336
360
|
const extraArgs = forcedModel
|
|
337
361
|
? stripModelArgs([...profileExtraArgs, ...presetTypeExtraArgs, ...taskExtraArgs, ...presetExtraArgs, ...globalExtraArgs])
|
|
338
362
|
: [...profileExtraArgs, ...presetTypeExtraArgs, ...taskExtraArgs, ...presetExtraArgs, ...globalExtraArgs];
|
|
@@ -480,6 +504,7 @@ function normalizeConfig(value: Record<string, unknown>, file: string): Partial<
|
|
|
480
504
|
description: trimString(rawProfile.description),
|
|
481
505
|
model: trimString(rawProfile.model),
|
|
482
506
|
fallbackModels: modelList(rawProfile.fallbackModels, rawProfile.fallbackModel),
|
|
507
|
+
modelByParent: normalizeModelByParent(rawProfile.modelByParent, name, file),
|
|
483
508
|
thinking: trimString(rawProfile.thinking),
|
|
484
509
|
tools: arrayOfStrings(rawProfile.tools),
|
|
485
510
|
extraArgs: arrayOfStrings(rawProfile.extraArgs),
|
|
@@ -555,6 +580,7 @@ function compactProfile(profile: SubagentTypeConfig): SubagentTypeConfig {
|
|
|
555
580
|
if (profile.description) compact.description = profile.description;
|
|
556
581
|
if (profile.model) compact.model = profile.model;
|
|
557
582
|
if (profile.fallbackModels && profile.fallbackModels.length > 0) compact.fallbackModels = profile.fallbackModels;
|
|
583
|
+
if (profile.modelByParent) compact.modelByParent = profile.modelByParent;
|
|
558
584
|
if (profile.thinking) compact.thinking = profile.thinking;
|
|
559
585
|
if (profile.tools && profile.tools.length > 0) compact.tools = profile.tools;
|
|
560
586
|
if (profile.extraArgs && profile.extraArgs.length > 0) compact.extraArgs = profile.extraArgs;
|
|
@@ -606,6 +632,38 @@ function normalizePresetTypeOverrides(value: unknown, file: string, presetName:
|
|
|
606
632
|
return Object.keys(types).length > 0 ? types : undefined;
|
|
607
633
|
}
|
|
608
634
|
|
|
635
|
+
function normalizeModelByParent(value: unknown, typeName: string, file: string): Record<string, ModelByParentEntry> | undefined {
|
|
636
|
+
if (value === undefined || value === null) return undefined;
|
|
637
|
+
if (!isRecord(value)) throw new Error(`Subagent type "${typeName}" modelByParent must be an object: ${file}`);
|
|
638
|
+
const out: Record<string, ModelByParentEntry> = {};
|
|
639
|
+
for (const [pattern, raw] of Object.entries(value)) {
|
|
640
|
+
const pat = trimString(pattern);
|
|
641
|
+
if (!pat) continue;
|
|
642
|
+
if (typeof raw === "string") {
|
|
643
|
+
const model = trimString(raw);
|
|
644
|
+
if (model) out[pat] = { model };
|
|
645
|
+
continue;
|
|
646
|
+
}
|
|
647
|
+
if (isRecord(raw)) {
|
|
648
|
+
const model = trimString(raw.model);
|
|
649
|
+
if (!model) throw new Error(`Subagent type "${typeName}" modelByParent["${pat}"].model must be a non-empty string: ${file}`);
|
|
650
|
+
out[pat] = { model, fallbackModels: modelList(raw.fallbackModels, raw.fallbackModel) };
|
|
651
|
+
continue;
|
|
652
|
+
}
|
|
653
|
+
throw new Error(`Subagent type "${typeName}" modelByParent["${pat}"] must be a string or object: ${file}`);
|
|
654
|
+
}
|
|
655
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
function resolveModelByParent(profile: SubagentTypeConfig | undefined, parentModelRef: string | undefined): ModelByParentEntry | undefined {
|
|
659
|
+
const entries = profile?.modelByParent;
|
|
660
|
+
if (!entries || !parentModelRef) return undefined;
|
|
661
|
+
for (const [pattern, entry] of Object.entries(entries)) {
|
|
662
|
+
if (modelPatternRegExp(pattern).test(parentModelRef)) return entry;
|
|
663
|
+
}
|
|
664
|
+
return undefined;
|
|
665
|
+
}
|
|
666
|
+
|
|
609
667
|
function resolveFallbackModels(options: {
|
|
610
668
|
model?: string;
|
|
611
669
|
presetType?: SubagentPresetTypeOverride;
|
|
@@ -32,7 +32,7 @@ export interface RoutedSubagentTasks {
|
|
|
32
32
|
const ROUTER_SYSTEM_PROMPT = [
|
|
33
33
|
"You route Pi async sub-agent tasks to the best configured subagentType.",
|
|
34
34
|
"Choose exactly one allowed type for each task. Use the allowed type descriptions as the source of truth.",
|
|
35
|
-
"Prefer the most specific matching type over generic quick/deep. Use frontend for UI/UX implementation or visual frontend polish
|
|
35
|
+
"Prefer the most specific matching type over generic quick/deep. Use frontend for UI/UX implementation or visual frontend polish. For pure image inspection, use the lookup tool rather than subagents.",
|
|
36
36
|
"Return only strict JSON with this shape: {\"routes\":[{\"id\":\"task-id\",\"subagentType\":\"type\"}]}",
|
|
37
37
|
"Do not include markdown, comments, explanations, or unknown types.",
|
|
38
38
|
].join("\n");
|
|
@@ -677,7 +677,7 @@ function subagentEnvironment(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
|
|
|
677
677
|
PI_TERMINAL_BELL_DISABLED: "1",
|
|
678
678
|
PI_TOOLS_SUITE_DISABLED_MODULES: appendEnvList(env.PI_TOOLS_SUITE_DISABLED_MODULES, [
|
|
679
679
|
"async-subagents",
|
|
680
|
-
"
|
|
680
|
+
"coding-discipline",
|
|
681
681
|
"question",
|
|
682
682
|
]),
|
|
683
683
|
};
|
|
@@ -14,7 +14,7 @@ export interface AgentTask {
|
|
|
14
14
|
promptAppend?: string;
|
|
15
15
|
/** Full prompt replacement for this task. Supports prompt template variables. */
|
|
16
16
|
promptOverride?: string;
|
|
17
|
-
/** Optional visual attention instructions for
|
|
17
|
+
/** Optional visual attention instructions for agents that receive imagePaths. */
|
|
18
18
|
focus?: string;
|
|
19
19
|
/** Local image files to attach to the sub-agent RPC prompt. */
|
|
20
20
|
imagePaths?: string[];
|