pi-subagents 0.21.5 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,30 +4,31 @@ import { buildRuntimeName, defaultSystemPromptMode, frontmatterNameForConfig, pa
4
4
  import { createEditorState, ensureCursorVisible, getCursorDisplayPos, handleEditorInput, renderEditor, wrapText } from "../tui/text-editor.ts";
5
5
  import type { TextEditorState } from "../tui/text-editor.ts";
6
6
  import { pad, row, renderHeader, renderFooter, formatScrollInfo } from "../tui/render-helpers.ts";
7
+ import { findModelInfo, getSupportedThinkingLevels, type ModelInfo, type ThinkingLevel } from "../shared/model-info.ts";
7
8
 
8
- export interface ModelInfo { provider: string; id: string; fullId: string; }
9
+ export type { ModelInfo };
9
10
  export interface SkillInfo { name: string; source: string; description?: string; }
10
11
  export type EditScreen = "edit" | "edit-field" | "edit-prompt";
11
12
  export type EditField = typeof FIELD_ORDER[number];
12
13
 
13
14
  export interface EditState {
14
15
  draft: AgentConfig; isNew: boolean; fieldIndex: number; fieldMode: "text" | "model" | "thinking" | "skills" | null;
15
- fieldEditor: TextEditorState; promptEditor: TextEditorState; modelSearchQuery: string; modelCursor: number; filteredModels: ModelInfo[];
16
+ fieldEditor: TextEditorState; promptEditor: TextEditorState; modelSearchQuery: string; modelCursor: number; models: ModelInfo[]; filteredModels: ModelInfo[];
16
17
  thinkingCursor: number; skillSearchQuery: string; skillCursor: number; filteredSkills: SkillInfo[]; skillSelected: Set<string>; error?: string;
17
18
  fields: EditField[];
18
19
  title?: string;
19
20
  overrideBase?: BuiltinAgentOverrideBase;
21
+ preferredProvider?: string;
20
22
  }
21
23
  interface EditInputResult { action?: "save" | "discard" | "delete"; nextScreen?: EditScreen; }
22
24
  interface CreateEditStateOptions {
23
25
  fields?: EditField[];
24
26
  title?: string;
25
27
  overrideBase?: BuiltinAgentOverrideBase;
28
+ preferredProvider?: string;
26
29
  }
27
30
 
28
- const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh"] as const;
29
31
  const FIELD_ORDER = ["name", "package", "description", "model", "fallbackModels", "thinking", "systemPromptMode", "inheritProjectContext", "inheritSkills", "defaultContext", "tools", "extensions", "skills", "output", "reads", "progress", "interactive", "prompt"] as const;
30
- type ThinkingLevel = typeof THINKING_LEVELS[number];
31
32
  const PROMPT_VIEWPORT_HEIGHT = 16;
32
33
  const MODEL_SELECTOR_HEIGHT = 10;
33
34
  const SKILL_SELECTOR_HEIGHT = 10;
@@ -86,8 +87,8 @@ export function createEditState(draft: AgentConfig, isNew: boolean, models: Mode
86
87
  return {
87
88
  draft: { ...draft, tools: draft.tools ? [...draft.tools] : undefined, mcpDirectTools: draft.mcpDirectTools ? [...draft.mcpDirectTools] : undefined, skills: draft.skills ? [...draft.skills] : undefined, fallbackModels: draft.fallbackModels ? [...draft.fallbackModels] : undefined, extensions: draft.extensions ? [...draft.extensions] : draft.extensions, defaultReads: draft.defaultReads ? [...draft.defaultReads] : undefined, extraFields: draft.extraFields ? { ...draft.extraFields } : undefined },
88
89
  isNew, fieldIndex: 0, fieldMode: null, fieldEditor: createEditorState(), promptEditor: createEditorState(draft.systemPrompt ?? ""),
89
- modelSearchQuery: "", modelCursor: 0, filteredModels: [...models], thinkingCursor: 0, skillSearchQuery: "", skillCursor: 0, filteredSkills: [...skills], skillSelected: new Set(draft.skills ?? []),
90
- fields: options.fields ?? [...FIELD_ORDER], title: options.title, overrideBase: options.overrideBase,
90
+ modelSearchQuery: "", modelCursor: 0, models: [...models], filteredModels: [...models], thinkingCursor: 0, skillSearchQuery: "", skillCursor: 0, filteredSkills: [...skills], skillSelected: new Set(draft.skills ?? []),
91
+ fields: options.fields ?? [...FIELD_ORDER], title: options.title, overrideBase: options.overrideBase, preferredProvider: options.preferredProvider,
91
92
  };
92
93
  }
93
94
 
@@ -180,9 +181,16 @@ function openModelPicker(state: EditState, models: ModelInfo[]): void {
180
181
  state.fieldIndex = state.fields.indexOf("model"); state.fieldMode = "model"; state.modelSearchQuery = ""; state.filteredModels = [...models];
181
182
  const idx = state.filteredModels.findIndex((m) => m.fullId === state.draft.model || m.id === state.draft.model); state.modelCursor = idx >= 0 ? idx : 0;
182
183
  }
184
+ function getDraftThinkingLevels(state: EditState): ThinkingLevel[] {
185
+ return getSupportedThinkingLevels(findModelInfo(state.draft.model, state.models, state.preferredProvider));
186
+ }
187
+
183
188
  function openThinkingPicker(state: EditState): void {
184
189
  state.fieldIndex = state.fields.indexOf("thinking"); state.fieldMode = "thinking";
185
- const idx = THINKING_LEVELS.indexOf((state.draft.thinking ?? "off") as ThinkingLevel); state.thinkingCursor = idx >= 0 ? idx : 0;
190
+ const levels = getDraftThinkingLevels(state);
191
+ const currentLevel = state.draft.thinking ?? "off";
192
+ const idx = levels.findIndex((level) => level === currentLevel);
193
+ state.thinkingCursor = idx >= 0 ? idx : Math.max(0, levels.indexOf("off"));
186
194
  }
187
195
  function openSkillPicker(state: EditState, skills: SkillInfo[]): void {
188
196
  state.fieldIndex = state.fields.indexOf("skills"); state.fieldMode = "skills"; state.skillSearchQuery = ""; state.filteredSkills = [...skills]; state.skillSelected = new Set(state.draft.skills ?? []); state.skillCursor = 0;
@@ -230,16 +238,22 @@ function renderThinkingPicker(state: EditState, width: number, theme: Theme): st
230
238
  high: "Deep reasoning",
231
239
  xhigh: "Maximum reasoning (ultrathink)",
232
240
  };
233
- for (let i = 0; i < THINKING_LEVELS.length; i++) {
234
- const level = THINKING_LEVELS[i]!;
235
- const isSelected = i === state.thinkingCursor;
236
- const prefix = isSelected ? theme.fg("accent", "→ ") : " ";
237
- const levelText = isSelected ? theme.fg("accent", level) : level;
238
- const desc = theme.fg("dim", ` - ${descriptions[level]}`);
239
- lines.push(row(` ${prefix}${levelText}${desc}`, width, theme));
241
+ const levels = getDraftThinkingLevels(state);
242
+ if (levels.length === 0) {
243
+ lines.push(row(` ${theme.fg("dim", "No supported thinking levels")}`, width, theme));
244
+ } else {
245
+ for (let i = 0; i < levels.length; i++) {
246
+ const level = levels[i]!;
247
+ const isSelected = i === state.thinkingCursor;
248
+ const prefix = isSelected ? theme.fg("accent", "→ ") : " ";
249
+ const levelText = isSelected ? theme.fg("accent", level) : level;
250
+ const desc = theme.fg("dim", ` - ${descriptions[level]}`);
251
+ lines.push(row(` ${prefix}${levelText}${desc}`, width, theme));
252
+ }
240
253
  }
241
254
  while (lines.length < 19) lines.push(row("", width, theme));
242
- lines.push(renderFooter(" [enter] select [esc] cancel [↑↓] navigate ", width, theme));
255
+ const footer = levels.length === 0 ? " [esc] cancel " : " [enter] select [esc] cancel [↑↓] navigate ";
256
+ lines.push(renderFooter(footer, width, theme));
243
257
  return lines;
244
258
  }
245
259
 
@@ -323,7 +337,15 @@ export function handleEditInput(screen: EditScreen, state: EditState, data: stri
323
337
  if (screen === "edit-field") {
324
338
  if (state.fieldMode === "model") {
325
339
  if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) { state.fieldMode = null; return { nextScreen: "edit" }; }
326
- if (matchesKey(data, "return")) { const selected = state.filteredModels[state.modelCursor]; if (selected) state.draft.model = selected.fullId; state.fieldMode = null; return { nextScreen: "edit" }; }
340
+ if (matchesKey(data, "return")) {
341
+ const selected = state.filteredModels[state.modelCursor];
342
+ if (selected) {
343
+ state.draft.model = selected.fullId;
344
+ if (state.draft.thinking && !getDraftThinkingLevels(state).some((level) => level === state.draft.thinking)) state.draft.thinking = undefined;
345
+ }
346
+ state.fieldMode = null;
347
+ return { nextScreen: "edit" };
348
+ }
327
349
  if (matchesKey(data, "up")) { if (state.filteredModels.length > 0) state.modelCursor = state.modelCursor === 0 ? state.filteredModels.length - 1 : state.modelCursor - 1; return; }
328
350
  if (matchesKey(data, "down")) { if (state.filteredModels.length > 0) state.modelCursor = state.modelCursor === state.filteredModels.length - 1 ? 0 : state.modelCursor + 1; return; }
329
351
  if (matchesKey(data, "backspace")) { if (state.modelSearchQuery.length > 0) state.modelSearchQuery = state.modelSearchQuery.slice(0, -1); }
@@ -334,10 +356,12 @@ export function handleEditInput(screen: EditScreen, state: EditState, data: stri
334
356
  return;
335
357
  }
336
358
  if (state.fieldMode === "thinking") {
359
+ const levels = getDraftThinkingLevels(state);
337
360
  if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) { state.fieldMode = null; return { nextScreen: "edit" }; }
338
- if (matchesKey(data, "return")) { const selected = THINKING_LEVELS[state.thinkingCursor]; state.draft.thinking = selected === "off" ? undefined : selected; state.fieldMode = null; return { nextScreen: "edit" }; }
339
- if (matchesKey(data, "up")) { state.thinkingCursor = state.thinkingCursor === 0 ? THINKING_LEVELS.length - 1 : state.thinkingCursor - 1; return; }
340
- if (matchesKey(data, "down")) { state.thinkingCursor = state.thinkingCursor === THINKING_LEVELS.length - 1 ? 0 : state.thinkingCursor + 1; return; }
361
+ if (levels.length === 0) return;
362
+ if (matchesKey(data, "return")) { const selected = levels[state.thinkingCursor] ?? "off"; state.draft.thinking = selected === "off" ? undefined : selected; state.fieldMode = null; return { nextScreen: "edit" }; }
363
+ if (matchesKey(data, "up")) { state.thinkingCursor = state.thinkingCursor === 0 ? levels.length - 1 : state.thinkingCursor - 1; return; }
364
+ if (matchesKey(data, "down")) { state.thinkingCursor = state.thinkingCursor === levels.length - 1 ? 0 : state.thinkingCursor + 1; return; }
341
365
  return;
342
366
  }
343
367
  if (state.fieldMode === "skills") {
@@ -45,7 +45,7 @@ interface ChainEntry { id: string; kind: "chain"; config: ChainConfig; }
45
45
  interface NameInputState { mode: "new-agent" | "clone-agent" | "clone-chain" | "new-chain"; editor: TextEditorState; scope: "user" | "project"; allowProject: boolean; sourceId?: string; template?: AgentTemplate; error?: string; }
46
46
  interface StatusMessage { text: string; type: "error" | "info"; }
47
47
  interface OverrideScopeState { selectedScope: "user" | "project"; allowProject: boolean; }
48
- export interface AgentManagerOptions { newShortcut?: string; }
48
+ export interface AgentManagerOptions { newShortcut?: string; preferredModelProvider?: string; }
49
49
 
50
50
  const BUILTIN_OVERRIDE_FIELDS: EditField[] = ["model", "fallbackModels", "thinking", "systemPromptMode", "inheritProjectContext", "inheritSkills", "defaultContext", "disabled", "tools", "skills", "prompt"];
51
51
 
@@ -135,6 +135,7 @@ export class AgentManagerComponent implements Component {
135
135
  private skills: SkillInfo[];
136
136
  private done: (result: ManagerResult) => void;
137
137
  private shortcuts: ListShortcuts;
138
+ private preferredModelProvider: string | undefined;
138
139
 
139
140
  constructor(tui: TUI, theme: Theme, agentData: AgentData, models: ModelInfo[], skills: SkillInfo[], done: (result: ManagerResult) => void, options: AgentManagerOptions = {}) {
140
141
  this.tui = tui;
@@ -144,6 +145,7 @@ export class AgentManagerComponent implements Component {
144
145
  this.skills = skills;
145
146
  this.done = done;
146
147
  this.shortcuts = { newShortcut: options.newShortcut?.trim() || DEFAULT_AGENT_MANAGER_NEW_SHORTCUT };
148
+ this.preferredModelProvider = options.preferredModelProvider;
147
149
  this.loadEntries();
148
150
  }
149
151
 
@@ -196,7 +198,7 @@ export class AgentManagerComponent implements Component {
196
198
 
197
199
  private enterDetail(entry: AgentEntry): void { this.currentAgentId = entry.id; this.detailState = { resolved: true, scrollOffset: 0, recentRuns: loadRunsForAgent(entry.config.name).slice(0, 5) }; this.screen = "detail"; }
198
200
  private enterChainDetail(entry: ChainEntry): void { this.currentChainId = entry.id; this.chainDetailState = { scrollOffset: 0 }; this.screen = "chain-detail"; }
199
- private enterEdit(entry: AgentEntry): void { this.currentAgentId = entry.id; this.builtinOverrideScope = null; this.editState = createEditState(entry.config, entry.isNew, this.models, this.skills); this.screen = "edit"; }
201
+ private enterEdit(entry: AgentEntry): void { this.currentAgentId = entry.id; this.builtinOverrideScope = null; this.editState = createEditState(entry.config, entry.isNew, this.models, this.skills, { preferredProvider: this.preferredModelProvider }); this.screen = "edit"; }
200
202
  private enterBuiltinOverrideScope(entry: AgentEntry): void {
201
203
  this.currentAgentId = entry.id;
202
204
  this.overrideScopeState = { selectedScope: this.agentData.projectSettingsPath ? "project" : "user", allowProject: Boolean(this.agentData.projectSettingsPath) };
@@ -209,6 +211,7 @@ export class AgentManagerComponent implements Component {
209
211
  fields: BUILTIN_OVERRIDE_FIELDS,
210
212
  title: `Builtin Override: ${entry.config.name} [${scope}]`,
211
213
  overrideBase: this.resolveBuiltinOverrideBase(entry),
214
+ preferredProvider: this.preferredModelProvider,
212
215
  });
213
216
  this.screen = "edit";
214
217
  }
@@ -538,6 +538,7 @@ interface SingleStepContext {
538
538
  piArgv1?: string;
539
539
  registerInterrupt?: (interrupt: (() => void) | undefined) => void;
540
540
  childIntercomTarget?: string;
541
+ orchestratorIntercomTarget?: string;
541
542
  onChildEvent?: (event: ChildEvent) => void;
542
543
  }
543
544
 
@@ -604,6 +605,10 @@ async function runSingleStep(
604
605
  mcpDirectTools: step.mcpDirectTools,
605
606
  promptFileStem: step.agent,
606
607
  intercomSessionName: ctx.childIntercomTarget,
608
+ orchestratorIntercomTarget: ctx.orchestratorIntercomTarget,
609
+ runId: ctx.id,
610
+ childAgentName: step.agent,
611
+ childIndex: ctx.flatIndex,
607
612
  });
608
613
  const run = await runPiStreaming(
609
614
  args,
@@ -1299,6 +1304,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
1299
1304
  piPackageRoot: config.piPackageRoot,
1300
1305
  piArgv1: config.piArgv1,
1301
1306
  childIntercomTarget: config.childIntercomTargets?.[fi],
1307
+ orchestratorIntercomTarget: config.controlIntercomTarget,
1302
1308
  registerInterrupt: (interrupt) => {
1303
1309
  activeChildInterrupt = interrupt;
1304
1310
  },
@@ -1439,6 +1445,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
1439
1445
  piPackageRoot: config.piPackageRoot,
1440
1446
  piArgv1: config.piArgv1,
1441
1447
  childIntercomTarget: config.childIntercomTargets?.[flatIndex],
1448
+ orchestratorIntercomTarget: config.controlIntercomTarget,
1442
1449
  registerInterrupt: (interrupt) => {
1443
1450
  activeChildInterrupt = interrupt;
1444
1451
  },
@@ -18,15 +18,10 @@ import { createEditorState, ensureCursorVisible, getCursorDisplayPos, handleEdit
18
18
  import { updateFrontmatterField } from "../../agents/agent-serializer.ts";
19
19
  import { serializeChain } from "../../agents/chain-serializer.ts";
20
20
  import { resolveModelCandidate, splitThinkingSuffix } from "../shared/model-fallback.ts";
21
+ import { findModelInfo, getSupportedThinkingLevels, type ModelInfo, type ThinkingLevel } from "../../shared/model-info.ts";
21
22
 
22
23
  type ClarifyMode = 'single' | 'parallel' | 'chain';
23
24
 
24
- export interface ModelInfo {
25
- provider: string;
26
- id: string;
27
- fullId: string;
28
- }
29
-
30
25
  export interface BehaviorOverride {
31
26
  output?: string | false;
32
27
  reads?: string[] | false;
@@ -44,9 +39,6 @@ export interface ChainClarifyResult {
44
39
 
45
40
  type EditMode = "template" | "output" | "reads" | "model" | "thinking" | "skills";
46
41
 
47
- const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh"] as const;
48
- type ThinkingLevel = typeof THINKING_LEVELS[number];
49
-
50
42
  /**
51
43
  * TUI component for chain clarification.
52
44
  * Factory signature matches ctx.ui.custom: (tui, theme, kb, done) => Component
@@ -602,7 +594,10 @@ export class ChainClarifyComponent implements Component {
602
594
  const selected = this.filteredModels[this.modelSelectedIndex];
603
595
  if (selected) {
604
596
  const { thinkingSuffix } = splitThinkingSuffix(this.getEffectiveModel(this.editingStep!));
605
- this.updateBehavior(this.editingStep!, "model", `${selected.fullId}${thinkingSuffix}`);
597
+ const requestedLevel = thinkingSuffix.slice(1);
598
+ const selectedModel = findModelInfo(selected.fullId, this.availableModels, this.preferredProvider);
599
+ const suffix = getSupportedThinkingLevels(selectedModel).some((level) => level === requestedLevel) ? thinkingSuffix : "";
600
+ this.updateBehavior(this.editingStep!, "model", `${selected.fullId}${suffix}`);
606
601
  }
607
602
  this.exitEditMode();
608
603
  return;
@@ -645,6 +640,10 @@ export class ChainClarifyComponent implements Component {
645
640
  }
646
641
  }
647
642
 
643
+ private getAvailableThinkingLevels(stepIndex: number): ThinkingLevel[] {
644
+ return getSupportedThinkingLevels(findModelInfo(this.getEffectiveModel(stepIndex), this.availableModels, this.preferredProvider));
645
+ }
646
+
648
647
  /** Enter thinking level selector mode */
649
648
  private enterThinkingSelector(): void {
650
649
  if (!this.getEffectiveBehavior(this.selectedStep).model) {
@@ -654,15 +653,11 @@ export class ChainClarifyComponent implements Component {
654
653
  this.editingStep = this.selectedStep;
655
654
  this.editMode = "thinking";
656
655
 
657
- const currentModel = this.getEffectiveModel(this.selectedStep);
658
- const colonIdx = currentModel.lastIndexOf(":");
659
- if (colonIdx !== -1) {
660
- const suffix = currentModel.substring(colonIdx + 1);
661
- const levelIdx = THINKING_LEVELS.indexOf(suffix as ThinkingLevel);
662
- this.thinkingSelectedIndex = levelIdx >= 0 ? levelIdx : 0;
663
- } else {
664
- this.thinkingSelectedIndex = 0;
665
- }
656
+ const levels = this.getAvailableThinkingLevels(this.selectedStep);
657
+ const { thinkingSuffix } = splitThinkingSuffix(this.getEffectiveModel(this.selectedStep));
658
+ const suffix = thinkingSuffix.slice(1);
659
+ const levelIdx = levels.findIndex((level) => level === suffix);
660
+ this.thinkingSelectedIndex = levelIdx >= 0 ? levelIdx : Math.max(0, levels.indexOf("off"));
666
661
 
667
662
  this.tui.requestRender();
668
663
  }
@@ -673,8 +668,11 @@ export class ChainClarifyComponent implements Component {
673
668
  return;
674
669
  }
675
670
 
671
+ const levels = this.getAvailableThinkingLevels(this.editingStep!);
672
+ if (levels.length === 0) return;
673
+
676
674
  if (matchesKey(data, "return")) {
677
- const selectedLevel = THINKING_LEVELS[this.thinkingSelectedIndex];
675
+ const selectedLevel = levels[this.thinkingSelectedIndex] ?? "off";
678
676
  this.applyThinkingLevel(selectedLevel);
679
677
  this.exitEditMode();
680
678
  return;
@@ -682,14 +680,14 @@ export class ChainClarifyComponent implements Component {
682
680
 
683
681
  if (matchesKey(data, "up")) {
684
682
  this.thinkingSelectedIndex = this.thinkingSelectedIndex === 0
685
- ? THINKING_LEVELS.length - 1
683
+ ? levels.length - 1
686
684
  : this.thinkingSelectedIndex - 1;
687
685
  this.tui.requestRender();
688
686
  return;
689
687
  }
690
688
 
691
689
  if (matchesKey(data, "down")) {
692
- this.thinkingSelectedIndex = this.thinkingSelectedIndex === THINKING_LEVELS.length - 1
690
+ this.thinkingSelectedIndex = this.thinkingSelectedIndex === levels.length - 1
693
691
  ? 0
694
692
  : this.thinkingSelectedIndex + 1;
695
693
  this.tui.requestRender();
@@ -1044,13 +1042,18 @@ export class ChainClarifyComponent implements Component {
1044
1042
  "xhigh": "Maximum reasoning (ultrathink)",
1045
1043
  };
1046
1044
 
1047
- for (let i = 0; i < THINKING_LEVELS.length; i++) {
1048
- const level = THINKING_LEVELS[i];
1049
- const isSelected = i === this.thinkingSelectedIndex;
1050
- const prefix = isSelected ? th.fg("accent", "→ ") : " ";
1051
- const levelText = isSelected ? th.fg("accent", level) : level;
1052
- const desc = th.fg("dim", ` - ${levelDescriptions[level]}`);
1053
- lines.push(this.row(` ${prefix}${levelText}${desc}`));
1045
+ const levels = this.getAvailableThinkingLevels(this.editingStep!);
1046
+ if (levels.length === 0) {
1047
+ lines.push(this.row(` ${th.fg("dim", "No supported thinking levels")}`));
1048
+ } else {
1049
+ for (let i = 0; i < levels.length; i++) {
1050
+ const level = levels[i]!;
1051
+ const isSelected = i === this.thinkingSelectedIndex;
1052
+ const prefix = isSelected ? th.fg("accent", "→ ") : " ";
1053
+ const levelText = isSelected ? th.fg("accent", level) : level;
1054
+ const desc = th.fg("dim", ` - ${levelDescriptions[level]}`);
1055
+ lines.push(this.row(` ${prefix}${levelText}${desc}`));
1056
+ }
1054
1057
  }
1055
1058
 
1056
1059
  const contentLines = lines.length;
@@ -1059,7 +1062,9 @@ export class ChainClarifyComponent implements Component {
1059
1062
  lines.push(this.row(""));
1060
1063
  }
1061
1064
 
1062
- const footerText = " [Enter] Select • [Esc] Cancel • ↑↓ Navigate ";
1065
+ const footerText = levels.length === 0
1066
+ ? " [Esc] Cancel "
1067
+ : " [Enter] Select • [Esc] Cancel • ↑↓ Navigate ";
1063
1068
  lines.push(this.renderFooter(footerText));
1064
1069
 
1065
1070
  return lines;
@@ -7,7 +7,8 @@ import * as path from "node:path";
7
7
  import type { AgentToolResult } from "@mariozechner/pi-agent-core";
8
8
  import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
9
9
  import type { AgentConfig } from "../../agents/agents.ts";
10
- import { ChainClarifyComponent, type ChainClarifyResult, type BehaviorOverride, type ModelInfo } from "./chain-clarify.ts";
10
+ import { ChainClarifyComponent, type ChainClarifyResult, type BehaviorOverride } from "./chain-clarify.ts";
11
+ import { toModelInfo, type ModelInfo } from "../../shared/model-info.ts";
11
12
  import {
12
13
  resolveChainTemplates,
13
14
  createChainDir,
@@ -93,6 +94,7 @@ interface ParallelChainRunInput {
93
94
  onControlEvent?: (event: ControlEvent) => void;
94
95
  controlConfig: ResolvedControlConfig;
95
96
  childIntercomTarget?: (agent: string, index: number) => string | undefined;
97
+ orchestratorIntercomTarget?: string;
96
98
  foregroundControl?: {
97
99
  updatedAt: number;
98
100
  currentAgent?: string;
@@ -240,6 +242,7 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
240
242
  controlConfig: input.controlConfig,
241
243
  onControlEvent: input.onControlEvent,
242
244
  intercomSessionName: input.childIntercomTarget?.(task.agent, input.globalTaskIndex + taskIndex),
245
+ orchestratorIntercomTarget: input.orchestratorIntercomTarget,
243
246
  modelOverride: effectiveModel,
244
247
  availableModels: input.availableModels,
245
248
  preferredModelProvider: input.ctx.model?.provider,
@@ -313,6 +316,7 @@ interface ChainExecutionParams {
313
316
  onControlEvent?: (event: ControlEvent) => void;
314
317
  controlConfig: ResolvedControlConfig;
315
318
  childIntercomTarget?: (agent: string, index: number) => string | undefined;
319
+ orchestratorIntercomTarget?: string;
316
320
  foregroundControl?: {
317
321
  updatedAt: number;
318
322
  currentAgent?: string;
@@ -363,6 +367,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
363
367
  onControlEvent,
364
368
  controlConfig,
365
369
  childIntercomTarget,
370
+ orchestratorIntercomTarget,
366
371
  foregroundControl,
367
372
  intercomEvents,
368
373
  chainSkills: chainSkillsParam,
@@ -389,11 +394,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
389
394
  let templates: ResolvedTemplates = resolveChainTemplates(chainSteps);
390
395
  const shouldClarify = clarify !== false && ctx.hasUI && !hasParallelSteps;
391
396
  let tuiBehaviorOverrides: (BehaviorOverride | undefined)[] | undefined;
392
- const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map((m) => ({
393
- provider: m.provider,
394
- id: m.id,
395
- fullId: `${m.provider}/${m.id}`,
396
- }));
397
+ const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map(toModelInfo);
397
398
  const availableSkills = discoverAvailableSkills(cwd ?? ctx.cwd);
398
399
 
399
400
  if (shouldClarify) {
@@ -586,6 +587,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
586
587
  controlConfig,
587
588
  onControlEvent,
588
589
  childIntercomTarget,
590
+ orchestratorIntercomTarget,
589
591
  foregroundControl,
590
592
  worktreeSetup,
591
593
  maxSubagentDepth: params.maxSubagentDepth,
@@ -771,6 +773,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
771
773
  controlConfig,
772
774
  onControlEvent,
773
775
  intercomSessionName: childIntercomTarget?.(seqStep.agent, globalTaskIndex),
776
+ orchestratorIntercomTarget,
774
777
  modelOverride: effectiveModel,
775
778
  availableModels,
776
779
  preferredModelProvider: ctx.model?.provider,
@@ -153,6 +153,10 @@ async function runSingleAttempt(
153
153
  mcpDirectTools: agent.mcpDirectTools,
154
154
  promptFileStem: agent.name,
155
155
  intercomSessionName: options.intercomSessionName,
156
+ orchestratorIntercomTarget: options.orchestratorIntercomTarget,
157
+ runId: options.runId,
158
+ childAgentName: agent.name,
159
+ childIndex: options.index ?? 0,
156
160
  });
157
161
 
158
162
  const result: SingleResult = {
@@ -5,7 +5,8 @@ import type { AgentToolResult } from "@mariozechner/pi-agent-core";
5
5
  import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
6
6
  import { type AgentConfig, type AgentScope } from "../../agents/agents.ts";
7
7
  import { getArtifactsDir } from "../../shared/artifacts.ts";
8
- import { ChainClarifyComponent, type ChainClarifyResult, type ModelInfo } from "./chain-clarify.ts";
8
+ import { ChainClarifyComponent, type ChainClarifyResult } from "./chain-clarify.ts";
9
+ import { toModelInfo, type ModelInfo } from "../../shared/model-info.ts";
9
10
  import { executeChain } from "./chain-execution.ts";
10
11
  import { resolveExecutionAgentScope } from "../../agents/agent-scope.ts";
11
12
  import { handleManagementAction } from "../../agents/agent-management.ts";
@@ -358,11 +359,7 @@ async function resumeAsyncRun(input: {
358
359
 
359
360
  const runId = randomUUID().slice(0, 8);
360
361
  const artifactConfig: ArtifactConfig = { ...DEFAULT_ARTIFACT_CONFIG, enabled: input.params.artifacts !== false };
361
- const availableModels = input.ctx.modelRegistry.getAvailable().map((m) => ({
362
- provider: m.provider,
363
- id: m.id,
364
- fullId: `${m.provider}/${m.id}`,
365
- }));
362
+ const availableModels = input.ctx.modelRegistry.getAvailable().map(toModelInfo);
366
363
  const result = executeAsyncSingle(runId, {
367
364
  agent: target.agent,
368
365
  task: buildRevivedAsyncTask(target, followUp),
@@ -782,11 +779,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
782
779
  currentSessionId: deps.state.currentSessionId!,
783
780
  currentModelProvider: ctx.model?.provider,
784
781
  };
785
- const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map((m) => ({
786
- provider: m.provider,
787
- id: m.id,
788
- fullId: `${m.provider}/${m.id}`,
789
- }));
782
+ const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map(toModelInfo);
790
783
  const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth);
791
784
  const currentProvider = ctx.model?.provider;
792
785
  const controlIntercomTarget = intercomBridge.active ? intercomBridge.orchestratorTarget : undefined;
@@ -951,6 +944,7 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
951
944
  onControlEvent,
952
945
  controlConfig,
953
946
  childIntercomTarget: childIntercomTarget ? (agent, index) => childIntercomTarget(runId, agent, index) : undefined,
947
+ orchestratorIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
954
948
  foregroundControl,
955
949
  chainSkills,
956
950
  chainDir: params.chainDir,
@@ -979,11 +973,7 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
979
973
  chain: asyncChain,
980
974
  agents,
981
975
  ctx: asyncCtx,
982
- availableModels: ctx.modelRegistry.getAvailable().map((m) => ({
983
- provider: m.provider,
984
- id: m.id,
985
- fullId: `${m.provider}/${m.id}`,
986
- })),
976
+ availableModels: ctx.modelRegistry.getAvailable().map(toModelInfo),
987
977
  cwd: effectiveCwd,
988
978
  maxOutput: params.maxOutput,
989
979
  artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
@@ -1045,6 +1035,7 @@ interface ForegroundParallelRunInput {
1045
1035
  controlConfig: ResolvedControlConfig;
1046
1036
  onControlEvent?: (event: ControlEvent) => void;
1047
1037
  childIntercomTarget?: (agent: string, index: number) => string | undefined;
1038
+ orchestratorIntercomTarget?: string;
1048
1039
  foregroundControl?: SubagentState["foregroundControls"] extends Map<string, infer T> ? T : never;
1049
1040
  concurrencyLimit: number;
1050
1041
  liveResults: (SingleResult | undefined)[];
@@ -1203,6 +1194,7 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
1203
1194
  controlConfig: input.controlConfig,
1204
1195
  onControlEvent: input.onControlEvent,
1205
1196
  intercomSessionName: input.childIntercomTarget?.(task.agent, index),
1197
+ orchestratorIntercomTarget: input.orchestratorIntercomTarget,
1206
1198
  modelOverride: input.modelOverrides[index],
1207
1199
  availableModels: input.availableModels,
1208
1200
  preferredModelProvider: input.ctx.model?.provider,
@@ -1307,11 +1299,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
1307
1299
  }
1308
1300
 
1309
1301
  const currentProvider = ctx.model?.provider;
1310
- const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map((m) => ({
1311
- provider: m.provider,
1312
- id: m.id,
1313
- fullId: `${m.provider}/${m.id}`,
1314
- }));
1302
+ const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map(toModelInfo);
1315
1303
  let taskTexts = tasks.map((t) => t.task);
1316
1304
  const skillOverrides: (string[] | false | undefined)[] = tasks.map((t) =>
1317
1305
  normalizeSkillInput(t.skill),
@@ -1484,6 +1472,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
1484
1472
  controlConfig,
1485
1473
  onControlEvent,
1486
1474
  childIntercomTarget: childIntercomTarget ? (agent, index) => childIntercomTarget(runId, agent, index) : undefined,
1475
+ orchestratorIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
1487
1476
  foregroundControl,
1488
1477
  concurrencyLimit: parallelConcurrency,
1489
1478
  maxSubagentDepths,
@@ -1588,11 +1577,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
1588
1577
  }
1589
1578
 
1590
1579
  const currentProvider = ctx.model?.provider;
1591
- const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map((m) => ({
1592
- provider: m.provider,
1593
- id: m.id,
1594
- fullId: `${m.provider}/${m.id}`,
1595
- }));
1580
+ const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map(toModelInfo);
1596
1581
  let task = params.task ?? "";
1597
1582
  let modelOverride: string | undefined = resolveModelCandidate(
1598
1583
  (params.model as string | undefined) ?? agentConfig.model,
@@ -1753,6 +1738,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
1753
1738
  controlConfig,
1754
1739
  onControlEvent,
1755
1740
  intercomSessionName: childIntercomTarget,
1741
+ orchestratorIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
1756
1742
  index: 0,
1757
1743
  modelOverride,
1758
1744
  availableModels,
@@ -1,10 +1,7 @@
1
+ import type { ModelInfo as AvailableModelInfo } from "../../shared/model-info.ts";
1
2
  import type { Usage } from "../../shared/types.ts";
2
3
 
3
- export interface AvailableModelInfo {
4
- provider: string;
5
- id: string;
6
- fullId: string;
7
- }
4
+ export type { AvailableModelInfo };
8
5
 
9
6
  interface ModelAttemptSummary {
10
7
  model: string;
@@ -7,6 +7,10 @@ const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh"];
7
7
  const TASK_ARG_LIMIT = 8000;
8
8
  const PROMPT_RUNTIME_EXTENSION_PATH = path.join(path.dirname(fileURLToPath(import.meta.url)), "subagent-prompt-runtime.ts");
9
9
  export const SUBAGENT_CHILD_ENV = "PI_SUBAGENT_CHILD";
10
+ export const SUBAGENT_ORCHESTRATOR_TARGET_ENV = "PI_SUBAGENT_ORCHESTRATOR_TARGET";
11
+ export const SUBAGENT_RUN_ID_ENV = "PI_SUBAGENT_RUN_ID";
12
+ export const SUBAGENT_CHILD_AGENT_ENV = "PI_SUBAGENT_CHILD_AGENT";
13
+ export const SUBAGENT_CHILD_INDEX_ENV = "PI_SUBAGENT_CHILD_INDEX";
10
14
 
11
15
  interface BuildPiArgsInput {
12
16
  baseArgs: string[];
@@ -25,6 +29,10 @@ interface BuildPiArgsInput {
25
29
  mcpDirectTools?: string[];
26
30
  promptFileStem?: string;
27
31
  intercomSessionName?: string;
32
+ orchestratorIntercomTarget?: string;
33
+ runId?: string;
34
+ childAgentName?: string;
35
+ childIndex?: number;
28
36
  }
29
37
 
30
38
  interface BuildPiArgsResult {
@@ -119,6 +127,18 @@ export function buildPiArgs(input: BuildPiArgsInput): BuildPiArgsResult {
119
127
  if (input.intercomSessionName) {
120
128
  env.PI_SUBAGENT_INTERCOM_SESSION_NAME = input.intercomSessionName;
121
129
  }
130
+ if (input.orchestratorIntercomTarget) {
131
+ env[SUBAGENT_ORCHESTRATOR_TARGET_ENV] = input.orchestratorIntercomTarget;
132
+ }
133
+ if (input.runId) {
134
+ env[SUBAGENT_RUN_ID_ENV] = input.runId;
135
+ }
136
+ if (input.childAgentName) {
137
+ env[SUBAGENT_CHILD_AGENT_ENV] = input.childAgentName;
138
+ }
139
+ if (input.childIndex !== undefined) {
140
+ env[SUBAGENT_CHILD_INDEX_ENV] = String(input.childIndex);
141
+ }
122
142
  if (input.mcpDirectTools?.length) {
123
143
  env.MCP_DIRECT_TOOLS = input.mcpDirectTools.join(",");
124
144
  } else {