@ryan_nookpi/pi-extension-subagent 0.4.0 → 0.4.2

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/commands.ts CHANGED
@@ -63,7 +63,12 @@ import { buildMainContextText, makeSubagentSessionFile, wrapTaskWithMainContext
63
63
  import { formatStarterPackNotice, offerStarterPackIfEmpty } from "./starter-pack.js";
64
64
  import { type SubagentStore, truncateText, updateRunFromResult } from "./store.js";
65
65
  import { createSubagentToolExecute } from "./tool-execute.js";
66
- import { renderSubagentToolCall, renderSubagentToolResult } from "./tool-render.js";
66
+ import {
67
+ renderListAgentsCall,
68
+ renderListAgentsResult,
69
+ renderSubagentToolCall,
70
+ renderSubagentToolResult,
71
+ } from "./tool-render.js";
67
72
  import type { CommandRunState, SingleResult, SubagentDetails } from "./types.js";
68
73
  import { ListAgentsParams, SubagentParams } from "./types.js";
69
74
  import { updateCommandRunsWidget, type WidgetRenderCtx } from "./widget.js";
@@ -927,6 +932,8 @@ export function registerAll(pi: ExtensionAPI, store: SubagentStore): SubagentReg
927
932
  description:
928
933
  "List available subagent definitions (name, source, model, thinking, tools, description). Useful before planning delegation.",
929
934
  parameters: ListAgentsParams,
935
+ renderCall: renderListAgentsCall,
936
+ renderResult: renderListAgentsResult,
930
937
  execute: async (_toolCallId, _params: Record<string, any>, _signal, _onUpdate, ctx) => {
931
938
  const starterPack = await offerStarterPackIfEmpty(ctx);
932
939
  const discovery = starterPack.discovery;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ryan_nookpi/pi-extension-subagent",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "Asynchronous subagent delegation for pi with run, batch, chain, and continuation workflows.",
5
5
  "license": "MIT",
6
6
  "repository": {
package/tool-render.ts CHANGED
@@ -5,10 +5,11 @@
5
5
  */
6
6
 
7
7
  import { getMarkdownTheme, type ThemeColor } from "@earendil-works/pi-coding-agent";
8
- import { Container, Markdown, Spacer, Text } from "@earendil-works/pi-tui";
8
+ import { Container, Markdown, Spacer, Text, visibleWidth } from "@earendil-works/pi-tui";
9
+ import { parseSubagentToolCommand } from "./cli.js";
9
10
  import { formatToolCall, formatUsageStats } from "./format.js";
10
11
  import { getDisplayItems, getFinalOutput } from "./runner.js";
11
- import { COLLAPSED_ITEM_COUNT } from "./store.js";
12
+ import { COLLAPSED_ITEM_COUNT, truncateText } from "./store.js";
12
13
  import type { DisplayItem, SubagentDetails } from "./types.js";
13
14
 
14
15
  // ─── Helpers (internal) ──────────────────────────────────────────────────────
@@ -24,6 +25,97 @@ type ToolRenderResult = {
24
25
  };
25
26
 
26
27
  type ToolRenderArgs = { command?: unknown };
28
+ type ToolCallRenderContext = { expanded: boolean };
29
+ type CompactBlock = { agent: string; task: string };
30
+ type ListAgentsDetails = { agents?: Array<{ name?: unknown }> };
31
+
32
+ const COMPACT_TASK_PREVIEW_WIDTH = 72;
33
+ const COMPACT_AGENT_WIDTH = 20;
34
+ const COMPACT_MAX_ITEMS = 3;
35
+ const COMPACT_AGENT_LIST_COUNT = 3;
36
+
37
+ function normalizeTaskPreview(task: string): string {
38
+ return truncateText(task.replace(/\s+/g, " ").trim(), COMPACT_TASK_PREVIEW_WIDTH);
39
+ }
40
+
41
+ function getContextLabel(params: Record<string, unknown>): "main" | "isolated" {
42
+ return params.contextMode === "main" ? "main" : "isolated";
43
+ }
44
+
45
+ function getCompactBlocks(value: unknown): CompactBlock[] | null {
46
+ if (!Array.isArray(value)) return null;
47
+ const blocks: CompactBlock[] = [];
48
+ for (const item of value) {
49
+ if (!item || typeof item !== "object") return null;
50
+ const agent = "agent" in item ? item.agent : undefined;
51
+ const task = "task" in item ? item.task : undefined;
52
+ if (typeof agent !== "string" || typeof task !== "string") return null;
53
+ blocks.push({ agent, task });
54
+ }
55
+ return blocks;
56
+ }
57
+
58
+ function formatCompactAgent(agent: string, width: number): string {
59
+ const label = truncateText(agent, COMPACT_AGENT_WIDTH);
60
+ return `${label}${" ".repeat(Math.max(0, width - visibleWidth(label)))}`;
61
+ }
62
+
63
+ function renderCompactBlocks(kind: "batch" | "chain", params: Record<string, unknown>): string | null {
64
+ const blocks = getCompactBlocks(kind === "batch" ? params.runs : params.steps);
65
+ if (!blocks || blocks.length === 0) return null;
66
+
67
+ const visibleBlocks = blocks.slice(0, COMPACT_MAX_ITEMS);
68
+ const agentWidth = Math.max(
69
+ ...visibleBlocks.map((block) => visibleWidth(truncateText(block.agent, COMPACT_AGENT_WIDTH))),
70
+ );
71
+ const contextLabel = getContextLabel(params);
72
+ const executionLabel = kind === "batch" ? "parallel" : "sequential";
73
+ const lines = [` ${kind} · ${contextLabel} · ${blocks.length} ${executionLabel}`];
74
+
75
+ for (const [index, block] of visibleBlocks.entries()) {
76
+ const agent = formatCompactAgent(block.agent, agentWidth);
77
+ const task = normalizeTaskPreview(block.task);
78
+ if (kind === "batch") {
79
+ lines.push(` ∥ ${agent} ${task}`);
80
+ continue;
81
+ }
82
+
83
+ const connector = index === 0 ? "" : `${" ".repeat(index - 1)}└─ `;
84
+ lines.push(` ${connector}${index + 1} ${agent} ${task}`);
85
+ }
86
+
87
+ const hiddenCount = blocks.length - visibleBlocks.length;
88
+ if (hiddenCount > 0) {
89
+ if (kind === "batch") lines.push(` ∥ +${hiddenCount} jobs`);
90
+ else lines.push(` ${" ".repeat(visibleBlocks.length - 1)}└─ +${hiddenCount} steps`);
91
+ }
92
+
93
+ return lines.join("\n");
94
+ }
95
+
96
+ function renderCompactLaunch(command: unknown): string | null {
97
+ const parsed = parseSubagentToolCommand(command);
98
+ if (parsed.type !== "params") return null;
99
+
100
+ const { params } = parsed;
101
+ if (params.asyncAction === "batch") return renderCompactBlocks("batch", params);
102
+ if (params.asyncAction === "chain") return renderCompactBlocks("chain", params);
103
+ if (typeof params.task !== "string") return null;
104
+
105
+ const contextLabel = getContextLabel(params);
106
+ const task = normalizeTaskPreview(params.task);
107
+ if (typeof params.runId === "number") {
108
+ const agent = typeof params.agent === "string" ? ` · ${params.agent}` : "";
109
+ return ` continue · #${params.runId}${agent} · ${contextLabel}\n ${task}`;
110
+ }
111
+ if (typeof params.agent !== "string") return null;
112
+ return ` run · ${params.agent} · ${contextLabel}\n ${task}`;
113
+ }
114
+
115
+ function getToolResultText(result: ToolRenderResult): string {
116
+ const raw = result.content[0];
117
+ return (raw?.type === "text" ? raw.text : undefined) ?? "(no output)";
118
+ }
27
119
 
28
120
  function renderDisplayItems(items: DisplayItem[], expanded: boolean, theme: RenderTheme, limit?: number): string {
29
121
  const toShow = limit ? items.slice(-limit) : items;
@@ -43,15 +135,57 @@ function renderDisplayItems(items: DisplayItem[], expanded: boolean, theme: Rend
43
135
 
44
136
  // ─── renderCall ──────────────────────────────────────────────────────────────
45
137
 
46
- export function renderSubagentToolCall(args: ToolRenderArgs, theme: RenderTheme) {
138
+ export function renderListAgentsCall(_args: unknown, theme: RenderTheme) {
139
+ return new Text(theme.fg("toolTitle", theme.bold("list-agents")), 0, 0);
140
+ }
141
+
142
+ export function renderListAgentsResult(
143
+ result: ToolRenderResult,
144
+ { expanded }: { expanded: boolean },
145
+ theme: RenderTheme,
146
+ ) {
147
+ const fullText = getToolResultText(result);
148
+ if (expanded) return new Text(theme.fg("toolOutput", fullText), 0, 0);
149
+
150
+ const details = result.details as ListAgentsDetails | undefined;
151
+ const names = details?.agents
152
+ ?.map((agent) => (typeof agent.name === "string" ? truncateText(agent.name, COMPACT_AGENT_WIDTH) : ""))
153
+ .filter(Boolean);
154
+ if (!names) return new Text(theme.fg("toolOutput", fullText.split("\n")[0] ?? fullText), 0, 0);
155
+ if (names.length === 0) return new Text(theme.fg("muted", "○ no agents"), 0, 0);
156
+
157
+ const visibleNames = names.slice(0, COMPACT_AGENT_LIST_COUNT);
158
+ const hiddenCount = names.length - visibleNames.length;
159
+ const hiddenSuffix = hiddenCount > 0 ? `, +${hiddenCount}` : "";
160
+ return new Text(
161
+ theme.fg(
162
+ "toolOutput",
163
+ `✓ ${names.length} agent${names.length === 1 ? "" : "s"} · ${visibleNames.join(", ")}${hiddenSuffix}`,
164
+ ),
165
+ 0,
166
+ 0,
167
+ );
168
+ }
169
+
170
+ export function renderSubagentToolCall(
171
+ args: ToolRenderArgs,
172
+ theme: RenderTheme,
173
+ context: ToolCallRenderContext = { expanded: false },
174
+ ) {
47
175
  const raw = typeof args.command === "string" ? args.command.trim() : "";
48
176
  const command = raw || "subagent help";
177
+ let text = theme.fg("toolTitle", theme.bold("subagent ")) + theme.fg("accent", "cli");
178
+
179
+ if (!context.expanded) {
180
+ const compactLaunch = renderCompactLaunch(args.command);
181
+ if (compactLaunch) return new Text(`${text}\n${theme.fg("dim", compactLaunch)}`, 0, 0);
182
+ }
183
+
49
184
  const MAX_CALL_LINES = 5;
50
185
  const commandLines = command.split("\n");
51
186
  const truncated = commandLines.length > MAX_CALL_LINES;
52
187
  const preview = truncated ? commandLines.slice(0, MAX_CALL_LINES).join("\n") : command;
53
188
 
54
- let text = theme.fg("toolTitle", theme.bold("subagent ")) + theme.fg("accent", "cli");
55
189
  text += `\n ${theme.fg("dim", preview)}`;
56
190
  if (truncated) text += `\n ${theme.fg("muted", `... +${commandLines.length - MAX_CALL_LINES} more lines`)}`;
57
191
  return new Text(text, 0, 0);