pi-subagents 0.9.0 → 0.9.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/CHANGELOG.md +23 -2
- package/README.md +9 -4
- package/agent-management.ts +19 -9
- package/agent-manager-detail.ts +5 -2
- package/agent-manager-list.ts +10 -8
- package/agent-manager.ts +9 -6
- package/agent-selection.ts +3 -0
- package/agents/context-builder.md +38 -0
- package/agents/planner.md +46 -0
- package/agents/researcher.md +51 -0
- package/agents/reviewer.md +30 -0
- package/agents/scout.md +42 -0
- package/agents/worker.md +32 -0
- package/agents.ts +5 -2
- package/package.json +2 -1
- package/render.ts +46 -28
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.9.2] - 2026-02-19
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- TUI crash on async subagent completion: "Rendered line exceeds terminal width." `render.ts` never truncated output to fit the terminal — widget lines (`agents.join(" -> ")`), chain visualizations, skills lists, and task previews could all exceed the terminal width. Added `truncLine` helper using pi-tui's `truncateToWidth`/`visibleWidth` and applied it to every `Text` widget and widget string. Task preview lengths are now dynamic based on terminal width instead of hardcoded.
|
|
9
|
+
- Agent Manager scope badge showed `[built]` instead of `[builtin]` in list and detail views. Widened scope column to fit.
|
|
10
|
+
|
|
11
|
+
## [0.9.1] - 2026-02-17
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- Builtin agents were silently excluded from management listings, chain validation, and agent resolution. Added `allAgents()` helper that includes all three tiers (builtin, user, project) and applied it to `handleList`, `findAgents`, `availableNames`, and `unknownChainAgents`.
|
|
15
|
+
- `resolveTarget` now blocks mutation of builtin agents with a clear error message suggesting the user create a same-named override, instead of allowing `fs.unlinkSync` or `fs.writeFileSync` on extension files.
|
|
16
|
+
- Agent Manager TUI guards: delete and edit actions on builtin agents are blocked with an error status. Detail screen hides `[e]dit` from the footer for builtins. Scope badge shows `[builtin]` instead of falling through to `[proj]`.
|
|
17
|
+
- Cloning a builtin agent set the scope to `"builtin"` at runtime (violating the `"user" | "project"` type), causing wrong badge display and the clone inheriting builtin protections until session reload. Now maps to `"user"`.
|
|
18
|
+
- Agent Manager `loadEntries` suppresses builtins overridden by user/project agents, preventing duplicate entries in the TUI list.
|
|
19
|
+
- `BUILTIN_AGENTS_DIR` resolved via `import.meta.url` instead of hardcoded `~/.pi/agent/extensions/subagent/agents` path. Works regardless of where the extension is installed.
|
|
20
|
+
- `handleCreate` now warns when creating an agent that shadows a builtin (informational, not an error).
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
- Simplified Agent Manager header from per-scope breakdown to total count (per-row badges already show scope).
|
|
24
|
+
- Reviewer builtin model changed from `openai/gpt-5.2` to `openai-codex/gpt-5.3-codex`.
|
|
25
|
+
- Removed `code-reviewer` builtin agent (redundant with `reviewer`).
|
|
26
|
+
|
|
5
27
|
## [0.9.0] - 2026-02-17
|
|
6
28
|
|
|
7
29
|
### Added
|
|
@@ -9,8 +31,7 @@
|
|
|
9
31
|
- `scout` — fast codebase recon (claude-haiku-4-5)
|
|
10
32
|
- `planner` — implementation plans from context (claude-opus-4-6, thinking: high)
|
|
11
33
|
- `worker` — general-purpose execution (claude-sonnet-4-6)
|
|
12
|
-
- `reviewer` — validates implementation against plans (gpt-5.
|
|
13
|
-
- `code-reviewer` — bug hunting and code review (claude-opus-4-6, thinking: high)
|
|
34
|
+
- `reviewer` — validates implementation against plans (gpt-5.3-codex, thinking: high)
|
|
14
35
|
- `context-builder` — analyzes requirements and codebase (claude-sonnet-4-6)
|
|
15
36
|
- `researcher` — autonomous web research with search, evaluation, and synthesis (claude-sonnet-4-6)
|
|
16
37
|
- **`"builtin"` agent source** — new third tier in agent discovery. Priority: builtin < user < project. Builtin agents appear in listings with a `[builtin]` badge and cannot be modified or deleted through management actions (create a same-named user agent to override instead).
|
package/README.md
CHANGED
|
@@ -26,13 +26,18 @@ Agents are markdown files with YAML frontmatter that define specialized subagent
|
|
|
26
26
|
|
|
27
27
|
**Agent file locations:**
|
|
28
28
|
|
|
29
|
-
| Scope | Path |
|
|
30
|
-
|
|
31
|
-
|
|
|
32
|
-
|
|
|
29
|
+
| Scope | Path | Priority |
|
|
30
|
+
|-------|------|----------|
|
|
31
|
+
| Builtin | `~/.pi/agent/extensions/subagent/agents/` | Lowest |
|
|
32
|
+
| User | `~/.pi/agent/agents/{name}.md` | Medium |
|
|
33
|
+
| Project | `.pi/agents/{name}.md` (searches up directory tree) | Highest |
|
|
33
34
|
|
|
34
35
|
Use `agentScope` parameter to control discovery: `"user"`, `"project"`, or `"both"` (default; project takes priority).
|
|
35
36
|
|
|
37
|
+
**Builtin agents:** The extension ships with ready-to-use agents — `scout`, `planner`, `worker`, `reviewer`, `context-builder`, and `researcher`. They load at lowest priority so any user or project agent with the same name overrides them. Builtin agents appear with a `[builtin]` badge in listings and cannot be modified through management actions (create a same-named user agent to override instead).
|
|
38
|
+
|
|
39
|
+
> **Note:** The `researcher` agent uses `web_search`, `fetch_content`, and `get_search_content` tools which require the [pi-web-access](https://github.com/nicobailon/pi-web-access) extension. Install it with `pi install npm:pi-web-access`.
|
|
40
|
+
|
|
36
41
|
**Agent frontmatter:**
|
|
37
42
|
|
|
38
43
|
```yaml
|
package/agent-management.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
|
5
5
|
import {
|
|
6
6
|
type AgentConfig,
|
|
7
7
|
type AgentScope,
|
|
8
|
+
type AgentSource,
|
|
8
9
|
type ChainConfig,
|
|
9
10
|
type ChainStepConfig,
|
|
10
11
|
discoverAgentsAll,
|
|
@@ -62,9 +63,13 @@ export function sanitizeName(name: string): string {
|
|
|
62
63
|
return name.toLowerCase().trim().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
|
|
63
64
|
}
|
|
64
65
|
|
|
66
|
+
function allAgents(d: { builtin: AgentConfig[]; user: AgentConfig[]; project: AgentConfig[] }): AgentConfig[] {
|
|
67
|
+
return [...d.builtin, ...d.user, ...d.project];
|
|
68
|
+
}
|
|
69
|
+
|
|
65
70
|
function availableNames(cwd: string, kind: "agent" | "chain"): string[] {
|
|
66
71
|
const d = discoverAgentsAll(cwd);
|
|
67
|
-
const items = kind === "agent" ?
|
|
72
|
+
const items = kind === "agent" ? allAgents(d) : d.chains;
|
|
68
73
|
return [...new Set(items.map((x) => x.name))].sort((a, b) => a.localeCompare(b));
|
|
69
74
|
}
|
|
70
75
|
|
|
@@ -72,7 +77,7 @@ export function findAgents(name: string, cwd: string, scope: AgentScope = "both"
|
|
|
72
77
|
const d = discoverAgentsAll(cwd);
|
|
73
78
|
const raw = name.trim();
|
|
74
79
|
const sanitized = sanitizeName(raw);
|
|
75
|
-
return
|
|
80
|
+
return allAgents(d)
|
|
76
81
|
.filter((a) => (scope === "both" || a.source === scope) && (a.name === raw || a.name === sanitized))
|
|
77
82
|
.sort((a, b) => a.source.localeCompare(b.source));
|
|
78
83
|
}
|
|
@@ -98,7 +103,7 @@ function nameExistsInScope(cwd: string, scope: ManagementScope, name: string, ex
|
|
|
98
103
|
|
|
99
104
|
function unknownChainAgents(cwd: string, steps: ChainStepConfig[]): string[] {
|
|
100
105
|
const d = discoverAgentsAll(cwd);
|
|
101
|
-
const known = new Set(
|
|
106
|
+
const known = new Set(allAgents(d).map((a) => a.name));
|
|
102
107
|
return [...new Set(steps.map((s) => s.agent).filter((a) => !known.has(a)))].sort((a, b) => a.localeCompare(b));
|
|
103
108
|
}
|
|
104
109
|
|
|
@@ -233,24 +238,28 @@ function applyAgentConfig(target: AgentConfig, cfg: Record<string, unknown>): st
|
|
|
233
238
|
return undefined;
|
|
234
239
|
}
|
|
235
240
|
|
|
236
|
-
function resolveTarget<T extends { source:
|
|
241
|
+
function resolveTarget<T extends { source: AgentSource; filePath: string }>(
|
|
237
242
|
kind: "agent" | "chain",
|
|
238
243
|
name: string,
|
|
239
244
|
matches: T[],
|
|
240
245
|
cwd: string,
|
|
241
246
|
scopeHint?: string,
|
|
242
247
|
): T | AgentToolResult<Details> {
|
|
243
|
-
|
|
248
|
+
const mutable = matches.filter((m) => m.source !== "builtin");
|
|
249
|
+
if (mutable.length === 0) {
|
|
250
|
+
if (matches.length > 0) {
|
|
251
|
+
return result(`${kind === "agent" ? "Agent" : "Chain"} '${name}' is builtin and cannot be modified. Create a same-named ${kind} in user or project scope to override it.`, true);
|
|
252
|
+
}
|
|
244
253
|
const available = availableNames(cwd, kind);
|
|
245
254
|
return result(`${kind === "agent" ? "Agent" : "Chain"} '${name}' not found. Available: ${available.join(", ") || "none"}.`, true);
|
|
246
255
|
}
|
|
247
|
-
if (
|
|
256
|
+
if (mutable.length === 1) return mutable[0]!;
|
|
248
257
|
const scope = asDisambiguationScope(scopeHint);
|
|
249
258
|
if (!scope) {
|
|
250
|
-
const paths =
|
|
259
|
+
const paths = mutable.map((m) => `${m.source}: ${m.filePath}`).join("\n");
|
|
251
260
|
return result(`${kind === "agent" ? "Agent" : "Chain"} '${name}' exists in both scopes. Specify agentScope: 'user' or 'project'.\n${paths}`, true);
|
|
252
261
|
}
|
|
253
|
-
const scoped =
|
|
262
|
+
const scoped = mutable.filter((m) => m.source === scope);
|
|
254
263
|
if (scoped.length === 0) return result(`${kind === "agent" ? "Agent" : "Chain"} '${name}' not found in scope '${scope}'.`, true);
|
|
255
264
|
if (scoped.length > 1) return result(`Multiple ${kind}s named '${name}' found in scope '${scope}': ${scoped.map((m) => m.filePath).join(", ")}`, true);
|
|
256
265
|
return scoped[0]!;
|
|
@@ -309,7 +318,7 @@ export function formatChainDetail(chain: ChainConfig): string {
|
|
|
309
318
|
export function handleList(params: ManagementParams, ctx: ManagementContext): AgentToolResult<Details> {
|
|
310
319
|
const scope = normalizeListScope(params.agentScope) ?? "both";
|
|
311
320
|
const d = discoverAgentsAll(ctx.cwd);
|
|
312
|
-
const agents =
|
|
321
|
+
const agents = allAgents(d).filter((a) => scope === "both" || a.source === "builtin" || a.source === scope).sort((a, b) => a.name.localeCompare(b.name));
|
|
313
322
|
const chains = d.chains.filter((c) => scope === "both" || c.source === scope).sort((a, b) => a.name.localeCompare(b.name));
|
|
314
323
|
const lines = ["Agents:", ...(agents.length ? agents.map((a) => `- ${a.name} (${a.source}): ${a.description}`) : ["- (none)"]), "", "Chains:", ...(chains.length ? chains.map((c) => `- ${c.name} (${c.source}): ${c.description}`) : ["- (none)"])];
|
|
315
324
|
return result(lines.join("\n"));
|
|
@@ -363,6 +372,7 @@ export function handleCreate(params: ManagementParams, ctx: ManagementContext):
|
|
|
363
372
|
const targetPath = path.join(targetDir, isChain ? `${name}.chain.md` : `${name}.md`);
|
|
364
373
|
if (fs.existsSync(targetPath)) return result(`File already exists at ${targetPath} but is not a valid ${isChain ? "chain" : "agent"} definition. Remove or rename it first.`, true);
|
|
365
374
|
const warnings: string[] = [];
|
|
375
|
+
if (!isChain && d.builtin.some((a) => a.name === name)) warnings.push(`Note: this shadows the builtin agent '${name}'.`);
|
|
366
376
|
if (isChain) {
|
|
367
377
|
const parsed = parseStepList(cfg.steps);
|
|
368
378
|
if (parsed.error) return result(parsed.error, true);
|
package/agent-manager-detail.ts
CHANGED
|
@@ -129,7 +129,7 @@ export function renderDetail(
|
|
|
129
129
|
theme: Theme,
|
|
130
130
|
): string[] {
|
|
131
131
|
const lines: string[] = [];
|
|
132
|
-
const scopeBadge = agent.source === "
|
|
132
|
+
const scopeBadge = agent.source === "builtin" ? "[builtin]" : agent.source === "project" ? "[proj]" : "[user]";
|
|
133
133
|
const headerText = ` ${agent.name} ${scopeBadge} ${formatPath(agent.filePath)} `;
|
|
134
134
|
lines.push(renderHeader(headerText, width, theme));
|
|
135
135
|
lines.push(row("", width, theme));
|
|
@@ -149,7 +149,10 @@ export function renderDetail(
|
|
|
149
149
|
const scrollInfo = formatScrollInfo(state.scrollOffset, Math.max(0, contentLines.length - (state.scrollOffset + DETAIL_VIEWPORT_HEIGHT)));
|
|
150
150
|
lines.push(row(scrollInfo ? ` ${theme.fg("dim", scrollInfo)}` : "", width, theme));
|
|
151
151
|
|
|
152
|
-
|
|
152
|
+
const footer = agent.source === "builtin"
|
|
153
|
+
? " [l]aunch [v] raw/resolved [↑↓] scroll [esc] back "
|
|
154
|
+
: " [l]aunch [e]dit [v] raw/resolved [↑↓] scroll [esc] back ";
|
|
155
|
+
lines.push(renderFooter(footer, width, theme));
|
|
153
156
|
return lines;
|
|
154
157
|
}
|
|
155
158
|
|
package/agent-manager-list.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Theme } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
import type { AgentSource } from "./agents.js";
|
|
2
3
|
import { matchesKey, truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
|
|
3
4
|
import { pad, row, renderHeader, renderFooter, fuzzyFilter, formatScrollInfo } from "./render-helpers.js";
|
|
4
5
|
|
|
@@ -7,7 +8,7 @@ export interface ListAgent {
|
|
|
7
8
|
name: string;
|
|
8
9
|
description: string;
|
|
9
10
|
model?: string;
|
|
10
|
-
source:
|
|
11
|
+
source: AgentSource;
|
|
11
12
|
kind: "agent" | "chain";
|
|
12
13
|
stepCount?: number;
|
|
13
14
|
}
|
|
@@ -28,7 +29,7 @@ export type ListAction =
|
|
|
28
29
|
| { type: "run-parallel"; ids: string[] }
|
|
29
30
|
| { type: "close" };
|
|
30
31
|
|
|
31
|
-
const LIST_VIEWPORT_HEIGHT =
|
|
32
|
+
const LIST_VIEWPORT_HEIGHT = 8;
|
|
32
33
|
|
|
33
34
|
function selectionCount(selected: string[], id: string): number {
|
|
34
35
|
let count = 0;
|
|
@@ -162,11 +163,11 @@ export function renderList(
|
|
|
162
163
|
const filtered = fuzzyFilter(agents, state.filterQuery);
|
|
163
164
|
clampCursor(state, filtered);
|
|
164
165
|
|
|
165
|
-
const
|
|
166
|
-
const userCount = agentOnly.filter((a) => a.source === "user").length;
|
|
167
|
-
const projectCount = agentOnly.filter((a) => a.source === "project").length;
|
|
166
|
+
const agentCount = agents.filter((a) => a.kind === "agent").length;
|
|
168
167
|
const chainCount = agents.filter((a) => a.kind === "chain").length;
|
|
169
|
-
const headerText =
|
|
168
|
+
const headerText = chainCount
|
|
169
|
+
? ` Subagents [${agentCount} agents ${chainCount} chains] `
|
|
170
|
+
: ` Subagents [${agentCount}] `;
|
|
170
171
|
lines.push(renderHeader(headerText, width, theme));
|
|
171
172
|
lines.push(row("", width, theme));
|
|
172
173
|
|
|
@@ -189,7 +190,7 @@ export function renderList(
|
|
|
189
190
|
const innerW = width - 2;
|
|
190
191
|
const nameWidth = 16;
|
|
191
192
|
const modelWidth = 12;
|
|
192
|
-
const scopeWidth =
|
|
193
|
+
const scopeWidth = 9;
|
|
193
194
|
|
|
194
195
|
for (let i = 0; i < visible.length; i++) {
|
|
195
196
|
const agent = visible[i]!;
|
|
@@ -207,7 +208,8 @@ export function renderList(
|
|
|
207
208
|
const modelDisplay = modelRaw.includes("/") ? modelRaw.split("/").pop() ?? modelRaw : modelRaw;
|
|
208
209
|
const nameText = isCursor ? theme.fg("accent", agent.name) : agent.name;
|
|
209
210
|
const modelText = theme.fg("dim", modelDisplay);
|
|
210
|
-
const
|
|
211
|
+
const scopeLabel = agent.kind === "chain" ? "[chain]" : agent.source === "builtin" ? "[builtin]" : agent.source === "project" ? "[proj]" : "[user]";
|
|
212
|
+
const scopeBadge = theme.fg("dim", scopeLabel);
|
|
211
213
|
const descText = theme.fg("dim", agent.description);
|
|
212
214
|
|
|
213
215
|
const descWidth = Math.max(0, innerW - 1 - visibleWidth(prefix) - nameWidth - modelWidth - scopeWidth - 3);
|
package/agent-manager.ts
CHANGED
|
@@ -24,7 +24,7 @@ export type ManagerResult =
|
|
|
24
24
|
| { action: "launch-chain"; chain: ChainConfig; task: string; skipClarify?: boolean }
|
|
25
25
|
| undefined;
|
|
26
26
|
|
|
27
|
-
export interface AgentData { user: AgentConfig[]; project: AgentConfig[]; chains: ChainConfig[]; userDir: string; projectDir: string | null; cwd: string; }
|
|
27
|
+
export interface AgentData { builtin: AgentConfig[]; user: AgentConfig[]; project: AgentConfig[]; chains: ChainConfig[]; userDir: string; projectDir: string | null; cwd: string; }
|
|
28
28
|
type ManagerScreen = "list" | "detail" | "chain-detail" | "edit" | "edit-field" | "edit-prompt" | "task-input" | "confirm-delete" | "name-input" | "chain-edit" | "template-select" | "parallel-builder";
|
|
29
29
|
interface AgentEntry { id: string; kind: "agent"; config: AgentConfig; isNew: boolean; }
|
|
30
30
|
interface ChainEntry { id: string; kind: "chain"; config: ChainConfig; }
|
|
@@ -65,7 +65,8 @@ export class AgentManagerComponent implements Component {
|
|
|
65
65
|
constructor(private tui: TUI, private theme: Theme, private agentData: AgentData, private models: ModelInfo[], private skills: SkillInfo[], private done: (result: ManagerResult) => void) { this.loadEntries(); }
|
|
66
66
|
|
|
67
67
|
private loadEntries(): void {
|
|
68
|
-
const
|
|
68
|
+
const overridden = new Set([...this.agentData.user, ...this.agentData.project].map((c) => c.name));
|
|
69
|
+
const agents: AgentEntry[] = []; for (const config of this.agentData.builtin) { if (!overridden.has(config.name)) agents.push({ id: `a${this.nextId++}`, kind: "agent", config: cloneConfig(config), isNew: false }); } for (const config of this.agentData.user) agents.push({ id: `a${this.nextId++}`, kind: "agent", config: cloneConfig(config), isNew: false }); for (const config of this.agentData.project) agents.push({ id: `a${this.nextId++}`, kind: "agent", config: cloneConfig(config), isNew: false }); this.agents = agents;
|
|
69
70
|
const chains: ChainEntry[] = []; for (const config of this.agentData.chains) chains.push({ id: `c${this.nextId++}`, kind: "chain", config: cloneChainConfig(config) }); this.chains = chains;
|
|
70
71
|
}
|
|
71
72
|
|
|
@@ -104,8 +105,8 @@ export class AgentManagerComponent implements Component {
|
|
|
104
105
|
|
|
105
106
|
private enterNameInput(mode: NameInputState["mode"], sourceId?: string, template?: AgentTemplate): void {
|
|
106
107
|
const allowProject = Boolean(this.agentData.projectDir); let initial = ""; let scope: "user" | "project" = "user";
|
|
107
|
-
if (mode === "clone-agent" && sourceId) { const entry = this.getAgentEntry(sourceId); if (entry) { initial = `${entry.config.name}-copy`; scope = entry.config.source; } }
|
|
108
|
-
if (mode === "clone-chain" && sourceId) { const entry = this.getChainEntry(sourceId); if (entry) { initial = `${entry.config.name}-copy`; scope = entry.config.source; } }
|
|
108
|
+
if (mode === "clone-agent" && sourceId) { const entry = this.getAgentEntry(sourceId); if (entry) { initial = `${entry.config.name}-copy`; scope = entry.config.source === "project" ? "project" : "user"; } }
|
|
109
|
+
if (mode === "clone-chain" && sourceId) { const entry = this.getChainEntry(sourceId); if (entry) { initial = `${entry.config.name}-copy`; scope = entry.config.source === "project" ? "project" : "user"; } }
|
|
109
110
|
if (mode === "new-agent" && template && template.name !== "Blank") initial = slugTemplateName(template.name);
|
|
110
111
|
this.nameInputState = { mode, editor: createEditorState(initial), scope, allowProject, sourceId, template }; this.screen = "name-input";
|
|
111
112
|
}
|
|
@@ -334,12 +335,14 @@ export class AgentManagerComponent implements Component {
|
|
|
334
335
|
this.editState = null; this.enterDetail(entry); this.tui.requestRender();
|
|
335
336
|
}
|
|
336
337
|
|
|
338
|
+
private isBuiltin(id: string): boolean { const a = this.getAgentEntry(id); return a?.config.source === "builtin"; }
|
|
339
|
+
|
|
337
340
|
private handleListAction(action: ListAction): void {
|
|
338
341
|
switch (action.type) {
|
|
339
342
|
case "open-detail": { const agent = this.getAgentEntry(action.id); if (agent) { this.enterDetail(agent); return; } const chain = this.getChainEntry(action.id); if (chain) this.enterChainDetail(chain); return; }
|
|
340
343
|
case "clone": if (this.getAgentEntry(action.id)) this.enterNameInput("clone-agent", action.id); else if (this.getChainEntry(action.id)) this.enterNameInput("clone-chain", action.id); return;
|
|
341
344
|
case "new": this.enterTemplateSelect(); return;
|
|
342
|
-
case "delete": this.confirmDeleteId = action.id; this.screen = "confirm-delete"; return;
|
|
345
|
+
case "delete": { if (this.isBuiltin(action.id)) { this.statusMessage = { text: "Builtin agents cannot be deleted. Clone to user scope to override.", type: "error" }; return; } this.confirmDeleteId = action.id; this.screen = "confirm-delete"; return; }
|
|
343
346
|
case "run-chain": this.enterTaskInput(action.ids); return;
|
|
344
347
|
case "run-parallel": this.enterParallelBuilder(action.ids); return;
|
|
345
348
|
case "close": this.done(undefined); return;
|
|
@@ -348,7 +351,7 @@ export class AgentManagerComponent implements Component {
|
|
|
348
351
|
|
|
349
352
|
private handleDetailAction(action: DetailAction, entry: AgentEntry): void {
|
|
350
353
|
if (action.type === "back") { this.screen = "list"; return; }
|
|
351
|
-
if (action.type === "edit") { this.enterEdit(entry); return; }
|
|
354
|
+
if (action.type === "edit") { if (entry.config.source === "builtin") { this.statusMessage = { text: "Builtin agents cannot be edited. Clone to user scope to override.", type: "error" }; this.screen = "list"; return; } this.enterEdit(entry); return; }
|
|
352
355
|
if (action.type === "launch") { this.enterTaskInput([entry.id], "detail"); return; }
|
|
353
356
|
}
|
|
354
357
|
|
package/agent-selection.ts
CHANGED
|
@@ -4,9 +4,12 @@ export function mergeAgentsForScope(
|
|
|
4
4
|
scope: AgentScope,
|
|
5
5
|
userAgents: AgentConfig[],
|
|
6
6
|
projectAgents: AgentConfig[],
|
|
7
|
+
builtinAgents: AgentConfig[] = [],
|
|
7
8
|
): AgentConfig[] {
|
|
8
9
|
const agentMap = new Map<string, AgentConfig>();
|
|
9
10
|
|
|
11
|
+
for (const agent of builtinAgents) agentMap.set(agent.name, agent);
|
|
12
|
+
|
|
10
13
|
if (scope === "both") {
|
|
11
14
|
for (const agent of userAgents) agentMap.set(agent.name, agent);
|
|
12
15
|
for (const agent of projectAgents) agentMap.set(agent.name, agent);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: context-builder
|
|
3
|
+
description: Analyzes requirements and codebase, generates context and meta-prompt
|
|
4
|
+
tools: read, grep, find, ls, bash, web_search
|
|
5
|
+
model: claude-sonnet-4-6
|
|
6
|
+
output: context.md
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
You analyze user requirements against a codebase to build comprehensive context.
|
|
10
|
+
|
|
11
|
+
Given a user request (prose, user stories, requirements), you will:
|
|
12
|
+
|
|
13
|
+
1. **Analyze the request** - Understand what the user wants to build
|
|
14
|
+
2. **Search the codebase** - Find all relevant files, patterns, dependencies
|
|
15
|
+
3. **Research if needed** - Look up APIs, libraries, best practices online
|
|
16
|
+
4. **Generate output files** - You'll receive instructions about where to write
|
|
17
|
+
|
|
18
|
+
When running in a chain, generate two files in the specified chain directory:
|
|
19
|
+
|
|
20
|
+
**context.md** - Code context:
|
|
21
|
+
# Code Context
|
|
22
|
+
## Relevant Files
|
|
23
|
+
[files with line numbers and snippets]
|
|
24
|
+
## Patterns Found
|
|
25
|
+
[existing patterns to follow]
|
|
26
|
+
## Dependencies
|
|
27
|
+
[libraries, APIs involved]
|
|
28
|
+
|
|
29
|
+
**meta-prompt.md** - Optimized instructions for planner:
|
|
30
|
+
# Meta-Prompt for Planning
|
|
31
|
+
## Requirements Summary
|
|
32
|
+
[distilled requirements]
|
|
33
|
+
## Technical Constraints
|
|
34
|
+
[must-haves, limitations]
|
|
35
|
+
## Suggested Approach
|
|
36
|
+
[recommended implementation strategy]
|
|
37
|
+
## Questions Resolved
|
|
38
|
+
[decisions made during analysis]
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: planner
|
|
3
|
+
description: Creates implementation plans from context and requirements
|
|
4
|
+
tools: read, grep, find, ls, write
|
|
5
|
+
model: claude-opus-4-6
|
|
6
|
+
thinking: high
|
|
7
|
+
output: plan.md
|
|
8
|
+
defaultReads: context.md
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
You are a planning specialist. You receive context and requirements, then produce a clear implementation plan.
|
|
12
|
+
|
|
13
|
+
You must NOT make any changes. Only read, analyze, and plan.
|
|
14
|
+
|
|
15
|
+
When running in a chain, you'll receive instructions about which files to read and where to write your output.
|
|
16
|
+
|
|
17
|
+
Output format (plan.md):
|
|
18
|
+
|
|
19
|
+
# Implementation Plan
|
|
20
|
+
|
|
21
|
+
## Goal
|
|
22
|
+
One sentence summary of what needs to be done.
|
|
23
|
+
|
|
24
|
+
## Tasks
|
|
25
|
+
Numbered steps, each small and actionable:
|
|
26
|
+
1. **Task 1**: Description
|
|
27
|
+
- File: `path/to/file.ts`
|
|
28
|
+
- Changes: What to modify
|
|
29
|
+
- Acceptance: How to verify
|
|
30
|
+
|
|
31
|
+
2. **Task 2**: Description
|
|
32
|
+
...
|
|
33
|
+
|
|
34
|
+
## Files to Modify
|
|
35
|
+
- `path/to/file.ts` - what changes
|
|
36
|
+
|
|
37
|
+
## New Files (if any)
|
|
38
|
+
- `path/to/new.ts` - purpose
|
|
39
|
+
|
|
40
|
+
## Dependencies
|
|
41
|
+
Which tasks depend on others.
|
|
42
|
+
|
|
43
|
+
## Risks
|
|
44
|
+
Anything to watch out for.
|
|
45
|
+
|
|
46
|
+
Keep the plan concrete. The worker agent will execute it.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: researcher
|
|
3
|
+
description: Autonomous web researcher — searches, evaluates, and synthesizes a focused research brief
|
|
4
|
+
tools: read, write, web_search, fetch_content, get_search_content
|
|
5
|
+
model: anthropic/claude-sonnet-4-6
|
|
6
|
+
output: research.md
|
|
7
|
+
defaultProgress: true
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
You are a research specialist. Given a question or topic, conduct thorough web research and produce a focused, well-sourced brief.
|
|
11
|
+
|
|
12
|
+
Process:
|
|
13
|
+
1. Break the question into 2-4 searchable facets
|
|
14
|
+
2. Search with `web_search` using `queries` (parallel, varied angles) and `curate: false`
|
|
15
|
+
3. Read the answers. Identify what's well-covered, what has gaps, what's noise.
|
|
16
|
+
4. For the 2-3 most promising source URLs, use `fetch_content` to get full page content
|
|
17
|
+
5. Synthesize everything into a brief that directly answers the question
|
|
18
|
+
|
|
19
|
+
Search strategy — always vary your angles:
|
|
20
|
+
- Direct answer query (the obvious one)
|
|
21
|
+
- Authoritative source query (official docs, specs, primary sources)
|
|
22
|
+
- Practical experience query (case studies, benchmarks, real-world usage)
|
|
23
|
+
- Recent developments query (only if the topic is time-sensitive)
|
|
24
|
+
|
|
25
|
+
Evaluation — what to keep vs drop:
|
|
26
|
+
- Official docs and primary sources outweigh blog posts and forum threads
|
|
27
|
+
- Recent sources outweigh stale ones (check URL path for dates like /2025/01/)
|
|
28
|
+
- Sources that directly address the question outweigh tangentially related ones
|
|
29
|
+
- Diverse perspectives outweigh redundant coverage of the same point
|
|
30
|
+
- Drop: SEO filler, outdated info, beginner tutorials (unless that's the audience)
|
|
31
|
+
|
|
32
|
+
If the first round of searches doesn't fully answer the question, search again with refined queries targeting the gaps. Don't settle for partial answers when a follow-up search could fill them.
|
|
33
|
+
|
|
34
|
+
Output format (research.md):
|
|
35
|
+
|
|
36
|
+
# Research: [topic]
|
|
37
|
+
|
|
38
|
+
## Summary
|
|
39
|
+
2-3 sentence direct answer.
|
|
40
|
+
|
|
41
|
+
## Findings
|
|
42
|
+
Numbered findings with inline source citations:
|
|
43
|
+
1. **Finding** — explanation. [Source](url)
|
|
44
|
+
2. **Finding** — explanation. [Source](url)
|
|
45
|
+
|
|
46
|
+
## Sources
|
|
47
|
+
- Kept: Source Title (url) — why relevant
|
|
48
|
+
- Dropped: Source Title — why excluded
|
|
49
|
+
|
|
50
|
+
## Gaps
|
|
51
|
+
What couldn't be answered. Suggested next steps.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: reviewer
|
|
3
|
+
description: Code review specialist that validates implementation and fixes issues
|
|
4
|
+
tools: read, grep, find, ls, bash
|
|
5
|
+
model: openai-codex/gpt-5.3-codex
|
|
6
|
+
thinking: high
|
|
7
|
+
defaultReads: plan.md, progress.md
|
|
8
|
+
defaultProgress: true
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
You are a senior code reviewer. Analyze implementation against the plan.
|
|
12
|
+
|
|
13
|
+
When running in a chain, you'll receive instructions about which files to read (plan and progress) and where to update progress.
|
|
14
|
+
|
|
15
|
+
Bash is for read-only commands only: `git diff`, `git log`, `git show`.
|
|
16
|
+
|
|
17
|
+
Review checklist:
|
|
18
|
+
1. Implementation matches plan requirements
|
|
19
|
+
2. Code quality and correctness
|
|
20
|
+
3. Edge cases handled
|
|
21
|
+
4. Security considerations
|
|
22
|
+
|
|
23
|
+
If issues found, fix them directly.
|
|
24
|
+
|
|
25
|
+
Update progress.md with:
|
|
26
|
+
|
|
27
|
+
## Review
|
|
28
|
+
- What's correct
|
|
29
|
+
- Fixed: Issue and resolution
|
|
30
|
+
- Note: Observations
|
package/agents/scout.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: scout
|
|
3
|
+
description: Fast codebase recon that returns compressed context for handoff
|
|
4
|
+
tools: read, grep, find, ls, bash, write
|
|
5
|
+
model: anthropic/claude-haiku-4-5
|
|
6
|
+
output: context.md
|
|
7
|
+
defaultProgress: true
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
You are a scout. Quickly investigate a codebase and return structured findings.
|
|
11
|
+
|
|
12
|
+
When running in a chain, you'll receive instructions about where to write your output.
|
|
13
|
+
When running solo, write to the provided output path and summarize what you found.
|
|
14
|
+
|
|
15
|
+
Thoroughness (infer from task, default medium):
|
|
16
|
+
- Quick: Targeted lookups, key files only
|
|
17
|
+
- Medium: Follow imports, read critical sections
|
|
18
|
+
- Thorough: Trace all dependencies, check tests/types
|
|
19
|
+
|
|
20
|
+
Strategy:
|
|
21
|
+
1. grep/find to locate relevant code
|
|
22
|
+
2. Read key sections (not entire files)
|
|
23
|
+
3. Identify types, interfaces, key functions
|
|
24
|
+
4. Note dependencies between files
|
|
25
|
+
|
|
26
|
+
Your output format (context.md):
|
|
27
|
+
|
|
28
|
+
# Code Context
|
|
29
|
+
|
|
30
|
+
## Files Retrieved
|
|
31
|
+
List with exact line ranges:
|
|
32
|
+
1. `path/to/file.ts` (lines 10-50) - Description
|
|
33
|
+
2. `path/to/other.ts` (lines 100-150) - Description
|
|
34
|
+
|
|
35
|
+
## Key Code
|
|
36
|
+
Critical types, interfaces, or functions with actual code snippets.
|
|
37
|
+
|
|
38
|
+
## Architecture
|
|
39
|
+
Brief explanation of how the pieces connect.
|
|
40
|
+
|
|
41
|
+
## Start Here
|
|
42
|
+
Which file to look at first and why.
|
package/agents/worker.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: worker
|
|
3
|
+
description: General-purpose subagent with full capabilities, isolated context
|
|
4
|
+
model: claude-sonnet-4-6
|
|
5
|
+
defaultReads: context.md, plan.md
|
|
6
|
+
defaultProgress: true
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
You are a worker agent with full capabilities. You operate in an isolated context window.
|
|
10
|
+
|
|
11
|
+
When running in a chain, you'll receive instructions about:
|
|
12
|
+
- Which files to read (context from previous steps)
|
|
13
|
+
- Where to maintain progress tracking
|
|
14
|
+
|
|
15
|
+
Work autonomously to complete the assigned task. Use all available tools as needed.
|
|
16
|
+
|
|
17
|
+
Progress.md format:
|
|
18
|
+
|
|
19
|
+
# Progress
|
|
20
|
+
|
|
21
|
+
## Status
|
|
22
|
+
[In Progress | Completed | Blocked]
|
|
23
|
+
|
|
24
|
+
## Tasks
|
|
25
|
+
- [x] Completed task
|
|
26
|
+
- [ ] Current task
|
|
27
|
+
|
|
28
|
+
## Files Changed
|
|
29
|
+
- `path/to/file.ts` - what changed
|
|
30
|
+
|
|
31
|
+
## Notes
|
|
32
|
+
Any blockers or decisions.
|
package/agents.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import * as fs from "node:fs";
|
|
6
6
|
import * as os from "node:os";
|
|
7
7
|
import * as path from "node:path";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
8
9
|
import { KNOWN_FIELDS } from "./agent-serializer.js";
|
|
9
10
|
import { parseChain } from "./chain-serializer.js";
|
|
10
11
|
import { mergeAgentsForScope } from "./agent-selection.js";
|
|
@@ -242,7 +243,7 @@ function findNearestProjectAgentsDir(cwd: string): string | null {
|
|
|
242
243
|
}
|
|
243
244
|
}
|
|
244
245
|
|
|
245
|
-
const BUILTIN_AGENTS_DIR = path.join(
|
|
246
|
+
const BUILTIN_AGENTS_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)), "agents");
|
|
246
247
|
|
|
247
248
|
export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryResult {
|
|
248
249
|
const userDir = path.join(os.homedir(), ".pi", "agent", "agents");
|
|
@@ -257,6 +258,7 @@ export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryRe
|
|
|
257
258
|
}
|
|
258
259
|
|
|
259
260
|
export function discoverAgentsAll(cwd: string): {
|
|
261
|
+
builtin: AgentConfig[];
|
|
260
262
|
user: AgentConfig[];
|
|
261
263
|
project: AgentConfig[];
|
|
262
264
|
chains: ChainConfig[];
|
|
@@ -266,6 +268,7 @@ export function discoverAgentsAll(cwd: string): {
|
|
|
266
268
|
const userDir = path.join(os.homedir(), ".pi", "agent", "agents");
|
|
267
269
|
const projectDir = findNearestProjectAgentsDir(cwd);
|
|
268
270
|
|
|
271
|
+
const builtin = loadAgentsFromDir(BUILTIN_AGENTS_DIR, "builtin");
|
|
269
272
|
const user = loadAgentsFromDir(userDir, "user");
|
|
270
273
|
const project = projectDir ? loadAgentsFromDir(projectDir, "project") : [];
|
|
271
274
|
const chains = [
|
|
@@ -273,5 +276,5 @@ export function discoverAgentsAll(cwd: string): {
|
|
|
273
276
|
...(projectDir ? loadChainsFromDir(projectDir, "project") : []),
|
|
274
277
|
];
|
|
275
278
|
|
|
276
|
-
return { user, project, chains, userDir, projectDir };
|
|
279
|
+
return { builtin, user, project, chains, userDir, projectDir };
|
|
277
280
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-subagents",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.2",
|
|
4
4
|
"description": "Pi extension for delegating tasks to subagents with chains, parallel execution, and TUI clarification",
|
|
5
5
|
"author": "Nico Bailon",
|
|
6
6
|
"license": "MIT",
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"*.ts",
|
|
30
30
|
"!*.test.ts",
|
|
31
31
|
"*.mjs",
|
|
32
|
+
"agents/",
|
|
32
33
|
"README.md",
|
|
33
34
|
"CHANGELOG.md"
|
|
34
35
|
],
|
package/render.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import type { AgentToolResult } from "@mariozechner/pi-agent-core";
|
|
6
6
|
import { getMarkdownTheme, type ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
7
|
-
import { Container, Markdown, Spacer, Text, type Widget } from "@mariozechner/pi-tui";
|
|
7
|
+
import { Container, Markdown, Spacer, Text, truncateToWidth, visibleWidth, type Widget } from "@mariozechner/pi-tui";
|
|
8
8
|
import {
|
|
9
9
|
type AsyncJobState,
|
|
10
10
|
type Details,
|
|
@@ -16,6 +16,15 @@ import { getFinalOutput, getDisplayItems, getOutputTail, getLastActivity } from
|
|
|
16
16
|
|
|
17
17
|
type Theme = ExtensionContext["ui"]["theme"];
|
|
18
18
|
|
|
19
|
+
function getTermWidth(): number {
|
|
20
|
+
return process.stdout.columns || 120;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function truncLine(text: string, maxWidth: number): string {
|
|
24
|
+
if (visibleWidth(text) <= maxWidth) return text;
|
|
25
|
+
return truncateToWidth(text, maxWidth - 1) + "…";
|
|
26
|
+
}
|
|
27
|
+
|
|
19
28
|
// Track last rendered widget state to avoid no-op re-renders
|
|
20
29
|
let lastWidgetHash = "";
|
|
21
30
|
|
|
@@ -67,6 +76,7 @@ export function renderWidget(ctx: ExtensionContext, jobs: AsyncJobState[]): void
|
|
|
67
76
|
lastWidgetHash = newHash;
|
|
68
77
|
|
|
69
78
|
const theme = ctx.ui.theme;
|
|
79
|
+
const w = getTermWidth();
|
|
70
80
|
const lines: string[] = [];
|
|
71
81
|
lines.push(theme.fg("accent", "Async subagents"));
|
|
72
82
|
|
|
@@ -90,12 +100,12 @@ export function renderWidget(ctx: ExtensionContext, jobs: AsyncJobState[]): void
|
|
|
90
100
|
const activityText = job.status === "running" ? getLastActivity(job.outputFile) : "";
|
|
91
101
|
const activitySuffix = activityText ? ` | ${theme.fg("dim", activityText)}` : "";
|
|
92
102
|
|
|
93
|
-
lines.push(`- ${id} ${status} | ${agentLabel} | ${stepText}${elapsed ? ` | ${elapsed}` : ""}${tokenText}${activitySuffix}
|
|
103
|
+
lines.push(truncLine(`- ${id} ${status} | ${agentLabel} | ${stepText}${elapsed ? ` | ${elapsed}` : ""}${tokenText}${activitySuffix}`, w));
|
|
94
104
|
|
|
95
105
|
if (job.status === "running" && job.outputFile) {
|
|
96
106
|
const tail = getOutputTail(job.outputFile, 3);
|
|
97
107
|
for (const line of tail) {
|
|
98
|
-
lines.push(theme.fg("dim", ` > ${line}`));
|
|
108
|
+
lines.push(truncLine(theme.fg("dim", ` > ${line}`), w));
|
|
99
109
|
}
|
|
100
110
|
}
|
|
101
111
|
}
|
|
@@ -114,7 +124,8 @@ export function renderSubagentResult(
|
|
|
114
124
|
const d = result.details;
|
|
115
125
|
if (!d || !d.results.length) {
|
|
116
126
|
const t = result.content[0];
|
|
117
|
-
|
|
127
|
+
const text = t?.type === "text" ? t.text : "(no output)";
|
|
128
|
+
return new Text(truncLine(text, getTermWidth() - 4), 0, 0);
|
|
118
129
|
}
|
|
119
130
|
|
|
120
131
|
const mdTheme = getMarkdownTheme();
|
|
@@ -135,37 +146,42 @@ export function renderSubagentResult(
|
|
|
135
146
|
? ` | ${r.progressSummary.toolCount} tools, ${formatTokens(r.progressSummary.tokens)} tok, ${formatDuration(r.progressSummary.durationMs)}`
|
|
136
147
|
: "";
|
|
137
148
|
|
|
149
|
+
const w = getTermWidth() - 4;
|
|
138
150
|
const c = new Container();
|
|
139
|
-
c.addChild(new Text(`${icon} ${theme.fg("toolTitle", theme.bold(r.agent))}${progressInfo}`, 0, 0));
|
|
151
|
+
c.addChild(new Text(truncLine(`${icon} ${theme.fg("toolTitle", theme.bold(r.agent))}${progressInfo}`, w), 0, 0));
|
|
140
152
|
c.addChild(new Spacer(1));
|
|
153
|
+
const taskMaxLen = Math.max(20, w - 8);
|
|
154
|
+
const taskPreview = r.task.length > taskMaxLen
|
|
155
|
+
? `${r.task.slice(0, taskMaxLen)}...`
|
|
156
|
+
: r.task;
|
|
141
157
|
c.addChild(
|
|
142
|
-
new Text(theme.fg("dim", `Task: ${
|
|
158
|
+
new Text(truncLine(theme.fg("dim", `Task: ${taskPreview}`), w), 0, 0),
|
|
143
159
|
);
|
|
144
160
|
c.addChild(new Spacer(1));
|
|
145
161
|
|
|
146
162
|
const items = getDisplayItems(r.messages);
|
|
147
163
|
for (const item of items) {
|
|
148
164
|
if (item.type === "tool")
|
|
149
|
-
c.addChild(new Text(theme.fg("muted", formatToolCall(item.name, item.args)), 0, 0));
|
|
165
|
+
c.addChild(new Text(truncLine(theme.fg("muted", formatToolCall(item.name, item.args)), w), 0, 0));
|
|
150
166
|
}
|
|
151
167
|
if (items.length) c.addChild(new Spacer(1));
|
|
152
168
|
|
|
153
169
|
if (output) c.addChild(new Markdown(output, 0, 0, mdTheme));
|
|
154
170
|
c.addChild(new Spacer(1));
|
|
155
171
|
if (r.skills?.length) {
|
|
156
|
-
c.addChild(new Text(theme.fg("dim", `Skills: ${r.skills.join(", ")}`), 0, 0));
|
|
172
|
+
c.addChild(new Text(truncLine(theme.fg("dim", `Skills: ${r.skills.join(", ")}`), w), 0, 0));
|
|
157
173
|
}
|
|
158
174
|
if (r.skillsWarning) {
|
|
159
|
-
c.addChild(new Text(theme.fg("warning", `⚠️ ${r.skillsWarning}`), 0, 0));
|
|
175
|
+
c.addChild(new Text(truncLine(theme.fg("warning", `⚠️ ${r.skillsWarning}`), w), 0, 0));
|
|
160
176
|
}
|
|
161
|
-
c.addChild(new Text(theme.fg("dim", formatUsage(r.usage, r.model)), 0, 0));
|
|
177
|
+
c.addChild(new Text(truncLine(theme.fg("dim", formatUsage(r.usage, r.model)), w), 0, 0));
|
|
162
178
|
if (r.sessionFile) {
|
|
163
|
-
c.addChild(new Text(theme.fg("dim", `Session: ${shortenPath(r.sessionFile)}`), 0, 0));
|
|
179
|
+
c.addChild(new Text(truncLine(theme.fg("dim", `Session: ${shortenPath(r.sessionFile)}`), w), 0, 0));
|
|
164
180
|
}
|
|
165
181
|
|
|
166
182
|
if (r.artifactPaths) {
|
|
167
183
|
c.addChild(new Spacer(1));
|
|
168
|
-
c.addChild(new Text(theme.fg("dim", `Artifacts: ${shortenPath(r.artifactPaths.outputPath)}`), 0, 0));
|
|
184
|
+
c.addChild(new Text(truncLine(theme.fg("dim", `Artifacts: ${shortenPath(r.artifactPaths.outputPath)}`), w), 0, 0));
|
|
169
185
|
}
|
|
170
186
|
return c;
|
|
171
187
|
}
|
|
@@ -231,7 +247,7 @@ export function renderSubagentResult(
|
|
|
231
247
|
&& Boolean(isComplete)
|
|
232
248
|
&& hasEmptyTextOutputWithoutOutputTarget(result.task, getFinalOutput(result.messages));
|
|
233
249
|
const isCurrent = i === (d.currentStepIndex ?? d.results.length);
|
|
234
|
-
const
|
|
250
|
+
const stepIcon = isFailed
|
|
235
251
|
? theme.fg("error", "✗")
|
|
236
252
|
: isEmptyWithoutTarget
|
|
237
253
|
? theme.fg("warning", "⚠")
|
|
@@ -240,22 +256,23 @@ export function renderSubagentResult(
|
|
|
240
256
|
: isCurrent && hasRunning
|
|
241
257
|
? theme.fg("warning", "●")
|
|
242
258
|
: theme.fg("dim", "○");
|
|
243
|
-
return `${
|
|
259
|
+
return `${stepIcon} ${agent}`;
|
|
244
260
|
})
|
|
245
261
|
.join(theme.fg("dim", " → "))
|
|
246
262
|
: null;
|
|
247
263
|
|
|
264
|
+
const w = getTermWidth() - 4;
|
|
248
265
|
const c = new Container();
|
|
249
266
|
c.addChild(
|
|
250
267
|
new Text(
|
|
251
|
-
`${icon} ${theme.fg("toolTitle", theme.bold(modeLabel))}${stepInfo}${summaryStr}`,
|
|
268
|
+
truncLine(`${icon} ${theme.fg("toolTitle", theme.bold(modeLabel))}${stepInfo}${summaryStr}`, w),
|
|
252
269
|
0,
|
|
253
270
|
0,
|
|
254
271
|
),
|
|
255
272
|
);
|
|
256
273
|
// Show chain visualization
|
|
257
274
|
if (chainVis) {
|
|
258
|
-
c.addChild(new Text(` ${chainVis}`, 0, 0));
|
|
275
|
+
c.addChild(new Text(truncLine(` ${chainVis}`, w), 0, 0));
|
|
259
276
|
}
|
|
260
277
|
|
|
261
278
|
// === STATIC STEP LAYOUT (like clarification UI) ===
|
|
@@ -275,7 +292,7 @@ export function renderSubagentResult(
|
|
|
275
292
|
|
|
276
293
|
if (!r) {
|
|
277
294
|
// Pending step
|
|
278
|
-
c.addChild(new Text(theme.fg("dim", ` Step ${i + 1}: ${agentName}`), 0, 0));
|
|
295
|
+
c.addChild(new Text(truncLine(theme.fg("dim", ` Step ${i + 1}: ${agentName}`), w), 0, 0));
|
|
279
296
|
c.addChild(new Text(theme.fg("dim", ` status: ○ pending`), 0, 0));
|
|
280
297
|
c.addChild(new Spacer(1));
|
|
281
298
|
continue;
|
|
@@ -299,45 +316,46 @@ export function renderSubagentResult(
|
|
|
299
316
|
const stepHeader = rRunning
|
|
300
317
|
? `${statusIcon} Step ${i + 1}: ${theme.bold(theme.fg("warning", r.agent))}${modelDisplay}${stats}`
|
|
301
318
|
: `${statusIcon} Step ${i + 1}: ${theme.bold(r.agent)}${modelDisplay}${stats}`;
|
|
302
|
-
c.addChild(new Text(stepHeader, 0, 0));
|
|
319
|
+
c.addChild(new Text(truncLine(stepHeader, w), 0, 0));
|
|
303
320
|
|
|
304
|
-
const
|
|
305
|
-
|
|
321
|
+
const taskMaxLen = Math.max(20, w - 12);
|
|
322
|
+
const taskPreview = r.task.slice(0, taskMaxLen) + (r.task.length > taskMaxLen ? "..." : "");
|
|
323
|
+
c.addChild(new Text(truncLine(theme.fg("dim", ` task: ${taskPreview}`), w), 0, 0));
|
|
306
324
|
|
|
307
325
|
const outputTarget = extractOutputTarget(r.task);
|
|
308
326
|
if (outputTarget) {
|
|
309
|
-
c.addChild(new Text(theme.fg("dim", ` output: ${outputTarget}`), 0, 0));
|
|
327
|
+
c.addChild(new Text(truncLine(theme.fg("dim", ` output: ${outputTarget}`), w), 0, 0));
|
|
310
328
|
}
|
|
311
329
|
|
|
312
330
|
if (r.skills?.length) {
|
|
313
|
-
c.addChild(new Text(theme.fg("dim", ` skills: ${r.skills.join(", ")}`), 0, 0));
|
|
331
|
+
c.addChild(new Text(truncLine(theme.fg("dim", ` skills: ${r.skills.join(", ")}`), w), 0, 0));
|
|
314
332
|
}
|
|
315
333
|
if (r.skillsWarning) {
|
|
316
|
-
c.addChild(new Text(theme.fg("warning", ` ⚠️ ${r.skillsWarning}`), 0, 0));
|
|
334
|
+
c.addChild(new Text(truncLine(theme.fg("warning", ` ⚠️ ${r.skillsWarning}`), w), 0, 0));
|
|
317
335
|
}
|
|
318
336
|
|
|
319
337
|
if (rRunning && rProg) {
|
|
320
338
|
if (rProg.skills?.length) {
|
|
321
|
-
c.addChild(new Text(theme.fg("accent", ` skills: ${rProg.skills.join(", ")}`), 0, 0));
|
|
339
|
+
c.addChild(new Text(truncLine(theme.fg("accent", ` skills: ${rProg.skills.join(", ")}`), w), 0, 0));
|
|
322
340
|
}
|
|
323
341
|
// Current tool for running step
|
|
324
342
|
if (rProg.currentTool) {
|
|
325
343
|
const toolLine = rProg.currentToolArgs
|
|
326
344
|
? `${rProg.currentTool}: ${rProg.currentToolArgs.slice(0, 100)}${rProg.currentToolArgs.length > 100 ? "..." : ""}`
|
|
327
345
|
: rProg.currentTool;
|
|
328
|
-
c.addChild(new Text(theme.fg("warning", ` > ${toolLine}`), 0, 0));
|
|
346
|
+
c.addChild(new Text(truncLine(theme.fg("warning", ` > ${toolLine}`), w), 0, 0));
|
|
329
347
|
}
|
|
330
348
|
// Recent tools
|
|
331
349
|
if (rProg.recentTools?.length) {
|
|
332
350
|
for (const t of rProg.recentTools.slice(0, 3)) {
|
|
333
351
|
const args = t.args.slice(0, 90) + (t.args.length > 90 ? "..." : "");
|
|
334
|
-
c.addChild(new Text(theme.fg("dim", ` ${t.tool}: ${args}`), 0, 0));
|
|
352
|
+
c.addChild(new Text(truncLine(theme.fg("dim", ` ${t.tool}: ${args}`), w), 0, 0));
|
|
335
353
|
}
|
|
336
354
|
}
|
|
337
355
|
// Recent output (limited)
|
|
338
356
|
const recentLines = (rProg.recentOutput ?? []).slice(-5);
|
|
339
357
|
for (const line of recentLines) {
|
|
340
|
-
c.addChild(new Text(theme.fg("dim", ` ${line.slice(0, 100)}${line.length > 100 ? "..." : ""}`), 0, 0));
|
|
358
|
+
c.addChild(new Text(truncLine(theme.fg("dim", ` ${line.slice(0, 100)}${line.length > 100 ? "..." : ""}`), w), 0, 0));
|
|
341
359
|
}
|
|
342
360
|
}
|
|
343
361
|
|
|
@@ -346,7 +364,7 @@ export function renderSubagentResult(
|
|
|
346
364
|
|
|
347
365
|
if (d.artifacts) {
|
|
348
366
|
c.addChild(new Spacer(1));
|
|
349
|
-
c.addChild(new Text(theme.fg("dim", `Artifacts dir: ${shortenPath(d.artifacts.dir)}`), 0, 0));
|
|
367
|
+
c.addChild(new Text(truncLine(theme.fg("dim", `Artifacts dir: ${shortenPath(d.artifacts.dir)}`), w), 0, 0));
|
|
350
368
|
}
|
|
351
369
|
return c;
|
|
352
370
|
}
|