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.
Files changed (64) hide show
  1. package/dist/app/app.d.ts +0 -1
  2. package/dist/app/app.js +28 -21
  3. package/dist/app/constants.js +1 -1
  4. package/dist/app/input/input-action-controller.d.ts +1 -0
  5. package/dist/app/input/input-action-controller.js +3 -0
  6. package/dist/app/input/input-controller.d.ts +1 -0
  7. package/dist/app/input/input-controller.js +40 -12
  8. package/dist/app/model/model-usage-status.js +4 -2
  9. package/dist/app/process.js +11 -0
  10. package/dist/app/rendering/conversation-tool-renderer.js +4 -6
  11. package/dist/app/session/request-history.js +2 -0
  12. package/dist/app/session/session-event-controller.d.ts +13 -0
  13. package/dist/app/session/session-event-controller.js +27 -0
  14. package/dist/app/session/tabs-controller.d.ts +8 -0
  15. package/dist/app/session/tabs-controller.js +37 -6
  16. package/dist/app/workspace/workspace-actions-controller.d.ts +1 -0
  17. package/dist/app/workspace/workspace-actions-controller.js +2 -1
  18. package/dist/bundled-extensions/terminal-bell/index.js +55 -1
  19. package/dist/config.js +1 -1
  20. package/dist/default-pix-config.js +1 -1
  21. package/dist/markdown-format.js +14 -25
  22. package/dist/terminal-width.d.ts +14 -0
  23. package/dist/terminal-width.js +31 -2
  24. package/dist/theme.js +2 -2
  25. package/external/pi-tools-suite/README.md +34 -9
  26. package/external/pi-tools-suite/package.json +3 -3
  27. package/external/pi-tools-suite/src/async-subagents/async-subagents.sample.jsonc +35 -21
  28. package/external/pi-tools-suite/src/async-subagents/commands.ts +1 -1
  29. package/external/pi-tools-suite/src/async-subagents/core/agent-strategy.ts +2 -2
  30. package/external/pi-tools-suite/src/async-subagents/core/config.ts +70 -12
  31. package/external/pi-tools-suite/src/async-subagents/core/routing.ts +1 -1
  32. package/external/pi-tools-suite/src/async-subagents/core/spawn.ts +1 -1
  33. package/external/pi-tools-suite/src/async-subagents/core/types.ts +1 -1
  34. package/external/pi-tools-suite/src/async-subagents/index.ts +6 -6
  35. package/external/pi-tools-suite/src/async-subagents/lib.ts +1 -1
  36. package/external/pi-tools-suite/src/async-subagents/tools/spawn.ts +4 -2
  37. package/external/pi-tools-suite/src/async-subagents/tools/subagents.ts +2 -2
  38. package/external/pi-tools-suite/src/{glm-coding-discipline → coding-discipline}/index.ts +17 -8
  39. package/external/pi-tools-suite/src/config.ts +1 -1
  40. package/external/pi-tools-suite/src/dcp/auto-compress.ts +368 -0
  41. package/external/pi-tools-suite/src/dcp/compress-tool.ts +3 -0
  42. package/external/pi-tools-suite/src/dcp/config.ts +23 -0
  43. package/external/pi-tools-suite/src/dcp/index.ts +112 -7
  44. package/external/pi-tools-suite/src/dcp/prompts.ts +8 -0
  45. package/external/pi-tools-suite/src/dcp/state.ts +41 -0
  46. package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +30 -22
  47. package/external/pi-tools-suite/src/index.ts +2 -1
  48. package/external/pi-tools-suite/src/session-name/index.ts +37 -0
  49. package/external/pi-tools-suite/src/tool-descriptions.ts +16 -4
  50. package/package.json +4 -4
  51. package/skills/skill-creator/SKILL.md +36 -40
  52. package/skills/skill-creator/eval-viewer/viewer.html +2 -2
  53. package/skills/skill-creator/references/schemas.md +1 -1
  54. package/skills/skill-creator/scripts/__pycache__/__init__.cpython-314.pyc +0 -0
  55. package/skills/skill-creator/scripts/__pycache__/aggregate_benchmark.cpython-314.pyc +0 -0
  56. package/skills/skill-creator/scripts/__pycache__/generate_report.cpython-314.pyc +0 -0
  57. package/skills/skill-creator/scripts/__pycache__/improve_description.cpython-314.pyc +0 -0
  58. package/skills/skill-creator/scripts/__pycache__/package_skill.cpython-314.pyc +0 -0
  59. package/skills/skill-creator/scripts/__pycache__/run_eval.cpython-314.pyc +0 -0
  60. package/skills/skill-creator/scripts/__pycache__/run_loop.cpython-314.pyc +0 -0
  61. package/skills/skill-creator/scripts/__pycache__/utils.cpython-314.pyc +0 -0
  62. package/skills/skill-creator/scripts/generate_report.py +1 -1
  63. package/skills/skill-creator/scripts/improve_description.py +14 -24
  64. package/skills/skill-creator/scripts/run_eval.py +89 -82
@@ -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 (let index = 0; index < text.length;) {
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: index, whitespace: currentWhitespace ?? false });
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 = index;
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 (let index = token.start; index < token.end;) {
239
- const codePoint = token.text.codePointAt(index - token.start) ?? 0;
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: index });
237
+ chunks.push({ text: chunkText, start: chunkStart, end: absoluteStart });
244
238
  chunkText = "";
245
- chunkStart = index;
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 (let index = 0; index < text.length;) {
510
- const codePoint = text.codePointAt(index) ?? 0;
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 = nextIndex;
518
- if (char === "/" && index > 0)
519
- breakIndex = index;
507
+ fallbackIndex = end;
508
+ if (char === "/" && start > 0)
509
+ breakIndex = start;
520
510
  else if (/[._:-]/u.test(char))
521
- breakIndex = nextIndex;
522
- index = nextIndex;
511
+ breakIndex = end;
523
512
  }
524
513
  return breakIndex > 0 ? breakIndex : fallbackIndex;
525
514
  }
@@ -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[];
@@ -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 EMOJI_REGEX = /\p{Emoji}/u;
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
- return EMOJI_PRESENTATION_REGEX.test(text) || (EMOJI_REGEX.test(text) && (text.includes("\ufe0f") || text.includes("\u20e3")));
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: "#8b949e",
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: "#475569",
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/glm-coding-discipline` — injects a deduplicated silent-mode and quality-discipline block at the very top of the main-session per-turn system prompt for GLM-family models immediately before the LLM request; disabled for async sub-agents
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 plus the `vision` profile for screenshot/image description via `openai-codex/gpt-5.4-mini`; 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
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`: glm-coding-discipline, ast-grep, async-subagents, lsp, comment-checker, 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
+ 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.5": {
76
+ "openai-codex/gpt-5*": {
75
77
  "compress": {
76
- "minContextPercent": "28%",
77
- "maxContextPercent": "48%"
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`, `vision`). 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; `vision` is only for screenshots/images when the parent model is a non-vision GLM-series model. Run `/hyperplan` to pressure-test a plan before implementation.
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
- When the parent model cannot inspect images, async-subagents adds vision-delegation guidance and can save current-turn image attachments under `.pi/subagents/attachments/` so a `vision` sub-agent can receive them as `imagePaths`. 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`. GLM is no longer treated as blind by async-subagents by default; the main-session `glm-coding-discipline` lookup tool is the preferred path for GLM visual lookups.
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; keep vision on its dedicated model.",
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.6",
42
- "@earendil-works/pi-coding-agent": "0.79.6",
43
- "@earendil-works/pi-tui": "0.79.6",
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 vision overrides. Pi provider metadata can be missing or stale
32
- // after switching models, so async-subagents treats these glob-like model refs
33
- // as text-only even if dynamic capability detection is inconclusive. Matching
34
- // is case-insensitive and supports `*` wildcards. Set [] to disable built-ins.
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; keep vision on the enabled GPT vision model.",
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
- "vision": {
216
- "description": "Use only when task has imagePaths, screenshots, or asks to inspect visible UI/image content for a text-only parent.",
217
- "model": "openai-codex/gpt-5.4-mini",
218
- "thinking": "off",
219
- "promptAppend": [
220
- "You are a vision helper for a parent model that may not be able to see images.",
221
- "Inspect any attached images and any image paths mentioned in the task/scope. Describe concrete visible details, UI state, text, layout, errors, and uncertainties.",
222
- "If focus instructions are provided, prioritize them, but still mention other important visible findings.",
223
- "Do not make code changes. Return a compact visual description that the parent agent can rely on."
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, vision. 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 vision only for screenshots/images when the parent model is a non-vision GLM-series model.
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, read compact results, make the final decisions in the parent session, and report only what matters.
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
- vision: {
205
- description: "Use only when task has imagePaths, screenshots, or asks to inspect visible UI/image content for a text-only parent.",
206
- model: "openai-codex/gpt-5.4-mini",
207
- thinking: "off",
208
- promptAppend: [
209
- "You are a vision helper for a parent model that may not be able to see images.",
210
- "Inspect any attached images and any image paths mentioned in the task/scope. Describe concrete visible details, UI state, text, layout, errors, and uncertainties.",
211
- "If focus instructions are provided, prioritize them, but still mention other important visible findings.",
212
- "Do not make code changes. Return a compact visual description that the parent agent can rely on.",
213
- ].join("\n"),
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
- : resolveFallbackModels({ model, presetType, preset, profile });
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; use vision only for attached images/screenshots or image inspection.",
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
- "glm-coding-discipline",
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 vision-capable sub-agents. */
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[];