@yandy0725/pi-subagents 0.2.1 → 0.2.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/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.2.1",
6
+ "version": "0.2.2",
7
7
  "description": "Focused, in-process sub-agent core for pi — autonomous agents plus a typed API and lifecycle events",
8
8
  "license": "MIT",
9
9
  "repository": {
@@ -35,10 +35,8 @@
35
35
  "peerDependencies": {
36
36
  "@earendil-works/pi-ai": ">=0.80.2",
37
37
  "@earendil-works/pi-coding-agent": ">=0.80.2",
38
- "@earendil-works/pi-tui": ">=0.80.2"
39
- },
40
- "dependencies": {
41
- "@sinclair/typebox": "^0.34.49"
38
+ "@earendil-works/pi-tui": ">=0.80.2",
39
+ "@sinclair/typebox": "*"
42
40
  },
43
41
  "devDependencies": {
44
42
  "@biomejs/biome": "^2.5.0",
@@ -134,7 +134,7 @@ export class SubagentManager {
134
134
  this.observer?.onSubagentStarted(agent);
135
135
  },
136
136
  onSessionCreated: options.observer?.onSessionCreated
137
- ? (agent) => options.observer!.onSessionCreated!(agent)
137
+ ? (agent) => options.observer?.onSessionCreated?.(agent)
138
138
  : undefined,
139
139
  onRunFinished: (agent) => {
140
140
  if (options.isBackground) {
@@ -122,7 +122,7 @@ export class SubagentState {
122
122
 
123
123
  /** Record a tool starting. Called by record-observer on tool_execution_start. */
124
124
  addActiveTool(toolName: string): void {
125
- this._activeTools.set(toolName + "_" + ++this._toolKeySeq, toolName);
125
+ this._activeTools.set(`${toolName}_${++this._toolKeySeq}`, toolName);
126
126
  }
127
127
 
128
128
  /** Remove one active tool by name (first match). Called by record-observer on tool_execution_end. */
@@ -53,7 +53,7 @@ export function formatTaskNotification(record: Subagent, resultMaxLen: number):
53
53
 
54
54
  const resultPreview = record.result
55
55
  ? record.result.length > resultMaxLen
56
- ? record.result.slice(0, resultMaxLen) + "\n...(truncated, use get_subagent_result for full output)"
56
+ ? `${record.result.slice(0, resultMaxLen)}\n...(truncated, use get_subagent_result for full output)`
57
57
  : record.result
58
58
  : "No output.";
59
59
 
@@ -92,7 +92,7 @@ export function buildNotificationDetails(record: Subagent, resultMaxLen: number)
92
92
  modelName: record.modelName,
93
93
  resultPreview: record.result
94
94
  ? record.result.length > resultMaxLen
95
- ? record.result.slice(0, resultMaxLen) + "…"
95
+ ? `${record.result.slice(0, resultMaxLen)}…`
96
96
  : record.result
97
97
  : "No output.",
98
98
  };
@@ -42,21 +42,21 @@ export function createNotificationRenderer() {
42
42
  if (d.totalTokens > 0) parts.push(formatTokens(d.totalTokens));
43
43
  if (d.durationMs > 0) parts.push(formatMs(d.durationMs));
44
44
  if (parts.length) {
45
- line += "\n " + parts.map((p) => theme.fg("dim", p)).join(" " + theme.fg("dim", "·") + " ");
45
+ line += `\n ${parts.map((p) => theme.fg("dim", p)).join(` ${theme.fg("dim", "·")} `)}`;
46
46
  }
47
47
 
48
48
  // Line 3: result preview (collapsed) or full (expanded)
49
49
  if (expanded) {
50
50
  const lines = d.resultPreview.split("\n").slice(0, 30);
51
- for (const l of lines) line += "\n" + theme.fg("dim", ` ${l}`);
51
+ for (const l of lines) line += `\n${theme.fg("dim", ` ${l}`)}`;
52
52
  } else {
53
53
  const preview = d.resultPreview.split("\n")[0]?.slice(0, 80) ?? "";
54
- line += "\n " + theme.fg("dim", `⎿ ${preview}`);
54
+ line += `\n ${theme.fg("dim", `⎿ ${preview}`)}`;
55
55
  }
56
56
 
57
57
  // Line 4: output file link (if present)
58
58
  if (d.outputFile) {
59
- line += "\n " + theme.fg("muted", `transcript: ${d.outputFile}`);
59
+ line += `\n ${theme.fg("muted", `transcript: ${d.outputFile}`)}`;
60
60
  }
61
61
 
62
62
  return new Text(line, 0, 0);
@@ -46,7 +46,6 @@ export class SubagentsServiceAdapter implements SubagentsService {
46
46
  }
47
47
 
48
48
  let model: unknown;
49
- let modelName: string | undefined;
50
49
  if (options?.model) {
51
50
  const registry = this.runtime.currentCtx.modelRegistry;
52
51
  if (!registry) {
@@ -59,9 +58,9 @@ export class SubagentsServiceAdapter implements SubagentsService {
59
58
  model = resolved;
60
59
  }
61
60
  // Always compute display model name, even when same as parent
62
- modelName = resolveModelName(
63
- (model as { id?: string; name?: string } | undefined) ??
64
- (this.runtime.currentCtx.model as { id?: string; name?: string } | undefined),
61
+ const modelName = resolveModelName(
62
+ (model as { id?: string; name?: string; provider?: string } | undefined) ??
63
+ (this.runtime.currentCtx.model as { id?: string; name?: string; provider?: string } | undefined),
65
64
  );
66
65
 
67
66
  const description = options?.description ?? prompt.slice(0, 80);
@@ -27,7 +27,7 @@ export function getAgentConversation(session: AgentSession): string {
27
27
  if (toolNames.length > 0) parts.push(`[Tool Calls]:\n${toolNames.map((n) => ` Tool: ${n}`).join("\n")}`);
28
28
  } else if (msg.role === "toolResult") {
29
29
  const text = extractText(msg.content);
30
- const truncated = text.length > 200 ? text.slice(0, 200) + "..." : text;
30
+ const truncated = text.length > 200 ? `${text.slice(0, 200)}...` : text;
31
31
  parts.push(`[Tool Result (${msg.toolName})]: ${truncated}`);
32
32
  }
33
33
  }
@@ -66,14 +66,14 @@ You are operating as a sub-agent invoked to handle a specific task.
66
66
  // placed verbatim (no wrapper tag) so it forms an identical byte prefix
67
67
  // with the parent session, maximising KV cache hits. The <active_agent>
68
68
  // tag and env block vary per call and are placed after the cached prefix.
69
- return identity + "\n\n" + bridge + "\n\n" + activeAgentTag + envBlock + customSection;
69
+ return `${identity}\n\n${bridge}\n\n${activeAgentTag}${envBlock}${customSection}`;
70
70
  }
71
71
 
72
72
  // "replace" mode — parent/genericBase prefix first for KV cache reuse, then
73
73
  // the active_agent tag, env block, and the config's full system prompt.
74
74
  // Unlike append mode, no <sub_agent_context> bridge or <agent_instructions>
75
75
  // wrapper is injected — the custom prompt retains full control.
76
- return identity + "\n\n" + activeAgentTag + envBlock + "\n\n" + config.systemPrompt;
76
+ return `${identity}\n\n${activeAgentTag}${envBlock}\n\n${config.systemPrompt}`;
77
77
  }
78
78
 
79
79
  /** Fallback base prompt when parent system prompt is unavailable (both modes). */
@@ -192,7 +192,7 @@ Guidelines:
192
192
  const displayName = args.subagent_type ? getDisplayName(args.subagent_type as string, registry) : "Subagent";
193
193
  const desc = (args.description as string | undefined) ?? "";
194
194
  return new Text(
195
- "▸ " + theme.fg("toolTitle", theme.bold(displayName)) + (desc ? " " + theme.fg("muted", desc) : ""),
195
+ `▸ ${theme.fg("toolTitle", theme.bold(displayName))}${desc ? ` ${theme.fg("muted", desc)}` : ""}`,
196
196
  0,
197
197
  0,
198
198
  );
@@ -33,8 +33,8 @@ export function renderAgentResult(
33
33
  export function renderRunning(details: AgentDetails, theme: Theme): string {
34
34
  const frame = SPINNER[details.spinnerFrame ?? 0];
35
35
  const s = renderStats(details, theme);
36
- let line = theme.fg("accent", frame) + (s ? " " + s : "");
37
- line += "\n" + theme.fg("dim", ` ⎿ ${details.activity ?? "thinking\u2026"}`);
36
+ let line = theme.fg("accent", frame) + (s ? ` ${s}` : "");
37
+ line += `\n${theme.fg("dim", ` ⎿ ${details.activity ?? "thinking\u2026"}`)}`;
38
38
  return line;
39
39
  }
40
40
 
@@ -49,22 +49,22 @@ export function renderCompleted(details: AgentDetails, resultText: string, expan
49
49
  const isSteered = details.status === "steered";
50
50
  const icon = isSteered ? theme.fg("warning", "\u2713") : theme.fg("success", "\u2713");
51
51
  const s = renderStats(details, theme);
52
- let line = icon + (s ? " " + s : "");
53
- line += " " + theme.fg("dim", "\u00B7") + " " + theme.fg("dim", duration);
52
+ let line = icon + (s ? ` ${s}` : "");
53
+ line += ` ${theme.fg("dim", "\u00B7")} ${theme.fg("dim", duration)}`;
54
54
 
55
55
  if (expanded) {
56
56
  if (resultText) {
57
57
  const lines = resultText.split("\n").slice(0, 50);
58
58
  for (const l of lines) {
59
- line += "\n" + theme.fg("dim", ` ${l}`);
59
+ line += `\n${theme.fg("dim", ` ${l}`)}`;
60
60
  }
61
61
  if (resultText.split("\n").length > 50) {
62
- line += "\n" + theme.fg("muted", " ... (use get_subagent_result with verbose for full output)");
62
+ line += `\n${theme.fg("muted", " ... (use get_subagent_result with verbose for full output)")}`;
63
63
  }
64
64
  }
65
65
  } else {
66
66
  const doneText = isSteered ? "Wrapped up (turn limit)" : "Done";
67
- line += "\n" + theme.fg("dim", ` \u23BF ${doneText}`);
67
+ line += `\n${theme.fg("dim", ` \u23BF ${doneText}`)}`;
68
68
  }
69
69
  return line;
70
70
  }
@@ -72,20 +72,20 @@ export function renderCompleted(details: AgentDetails, resultText: string, expan
72
72
  /** Render stopped status: dim stop icon + stats + "Stopped". */
73
73
  export function renderStopped(details: AgentDetails, theme: Theme): string {
74
74
  const s = renderStats(details, theme);
75
- let line = theme.fg("dim", "\u25A0") + (s ? " " + s : "");
76
- line += "\n" + theme.fg("dim", " \u23BF Stopped");
75
+ let line = theme.fg("dim", "\u25A0") + (s ? ` ${s}` : "");
76
+ line += `\n${theme.fg("dim", " \u23BF Stopped")}`;
77
77
  return line;
78
78
  }
79
79
 
80
80
  /** Render error or aborted status: error icon + stats + status message. */
81
81
  export function renderFailed(details: AgentDetails, theme: Theme): string {
82
82
  const s = renderStats(details, theme);
83
- let line = theme.fg("error", "\u2717") + (s ? " " + s : "");
83
+ let line = theme.fg("error", "\u2717") + (s ? ` ${s}` : "");
84
84
 
85
85
  if (details.status === "error") {
86
- line += "\n" + theme.fg("error", ` \u23BF Error: ${details.error ?? "unknown"}`);
86
+ line += `\n${theme.fg("error", ` \u23BF Error: ${details.error ?? "unknown"}`)}`;
87
87
  } else {
88
- line += "\n" + theme.fg("warning", " \u23BF Aborted (max turns exceeded)");
88
+ line += `\n${theme.fg("warning", " \u23BF Aborted (max turns exceeded)")}`;
89
89
  }
90
90
  return line;
91
91
  }
@@ -105,5 +105,5 @@ export function renderStats(details: AgentDetails, theme: Theme): string {
105
105
  }
106
106
  if (details.toolUses > 0) parts.push(`${details.toolUses} tool use${details.toolUses === 1 ? "" : "s"}`);
107
107
  if (details.tokens) parts.push(details.tokens);
108
- return parts.map((p) => theme.fg("dim", p)).join(" " + theme.fg("dim", "\u00B7") + " ");
108
+ return parts.map((p) => theme.fg("dim", p)).join(` ${theme.fg("dim", "\u00B7")} `);
109
109
  }
@@ -16,16 +16,19 @@ import type { AgentInvocation, SubagentType, ThinkingLevel } from "../types";
16
16
  import { type AgentDetails, buildInvocationTags, getDisplayName, getPromptModeLabel } from "../ui/display";
17
17
 
18
18
  /** Derive a short display model name from a model object (or undefined). */
19
- export function resolveModelName(model: { id?: string; name?: string } | undefined): string | undefined {
19
+ export function resolveModelName(
20
+ model: { id?: string; name?: string; provider?: string } | undefined,
21
+ ): string | undefined {
20
22
  if (!model) return undefined;
21
- const raw = model.name ?? model.id;
22
- if (!raw) return undefined;
23
- return raw.replace(/^Claude\s+/i, "").toLowerCase();
23
+ // Primary: provider/id complete format
24
+ if (model.provider && model.id) return `${model.provider}/${model.id}`;
25
+ // Fallback: raw name or id, no transformation
26
+ return model.name ?? model.id ?? undefined;
24
27
  }
25
28
 
26
29
  /** Model info extracted from the parent session context. */
27
30
  export interface ModelInfo {
28
- parentModel: { id: string; name?: string } | undefined;
31
+ parentModel: { id: string; name?: string; provider?: string } | undefined;
29
32
  modelRegistry: unknown;
30
33
  }
31
34
 
@@ -203,12 +203,12 @@ export class AgentWidget implements SubagentManagerObserver {
203
203
  */
204
204
  private clearWidget(backgroundAgents: readonly AgentSummary[]): void {
205
205
  if (this.widgetRegistered) {
206
- this.uiCtx!.setWidget("agents", undefined);
206
+ this.uiCtx?.setWidget("agents", undefined);
207
207
  this.widgetRegistered = false;
208
208
  this.tui = undefined;
209
209
  }
210
210
  if (this.lastStatusText !== undefined) {
211
- this.uiCtx!.setStatus("subagents", undefined);
211
+ this.uiCtx?.setStatus("subagents", undefined);
212
212
  this.lastStatusText = undefined;
213
213
  }
214
214
  if (this.widgetInterval) {
@@ -234,7 +234,7 @@ export class AgentWidget implements SubagentManagerObserver {
234
234
  newStatusText = `${statusParts.join(", ")} agent${total === 1 ? "" : "s"}`;
235
235
  }
236
236
  if (newStatusText !== this.lastStatusText) {
237
- this.uiCtx!.setStatus("subagents", newStatusText);
237
+ this.uiCtx?.setStatus("subagents", newStatusText);
238
238
  this.lastStatusText = newStatusText;
239
239
  }
240
240
  }
package/src/ui/display.ts CHANGED
@@ -142,7 +142,7 @@ function truncateLine(text: string, len = 60): string {
142
142
  .find((l) => l.trim())
143
143
  ?.trim() ?? "";
144
144
  if (line.length <= len) return line;
145
- return line.slice(0, len) + "…";
145
+ return `${line.slice(0, len)}…`;
146
146
  }
147
147
 
148
148
  /** Build a human-readable activity string from currently-running tools or response text. */
@@ -162,7 +162,7 @@ export function describeActivity(activeTools: ReadonlyMap<string, string>, respo
162
162
  parts.push(action);
163
163
  }
164
164
  }
165
- return parts.join(", ") + "…";
165
+ return `${parts.join(", ")}…`;
166
166
  }
167
167
 
168
168
  // No tools active — show truncated response text if available
@@ -270,7 +270,7 @@ export class TranscriptOverlay implements Component {
270
270
 
271
271
  const pad = (s: string, len: number): string => s + " ".repeat(Math.max(0, len - visibleWidth(s)));
272
272
  const row = (content: string): string =>
273
- th.fg("border", "│") + " " + truncateToWidth(pad(content, innerW), innerW) + " " + th.fg("border", "│");
273
+ `${th.fg("border", "│")} ${truncateToWidth(pad(content, innerW), innerW)} ${th.fg("border", "│")}`;
274
274
  const hrTop = th.fg("border", `╭${"─".repeat(width - 2)}╮`);
275
275
  const hrBot = th.fg("border", `╰${"─".repeat(width - 2)}╯`);
276
276
  const hrMid = row(th.fg("dim", "─".repeat(innerW)));
@@ -159,14 +159,14 @@ function buildSections(
159
159
  ): WidgetSections {
160
160
  const finishedLines: string[] = [];
161
161
  for (const a of categories.finished) {
162
- finishedLines.push(truncate(theme.fg("dim", "\u251C\u2500") + " " + renderFinishedLine(a, registry, theme)));
162
+ finishedLines.push(truncate(`${theme.fg("dim", "\u251C\u2500")} ${renderFinishedLine(a, registry, theme)}`));
163
163
  }
164
164
 
165
165
  const runningLines: [string, string][] = [];
166
166
  for (const a of categories.running) {
167
167
  const [header, act] = renderRunningLines(a, registry, spinnerFrame, theme);
168
168
  runningLines.push([
169
- truncate(theme.fg("dim", "\u251C\u2500") + ` ${header}`),
169
+ truncate(`${theme.fg("dim", "\u251C\u2500")} ${header}`),
170
170
  truncate(theme.fg("dim", "\u2502 ") + act),
171
171
  ]);
172
172
  }
@@ -252,7 +252,7 @@ function assembleOverflow(
252
252
  const overflowText = overflowParts.join(", ");
253
253
  lines.push(
254
254
  truncate(
255
- theme.fg("dim", "\u2514\u2500") + ` ${theme.fg("dim", `+${hiddenRunning + hiddenFinished} more (${overflowText})`)}`,
255
+ `${theme.fg("dim", "\u2514\u2500")} ${theme.fg("dim", `+${hiddenRunning + hiddenFinished} more (${overflowText})`)}`,
256
256
  ),
257
257
  );
258
258
  return lines;
@@ -291,7 +291,7 @@ export function renderWidgetLines(params: {
291
291
  // Assemble with overflow cap (heading takes 1 line).
292
292
  const maxBody = MAX_WIDGET_LINES - 1;
293
293
  const totalBody = finishedLines.length + runningLines.length * 2 + (queuedLine ? 1 : 0);
294
- const heading = truncate(theme.fg(headingColor, headingIcon) + " " + theme.fg(headingColor, "Agents"));
294
+ const heading = truncate(`${theme.fg(headingColor, headingIcon)} ${theme.fg(headingColor, "Agents")}`);
295
295
 
296
296
  if (totalBody <= maxBody) {
297
297
  return assembleWithinBudget(heading, { finishedLines, runningLines, queuedLine });