@gotgenes/pi-subagents 6.3.1 → 6.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.ts CHANGED
@@ -14,7 +14,7 @@ import { join } from "node:path";
14
14
  import { defineTool, type ExtensionAPI, getAgentDir } from "@earendil-works/pi-coding-agent";
15
15
  import { AgentManager } from "./agent-manager.js";
16
16
  import { getAgentConversation, normalizeMaxTurns, resumeAgent, runAgent, steerAgent } from "./agent-runner.js";
17
- import { getAvailableTypes, getDefaultAgentNames, getUserAgentNames, registerAgents, resolveAgentConfig, } from "./agent-types.js";
17
+ import { AgentTypeRegistry } from "./agent-types.js";
18
18
  import { loadCustomAgents } from "./custom-agents.js";
19
19
  import { SessionLifecycleHandler, ToolStartHandler } from "./handlers/index.js";
20
20
  import { type ModelRegistry, resolveModel } from "./model-resolver.js";
@@ -40,14 +40,7 @@ export default function (pi: ExtensionAPI) {
40
40
  // ---- Register custom notification renderer ----
41
41
  pi.registerMessageRenderer<NotificationDetails>("subagent-notification", createNotificationRenderer());
42
42
 
43
- /** Reload agents from .pi/agents/*.md and merge with defaults (called on init and each Agent invocation). */
44
- const reloadCustomAgents = () => {
45
- const userAgents = loadCustomAgents(process.cwd());
46
- registerAgents(userAgents);
47
- };
48
-
49
- // Initial load
50
- reloadCustomAgents();
43
+ const registry = new AgentTypeRegistry(() => loadCustomAgents(process.cwd()));
51
44
 
52
45
  // ---- Runtime: all mutable extension state in one place ----
53
46
  const runtime = createSubagentRuntime();
@@ -67,6 +60,7 @@ export default function (pi: ExtensionAPI) {
67
60
  runner: { run: runAgent, resume: resumeAgent },
68
61
  worktrees: new GitWorktreeManager(process.cwd()),
69
62
  exec: (cmd, args, opts) => pi.exec(cmd, args, opts),
63
+ registry,
70
64
  onComplete: (record) => {
71
65
  // Emit lifecycle event based on terminal status
72
66
  const isError = record.status === "error" || record.status === "stopped" || record.status === "aborted";
@@ -137,7 +131,7 @@ export default function (pi: ExtensionAPI) {
137
131
  pi.on("session_shutdown", () => lifecycle.handleSessionShutdown());
138
132
 
139
133
  // Live widget: show running agents above editor
140
- runtime.widget = new AgentWidget(manager, runtime.agentActivity);
134
+ runtime.widget = new AgentWidget(manager, runtime.agentActivity, registry);
141
135
 
142
136
  // Grab UI context from first tool execution + clear lingering widget on new turn
143
137
  const toolStart = new ToolStartHandler(runtime);
@@ -145,17 +139,17 @@ export default function (pi: ExtensionAPI) {
145
139
 
146
140
  /** Build the full type list text dynamically from the unified registry. */
147
141
  const buildTypeListText = () => {
148
- const defaultNames = getDefaultAgentNames();
149
- const userNames = getUserAgentNames();
142
+ const defaultNames = registry.getDefaultAgentNames();
143
+ const userNames = registry.getUserAgentNames();
150
144
 
151
145
  const defaultDescs = defaultNames.map((name) => {
152
- const cfg = resolveAgentConfig(name);
146
+ const cfg = registry.resolveAgentConfig(name);
153
147
  const modelSuffix = cfg.model ? ` (${getModelLabelFromConfig(cfg.model)})` : "";
154
148
  return `- ${name}: ${cfg.description}${modelSuffix}`;
155
149
  });
156
150
 
157
151
  const customDescs = userNames.map((name) => {
158
- const cfg = resolveAgentConfig(name);
152
+ const cfg = registry.resolveAgentConfig(name);
159
153
  return `- ${name}: ${cfg.description}`;
160
154
  });
161
155
 
@@ -201,9 +195,9 @@ export default function (pi: ExtensionAPI) {
201
195
  },
202
196
  agentActivity: runtime.agentActivity,
203
197
  emitEvent: (name, data) => pi.events.emit(name, data),
204
- reloadCustomAgents,
198
+ registry,
205
199
  typeListText,
206
- availableTypesText: getAvailableTypes().join(", "),
200
+ availableTypesText: registry.getAvailableTypes().join(", "),
207
201
  agentDir: getAgentDir(),
208
202
  getDefaultMaxTurns: () => runtime.defaultMaxTurns,
209
203
  })));
@@ -214,6 +208,7 @@ export default function (pi: ExtensionAPI) {
214
208
  getRecord: (id) => manager.getRecord(id),
215
209
  cancelNudge: (key) => notifications.cancelNudge(key),
216
210
  getConversation: (session) => getAgentConversation(session),
211
+ registry,
217
212
  })));
218
213
 
219
214
  // ---- steer_subagent tool ----
@@ -234,13 +229,13 @@ export default function (pi: ExtensionAPI) {
234
229
  getMaxConcurrent: () => manager.getMaxConcurrent(),
235
230
  setMaxConcurrent: (n) => manager.setMaxConcurrent(n),
236
231
  },
237
- reloadCustomAgents,
232
+ registry,
238
233
  agentActivity: runtime.agentActivity,
239
- getModelLabel: (type, registry) => {
240
- const cfg = resolveAgentConfig(type);
234
+ getModelLabel: (type, modelRegistry) => {
235
+ const cfg = registry.resolveAgentConfig(type);
241
236
  if (!cfg.model) return 'inherit';
242
- if (registry) {
243
- const resolved = resolveModel(cfg.model, registry);
237
+ if (modelRegistry) {
238
+ const resolved = resolveModel(cfg.model, modelRegistry);
244
239
  if (typeof resolved === 'string') return 'inherit';
245
240
  }
246
241
  return getModelLabelFromConfig(cfg.model);
@@ -11,10 +11,9 @@
11
11
  */
12
12
 
13
13
  import {
14
+ type AgentConfigLookup,
14
15
  getMemoryToolNames,
15
16
  getReadOnlyMemoryToolNames,
16
- getToolNamesForType,
17
- resolveAgentConfig,
18
17
  } from "./agent-types.js";
19
18
  import { buildMemoryBlock, buildReadOnlyMemoryBlock } from "./memory.js";
20
19
  import { buildAgentPrompt, type PromptExtras } from "./prompts.js";
@@ -140,14 +139,16 @@ function resolveDefaultModel(
140
139
  * @param ctx Narrow context from the parent session.
141
140
  * @param options Per-call overrides (cwd, isolated, model, thinkingLevel).
142
141
  * @param env Pre-resolved environment info from `detectEnv()`.
142
+ * @param registry Agent config lookup — provides resolveAgentConfig and getToolNamesForType.
143
143
  */
144
144
  export function assembleSessionConfig(
145
145
  type: SubagentType,
146
146
  ctx: AssemblerContext,
147
147
  options: AssemblerOptions,
148
148
  env: EnvInfo,
149
+ registry: AgentConfigLookup,
149
150
  ): SessionConfig {
150
- const agentConfig = resolveAgentConfig(type);
151
+ const agentConfig = registry.resolveAgentConfig(type);
151
152
 
152
153
  const effectiveCwd = options.cwd ?? ctx.cwd;
153
154
 
@@ -166,7 +167,7 @@ export function assembleSessionConfig(
166
167
  }
167
168
  }
168
169
 
169
- let toolNames = getToolNamesForType(type);
170
+ let toolNames = registry.getToolNamesForType(type);
170
171
 
171
172
  // Persistent memory: detect write capability and branch accordingly.
172
173
  // Account for disallowedTools — a tool in the base set but on the denylist
@@ -3,7 +3,7 @@ import { Text } from "@earendil-works/pi-tui";
3
3
  import { Type } from "@sinclair/typebox";
4
4
  import type { SpawnOptions } from "../agent-manager.js";
5
5
  import { normalizeMaxTurns } from "../agent-runner.js";
6
- import { resolveAgentConfig, resolveType } from "../agent-types.js";
6
+ import { AgentTypeRegistry } from "../agent-types.js";
7
7
  import { resolveAgentInvocationConfig } from "../invocation-config.js";
8
8
  import { resolveInvocationModel } from "../model-resolver.js";
9
9
 
@@ -108,7 +108,7 @@ export interface AgentToolDeps {
108
108
  widget: AgentToolWidget;
109
109
  agentActivity: Map<string, AgentActivity>;
110
110
  emitEvent: (name: string, data: unknown) => void;
111
- reloadCustomAgents: () => void;
111
+ registry: AgentTypeRegistry;
112
112
  typeListText: string;
113
113
  availableTypesText: string;
114
114
  agentDir: string;
@@ -207,7 +207,7 @@ Guidelines:
207
207
 
208
208
  renderCall(args: Record<string, unknown>, theme: any) {
209
209
  const displayName = args.subagent_type
210
- ? getDisplayName(args.subagent_type as string)
210
+ ? getDisplayName(args.subagent_type as string, deps.registry)
211
211
  : "Agent";
212
212
  const desc = (args.description as string) ?? "";
213
213
  return new Text(
@@ -327,17 +327,17 @@ Guidelines:
327
327
  deps.widget.setUICtx(ctx.ui as UICtx);
328
328
 
329
329
  // Reload custom agents so new .pi/agents/*.md files are picked up without restart
330
- deps.reloadCustomAgents();
330
+ deps.registry.reload();
331
331
 
332
332
  const rawType = params.subagent_type as SubagentType;
333
- const resolved = resolveType(rawType);
333
+ const resolved = deps.registry.resolveType(rawType);
334
334
  const subagentType = resolved ?? "general-purpose";
335
335
  const fellBack = resolved === undefined;
336
336
 
337
- const displayName = getDisplayName(subagentType);
337
+ const displayName = getDisplayName(subagentType, deps.registry);
338
338
 
339
339
  // Get agent config for invocation resolution
340
- const customConfig = resolveAgentConfig(subagentType);
340
+ const customConfig = deps.registry.resolveAgentConfig(subagentType);
341
341
 
342
342
  const resolvedConfig = resolveAgentInvocationConfig(customConfig, params);
343
343
 
@@ -375,7 +375,7 @@ Guidelines:
375
375
  runInBackground,
376
376
  isolation,
377
377
  };
378
- const modeLabel = getPromptModeLabel(subagentType);
378
+ const modeLabel = getPromptModeLabel(subagentType, deps.registry);
379
379
  const { tags: invocationTags } = buildInvocationTags(agentInvocation);
380
380
  const agentTags = modeLabel ? [modeLabel, ...invocationTags] : invocationTags;
381
381
  const detailBase = {
@@ -1,5 +1,6 @@
1
1
  import type { AgentSession } from "@earendil-works/pi-coding-agent";
2
2
  import { Type } from "@sinclair/typebox";
3
+ import type { AgentConfigLookup } from "../agent-types.js";
3
4
  import type { AgentRecord } from "../types.js";
4
5
  import { formatDuration, getDisplayName } from "../ui/agent-widget.js";
5
6
  import { getSessionContextPercent } from "../usage.js";
@@ -10,6 +11,7 @@ export interface GetResultDeps {
10
11
  getRecord: (id: string) => AgentRecord | undefined;
11
12
  cancelNudge: (key: string) => void;
12
13
  getConversation: (session: AgentSession) => string | undefined;
14
+ registry: AgentConfigLookup;
13
15
  }
14
16
 
15
17
  /** Create the get_subagent_result tool definition (without Pi SDK wrapper). */
@@ -57,7 +59,7 @@ export function createGetResultTool(deps: GetResultDeps) {
57
59
  await record.promise;
58
60
  }
59
61
 
60
- const displayName = getDisplayName(record.type);
62
+ const displayName = getDisplayName(record.type, deps.registry);
61
63
  const duration = formatDuration(record.startedAt, record.completedAt);
62
64
  const tokens = formatLifetimeTokens(record);
63
65
  const contextPercent = getSessionContextPercent(record.session);
package/src/types.ts CHANGED
@@ -12,9 +12,6 @@ export type { ThinkingLevel };
12
12
  /** Agent type: any string name (built-in defaults or user-defined). */
13
13
  export type SubagentType = string;
14
14
 
15
- /** Names of the three embedded default agents. */
16
- export const DEFAULT_AGENT_NAMES = ["general-purpose", "Explore", "Plan"] as const;
17
-
18
15
  /** Memory scope for persistent agent memory. */
19
16
  export type MemoryScope = "user" | "project" | "local";
20
17
 
@@ -4,10 +4,8 @@ import { join } from "node:path";
4
4
  import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
5
5
  import type { SpawnOptions } from "../agent-manager.js";
6
6
  import {
7
+ AgentTypeRegistry,
7
8
  BUILTIN_TOOL_NAMES,
8
- getAllTypes,
9
- resolveAgentConfig,
10
- resolveType,
11
9
  } from "../agent-types.js";
12
10
  import type { ModelRegistry } from "../model-resolver.js";
13
11
  import type { AgentConfig, AgentRecord } from "../types.js";
@@ -28,7 +26,7 @@ export interface AgentMenuManager {
28
26
 
29
27
  export interface AgentMenuDeps {
30
28
  manager: AgentMenuManager;
31
- reloadCustomAgents: () => void;
29
+ registry: AgentTypeRegistry;
32
30
  agentActivity: Map<string, AgentActivity>;
33
31
  /** Resolve model label for a given agent type + registry. */
34
32
  getModelLabel: (type: string, registry?: ModelRegistry) => string;
@@ -72,8 +70,8 @@ export function createAgentsMenuHandler(deps: AgentMenuDeps) {
72
70
  }
73
71
 
74
72
  async function showAgentsMenu(ctx: ExtensionContext) {
75
- deps.reloadCustomAgents();
76
- const allNames = getAllTypes();
73
+ deps.registry.reload();
74
+ const allNames = deps.registry.getAllTypes();
77
75
 
78
76
  const options: string[] = [];
79
77
 
@@ -126,7 +124,7 @@ export function createAgentsMenuHandler(deps: AgentMenuDeps) {
126
124
  }
127
125
 
128
126
  async function showAllAgentsList(ctx: ExtensionContext) {
129
- const allNames = getAllTypes();
127
+ const allNames = deps.registry.getAllTypes();
130
128
  if (allNames.length === 0) {
131
129
  ctx.ui.notify("No agents.", "info");
132
130
  return;
@@ -141,7 +139,7 @@ export function createAgentsMenuHandler(deps: AgentMenuDeps) {
141
139
  };
142
140
 
143
141
  const entries = allNames.map((name) => {
144
- const cfg = resolveAgentConfig(name);
142
+ const cfg = deps.registry.resolveAgentConfig(name);
145
143
  const disabled = cfg.enabled === false;
146
144
  const model = deps.getModelLabel(name, ctx.modelRegistry);
147
145
  const indicator = sourceIndicator(cfg);
@@ -152,10 +150,10 @@ export function createAgentsMenuHandler(deps: AgentMenuDeps) {
152
150
  const maxPrefix = Math.max(...entries.map((e) => e.prefix.length));
153
151
 
154
152
  const hasCustom = allNames.some((n) => {
155
- const c = resolveAgentConfig(n);
153
+ const c = deps.registry.resolveAgentConfig(n);
156
154
  return !c.isDefault && c.enabled !== false;
157
155
  });
158
- const hasDisabled = allNames.some((n) => resolveAgentConfig(n).enabled === false);
156
+ const hasDisabled = allNames.some((n) => deps.registry.resolveAgentConfig(n).enabled === false);
159
157
  const legendParts: string[] = [];
160
158
  if (hasCustom) legendParts.push("• = project ◦ = global");
161
159
  if (hasDisabled) legendParts.push("✕ = disabled");
@@ -173,7 +171,7 @@ export function createAgentsMenuHandler(deps: AgentMenuDeps) {
173
171
  .split(" · ")[0]
174
172
  .replace(/^[•◦✕\s]+/, "")
175
173
  .trim();
176
- if (resolveType(agentName) != null) {
174
+ if (deps.registry.resolveType(agentName) != null) {
177
175
  await showAgentDetail(ctx, agentName);
178
176
  await showAllAgentsList(ctx);
179
177
  }
@@ -187,7 +185,7 @@ export function createAgentsMenuHandler(deps: AgentMenuDeps) {
187
185
  }
188
186
 
189
187
  const options = agents.map((a) => {
190
- const dn = getDisplayName(a.type);
188
+ const dn = getDisplayName(a.type, deps.registry);
191
189
  const dur = formatDuration(a.startedAt, a.completedAt);
192
190
  return `${dn} (${a.description}) · ${a.toolUses} tools · ${a.status} · ${dur}`;
193
191
  });
@@ -220,7 +218,7 @@ export function createAgentsMenuHandler(deps: AgentMenuDeps) {
220
218
 
221
219
  await ctx.ui.custom<undefined>(
222
220
  (tui: any, theme: any, _keybindings: any, done: any) => {
223
- return new ConversationViewer(tui, session, record, activity, theme, done);
221
+ return new ConversationViewer(tui, session, record, activity, theme, done, deps.registry);
224
222
  },
225
223
  {
226
224
  overlay: true,
@@ -234,11 +232,11 @@ export function createAgentsMenuHandler(deps: AgentMenuDeps) {
234
232
  }
235
233
 
236
234
  async function showAgentDetail(ctx: ExtensionContext, name: string) {
237
- if (resolveType(name) == null) {
235
+ if (deps.registry.resolveType(name) == null) {
238
236
  ctx.ui.notify(`Agent config not found for "${name}".`, "warning");
239
237
  return;
240
238
  }
241
- const cfg = resolveAgentConfig(name);
239
+ const cfg = deps.registry.resolveAgentConfig(name);
242
240
 
243
241
  const file = findAgentFile(name);
244
242
  const isDefault = cfg.isDefault === true;
@@ -266,7 +264,7 @@ export function createAgentsMenuHandler(deps: AgentMenuDeps) {
266
264
  if (edited !== undefined && edited !== content) {
267
265
  const { writeFileSync } = await import("node:fs");
268
266
  writeFileSync(file.path, edited, "utf-8");
269
- deps.reloadCustomAgents();
267
+ deps.registry.reload();
270
268
  ctx.ui.notify(`Updated ${file.path}`, "info");
271
269
  }
272
270
  } else if (choice === "Delete") {
@@ -277,7 +275,7 @@ export function createAgentsMenuHandler(deps: AgentMenuDeps) {
277
275
  );
278
276
  if (confirmed) {
279
277
  unlinkSync(file.path);
280
- deps.reloadCustomAgents();
278
+ deps.registry.reload();
281
279
  ctx.ui.notify(`Deleted ${file.path}`, "info");
282
280
  }
283
281
  }
@@ -288,7 +286,7 @@ export function createAgentsMenuHandler(deps: AgentMenuDeps) {
288
286
  );
289
287
  if (confirmed) {
290
288
  unlinkSync(file.path);
291
- deps.reloadCustomAgents();
289
+ deps.registry.reload();
292
290
  ctx.ui.notify(`Restored default ${name}`, "info");
293
291
  }
294
292
  } else if (choice.startsWith("Eject")) {
@@ -347,7 +345,7 @@ export function createAgentsMenuHandler(deps: AgentMenuDeps) {
347
345
 
348
346
  const { writeFileSync } = await import("node:fs");
349
347
  writeFileSync(targetPath, content, "utf-8");
350
- deps.reloadCustomAgents();
348
+ deps.registry.reload();
351
349
  ctx.ui.notify(`Ejected ${name} to ${targetPath}`, "info");
352
350
  }
353
351
 
@@ -362,7 +360,7 @@ export function createAgentsMenuHandler(deps: AgentMenuDeps) {
362
360
  const updated = content.replace(/^---\n/, "---\nenabled: false\n");
363
361
  const { writeFileSync } = await import("node:fs");
364
362
  writeFileSync(file.path, updated, "utf-8");
365
- deps.reloadCustomAgents();
363
+ deps.registry.reload();
366
364
  ctx.ui.notify(`Disabled ${name} (${file.path})`, "info");
367
365
  return;
368
366
  }
@@ -381,7 +379,7 @@ export function createAgentsMenuHandler(deps: AgentMenuDeps) {
381
379
  const targetPath = join(targetDir, `${name}.md`);
382
380
  const { writeFileSync } = await import("node:fs");
383
381
  writeFileSync(targetPath, "---\nenabled: false\n---\n", "utf-8");
384
- deps.reloadCustomAgents();
382
+ deps.registry.reload();
385
383
  ctx.ui.notify(`Disabled ${name} (${targetPath})`, "info");
386
384
  }
387
385
 
@@ -395,11 +393,11 @@ export function createAgentsMenuHandler(deps: AgentMenuDeps) {
395
393
 
396
394
  if (updated.trim() === "---\n---" || updated.trim() === "---\n---\n") {
397
395
  unlinkSync(file.path);
398
- deps.reloadCustomAgents();
396
+ deps.registry.reload();
399
397
  ctx.ui.notify(`Enabled ${name} (removed ${file.path})`, "info");
400
398
  } else {
401
399
  writeFileSync(file.path, updated, "utf-8");
402
- deps.reloadCustomAgents();
400
+ deps.registry.reload();
403
401
  ctx.ui.notify(`Enabled ${name} (${file.path})`, "info");
404
402
  }
405
403
  }
@@ -501,7 +499,7 @@ Write the file using the write tool. Only write the file, nothing else.`;
501
499
  return;
502
500
  }
503
501
 
504
- deps.reloadCustomAgents();
502
+ deps.registry.reload();
505
503
 
506
504
  if (existsSync(targetPath)) {
507
505
  ctx.ui.notify(`Created ${targetPath}`, "info");
@@ -604,7 +602,7 @@ ${systemPrompt}
604
602
 
605
603
  const { writeFileSync } = await import("node:fs");
606
604
  writeFileSync(targetPath, content, "utf-8");
607
- deps.reloadCustomAgents();
605
+ deps.registry.reload();
608
606
  ctx.ui.notify(`Created ${targetPath}`, "info");
609
607
  }
610
608
 
@@ -7,7 +7,7 @@
7
7
 
8
8
  import { truncateToWidth } from "@earendil-works/pi-tui";
9
9
  import type { AgentManager } from "../agent-manager.js";
10
- import { resolveAgentConfig } from "../agent-types.js";
10
+ import { type AgentConfigLookup, AgentTypeRegistry } from "../agent-types.js";
11
11
  import type { AgentInvocation, SubagentType } from "../types.js";
12
12
  import { getLifetimeTotal, getSessionContextPercent, type LifetimeUsage, type SessionLike } from "../usage.js";
13
13
 
@@ -143,14 +143,14 @@ export function formatDuration(startedAt: number, completedAt?: number): string
143
143
  }
144
144
 
145
145
  /** Get display name for any agent type (built-in or custom). */
146
- export function getDisplayName(type: SubagentType): string {
147
- const config = resolveAgentConfig(type);
146
+ export function getDisplayName(type: SubagentType, registry: AgentConfigLookup): string {
147
+ const config = registry.resolveAgentConfig(type);
148
148
  return config.displayName ?? config.name;
149
149
  }
150
150
 
151
151
  /** Short label for prompt mode: "twin" for append, nothing for replace (the default). */
152
- export function getPromptModeLabel(type: SubagentType): string | undefined {
153
- const config = resolveAgentConfig(type);
152
+ export function getPromptModeLabel(type: SubagentType, registry: AgentConfigLookup): string | undefined {
153
+ const config = registry.resolveAgentConfig(type);
154
154
  return config.promptMode === "append" ? "twin" : undefined;
155
155
  }
156
156
 
@@ -225,6 +225,7 @@ export class AgentWidget {
225
225
  constructor(
226
226
  private manager: AgentManager,
227
227
  private agentActivity: Map<string, AgentActivity>,
228
+ private registry: AgentTypeRegistry,
228
229
  ) {}
229
230
 
230
231
  /** Set the UI context (grabbed from first tool execution). */
@@ -275,8 +276,8 @@ export class AgentWidget {
275
276
 
276
277
  /** Render a finished agent line. */
277
278
  private renderFinishedLine(a: { id: string; type: SubagentType; status: string; description: string; toolUses: number; startedAt: number; completedAt?: number; error?: string }, theme: Theme): string {
278
- const name = getDisplayName(a.type);
279
- const modeLabel = getPromptModeLabel(a.type);
279
+ const name = getDisplayName(a.type, this.registry);
280
+ const modeLabel = getPromptModeLabel(a.type, this.registry);
280
281
  const duration = formatMs((a.completedAt ?? Date.now()) - a.startedAt);
281
282
 
282
283
  let icon: string;
@@ -345,8 +346,8 @@ export class AgentWidget {
345
346
 
346
347
  const runningLines: string[][] = []; // each entry is [header, activity]
347
348
  for (const a of running) {
348
- const name = getDisplayName(a.type);
349
- const modeLabel = getPromptModeLabel(a.type);
349
+ const name = getDisplayName(a.type, this.registry);
350
+ const modeLabel = getPromptModeLabel(a.type, this.registry);
350
351
  const modeTag = modeLabel ? ` ${theme.fg("dim", `(${modeLabel})`)}` : "";
351
352
  const elapsed = formatMs(Date.now() - a.startedAt);
352
353
 
@@ -7,6 +7,7 @@
7
7
 
8
8
  import type { AgentSession } from "@earendil-works/pi-coding-agent";
9
9
  import { type Component, matchesKey, type TUI, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
10
+ import type { AgentConfigLookup } from "../agent-types.js";
10
11
  import { extractText } from "../context.js";
11
12
  import type { AgentRecord } from "../types.js";
12
13
  import { getLifetimeTotal, getSessionContextPercent } from "../usage.js";
@@ -33,6 +34,7 @@ export class ConversationViewer implements Component {
33
34
  private activity: AgentActivity | undefined,
34
35
  private theme: Theme,
35
36
  private done: (result: undefined) => void,
37
+ private registry: AgentConfigLookup,
36
38
  ) {
37
39
  this.unsubscribe = session.subscribe(() => {
38
40
  if (this.closed) return;
@@ -91,8 +93,8 @@ export class ConversationViewer implements Component {
91
93
 
92
94
  // Header
93
95
  lines.push(hrTop);
94
- const name = getDisplayName(this.record.type);
95
- const modeLabel = getPromptModeLabel(this.record.type);
96
+ const name = getDisplayName(this.record.type, this.registry);
97
+ const modeLabel = getPromptModeLabel(this.record.type, this.registry);
96
98
  const modeTag = modeLabel ? ` ${th.fg("dim", `(${modeLabel})`)}` : "";
97
99
  const statusIcon = this.record.status === "running"
98
100
  ? th.fg("accent", "●")