pi-subagents 0.8.5 → 0.9.1
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 +31 -0
- package/README.md +9 -4
- package/agent-management.ts +19 -9
- package/agent-manager-detail.ts +5 -2
- package/agent-manager-list.ts +9 -7
- 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 +14 -6
- package/async-execution.ts +4 -0
- package/package.json +2 -1
- package/pi-spawn.ts +22 -1
- package/subagent-runner.ts +4 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,37 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.9.1] - 2026-02-17
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- 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`.
|
|
9
|
+
- `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.
|
|
10
|
+
- 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 `[built]` instead of falling through to `[proj]`.
|
|
11
|
+
- 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"`.
|
|
12
|
+
- Agent Manager `loadEntries` suppresses builtins overridden by user/project agents, preventing duplicate entries in the TUI list.
|
|
13
|
+
- `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.
|
|
14
|
+
- `handleCreate` now warns when creating an agent that shadows a builtin (informational, not an error).
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
- Simplified Agent Manager header from per-scope breakdown to total count (per-row badges already show scope).
|
|
18
|
+
- Reviewer builtin model changed from `openai/gpt-5.2` to `openai-codex/gpt-5.3-codex`.
|
|
19
|
+
- Removed `code-reviewer` builtin agent (redundant with `reviewer`).
|
|
20
|
+
|
|
21
|
+
## [0.9.0] - 2026-02-17
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
- **Builtin agents** — the extension now ships with a default set of agent definitions in `agents/`. These are loaded with lowest priority so user and project agents always override them. New users get a useful set of agents out of the box without manual setup.
|
|
25
|
+
- `scout` — fast codebase recon (claude-haiku-4-5)
|
|
26
|
+
- `planner` — implementation plans from context (claude-opus-4-6, thinking: high)
|
|
27
|
+
- `worker` — general-purpose execution (claude-sonnet-4-6)
|
|
28
|
+
- `reviewer` — validates implementation against plans (gpt-5.3-codex, thinking: high)
|
|
29
|
+
- `context-builder` — analyzes requirements and codebase (claude-sonnet-4-6)
|
|
30
|
+
- `researcher` — autonomous web research with search, evaluation, and synthesis (claude-sonnet-4-6)
|
|
31
|
+
- **`"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).
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
- Async subagent session sharing no longer fails with `ERR_PACKAGE_PATH_NOT_EXPORTED`. The runner tried `require.resolve("@mariozechner/pi-coding-agent/package.json")` to find pi's HTML export module, but pi's `exports` map doesn't include that subpath. The fix resolves the package root in the main pi process by walking up from `process.argv[1]` and passes it to the spawned runner through the config, bypassing `require.resolve` entirely. The Windows CLI resolution fallback in `getPiSpawnCommand` benefits from the same walk-up function.
|
|
35
|
+
|
|
5
36
|
## [0.8.5] - 2026-02-16
|
|
6
37
|
|
|
7
38
|
### Fixed
|
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" ? "[built]" : 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
|
|
|
@@ -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" ? "[built]" : 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,12 +5,15 @@
|
|
|
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";
|
|
11
12
|
|
|
12
13
|
export type AgentScope = "user" | "project" | "both";
|
|
13
14
|
|
|
15
|
+
export type AgentSource = "builtin" | "user" | "project";
|
|
16
|
+
|
|
14
17
|
export interface AgentConfig {
|
|
15
18
|
name: string;
|
|
16
19
|
description: string;
|
|
@@ -19,7 +22,7 @@ export interface AgentConfig {
|
|
|
19
22
|
model?: string;
|
|
20
23
|
thinking?: string;
|
|
21
24
|
systemPrompt: string;
|
|
22
|
-
source:
|
|
25
|
+
source: AgentSource;
|
|
23
26
|
filePath: string;
|
|
24
27
|
skills?: string[];
|
|
25
28
|
extensions?: string[];
|
|
@@ -44,7 +47,7 @@ export interface ChainStepConfig {
|
|
|
44
47
|
export interface ChainConfig {
|
|
45
48
|
name: string;
|
|
46
49
|
description: string;
|
|
47
|
-
source:
|
|
50
|
+
source: AgentSource;
|
|
48
51
|
filePath: string;
|
|
49
52
|
steps: ChainStepConfig[];
|
|
50
53
|
extraFields?: Record<string, string>;
|
|
@@ -85,7 +88,7 @@ function parseFrontmatter(content: string): { frontmatter: Record<string, string
|
|
|
85
88
|
return { frontmatter, body };
|
|
86
89
|
}
|
|
87
90
|
|
|
88
|
-
function loadAgentsFromDir(dir: string, source:
|
|
91
|
+
function loadAgentsFromDir(dir: string, source: AgentSource): AgentConfig[] {
|
|
89
92
|
const agents: AgentConfig[] = [];
|
|
90
93
|
|
|
91
94
|
if (!fs.existsSync(dir)) {
|
|
@@ -184,7 +187,7 @@ function loadAgentsFromDir(dir: string, source: "user" | "project"): AgentConfig
|
|
|
184
187
|
return agents;
|
|
185
188
|
}
|
|
186
189
|
|
|
187
|
-
function loadChainsFromDir(dir: string, source:
|
|
190
|
+
function loadChainsFromDir(dir: string, source: AgentSource): ChainConfig[] {
|
|
188
191
|
const chains: ChainConfig[] = [];
|
|
189
192
|
|
|
190
193
|
if (!fs.existsSync(dir)) {
|
|
@@ -240,18 +243,22 @@ function findNearestProjectAgentsDir(cwd: string): string | null {
|
|
|
240
243
|
}
|
|
241
244
|
}
|
|
242
245
|
|
|
246
|
+
const BUILTIN_AGENTS_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)), "agents");
|
|
247
|
+
|
|
243
248
|
export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryResult {
|
|
244
249
|
const userDir = path.join(os.homedir(), ".pi", "agent", "agents");
|
|
245
250
|
const projectAgentsDir = findNearestProjectAgentsDir(cwd);
|
|
246
251
|
|
|
252
|
+
const builtinAgents = loadAgentsFromDir(BUILTIN_AGENTS_DIR, "builtin");
|
|
247
253
|
const userAgents = scope === "project" ? [] : loadAgentsFromDir(userDir, "user");
|
|
248
254
|
const projectAgents = scope === "user" || !projectAgentsDir ? [] : loadAgentsFromDir(projectAgentsDir, "project");
|
|
249
|
-
const agents = mergeAgentsForScope(scope, userAgents, projectAgents);
|
|
255
|
+
const agents = mergeAgentsForScope(scope, userAgents, projectAgents, builtinAgents);
|
|
250
256
|
|
|
251
257
|
return { agents, projectAgentsDir };
|
|
252
258
|
}
|
|
253
259
|
|
|
254
260
|
export function discoverAgentsAll(cwd: string): {
|
|
261
|
+
builtin: AgentConfig[];
|
|
255
262
|
user: AgentConfig[];
|
|
256
263
|
project: AgentConfig[];
|
|
257
264
|
chains: ChainConfig[];
|
|
@@ -261,6 +268,7 @@ export function discoverAgentsAll(cwd: string): {
|
|
|
261
268
|
const userDir = path.join(os.homedir(), ".pi", "agent", "agents");
|
|
262
269
|
const projectDir = findNearestProjectAgentsDir(cwd);
|
|
263
270
|
|
|
271
|
+
const builtin = loadAgentsFromDir(BUILTIN_AGENTS_DIR, "builtin");
|
|
264
272
|
const user = loadAgentsFromDir(userDir, "user");
|
|
265
273
|
const project = projectDir ? loadAgentsFromDir(projectDir, "project") : [];
|
|
266
274
|
const chains = [
|
|
@@ -268,5 +276,5 @@ export function discoverAgentsAll(cwd: string): {
|
|
|
268
276
|
...(projectDir ? loadChainsFromDir(projectDir, "project") : []),
|
|
269
277
|
];
|
|
270
278
|
|
|
271
|
-
return { user, project, chains, userDir, projectDir };
|
|
279
|
+
return { builtin, user, project, chains, userDir, projectDir };
|
|
272
280
|
}
|
package/async-execution.ts
CHANGED
|
@@ -13,6 +13,7 @@ import type { AgentConfig } from "./agents.js";
|
|
|
13
13
|
import { applyThinkingSuffix } from "./execution.js";
|
|
14
14
|
import { injectSingleOutputInstruction, resolveSingleOutputPath } from "./single-output.js";
|
|
15
15
|
import { isParallelStep, resolveStepBehavior, type ChainStep, type SequentialStep, type StepOverrides } from "./settings.js";
|
|
16
|
+
import { resolvePiPackageRoot } from "./pi-spawn.js";
|
|
16
17
|
import { buildSkillInjection, normalizeSkillInput, resolveSkills } from "./skills.js";
|
|
17
18
|
import {
|
|
18
19
|
type ArtifactConfig,
|
|
@@ -23,6 +24,7 @@ import {
|
|
|
23
24
|
} from "./types.js";
|
|
24
25
|
|
|
25
26
|
const require = createRequire(import.meta.url);
|
|
27
|
+
const piPackageRoot = resolvePiPackageRoot();
|
|
26
28
|
const jitiCliPath: string | undefined = (() => {
|
|
27
29
|
const candidates: Array<() => string> = [
|
|
28
30
|
() => path.join(path.dirname(require.resolve("jiti/package.json")), "lib/jiti-cli.mjs"),
|
|
@@ -188,6 +190,7 @@ export function executeAsyncChain(
|
|
|
188
190
|
sessionDir: sessionRoot ? path.join(sessionRoot, `async-${id}`) : undefined,
|
|
189
191
|
asyncDir,
|
|
190
192
|
sessionId: ctx.currentSessionId,
|
|
193
|
+
piPackageRoot,
|
|
191
194
|
},
|
|
192
195
|
id,
|
|
193
196
|
runnerCwd,
|
|
@@ -265,6 +268,7 @@ export function executeAsyncSingle(
|
|
|
265
268
|
sessionDir: sessionRoot ? path.join(sessionRoot, `async-${id}`) : undefined,
|
|
266
269
|
asyncDir,
|
|
267
270
|
sessionId: ctx.currentSessionId,
|
|
271
|
+
piPackageRoot,
|
|
268
272
|
},
|
|
269
273
|
id,
|
|
270
274
|
runnerCwd,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-subagents",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1",
|
|
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/pi-spawn.ts
CHANGED
|
@@ -4,6 +4,22 @@ import * as path from "node:path";
|
|
|
4
4
|
|
|
5
5
|
const require = createRequire(import.meta.url);
|
|
6
6
|
|
|
7
|
+
export function resolvePiPackageRoot(): string | undefined {
|
|
8
|
+
try {
|
|
9
|
+
const entry = process.argv[1];
|
|
10
|
+
if (!entry) return undefined;
|
|
11
|
+
let dir = path.dirname(fs.realpathSync(entry));
|
|
12
|
+
while (dir !== path.dirname(dir)) {
|
|
13
|
+
try {
|
|
14
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(dir, "package.json"), "utf-8"));
|
|
15
|
+
if (pkg.name === "@mariozechner/pi-coding-agent") return dir;
|
|
16
|
+
} catch {}
|
|
17
|
+
dir = path.dirname(dir);
|
|
18
|
+
}
|
|
19
|
+
} catch {}
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
7
23
|
export interface PiSpawnDeps {
|
|
8
24
|
platform?: NodeJS.Platform;
|
|
9
25
|
execPath?: string;
|
|
@@ -11,6 +27,7 @@ export interface PiSpawnDeps {
|
|
|
11
27
|
existsSync?: (filePath: string) => boolean;
|
|
12
28
|
readFileSync?: (filePath: string, encoding: "utf-8") => string;
|
|
13
29
|
resolvePackageJson?: () => string;
|
|
30
|
+
piPackageRoot?: string;
|
|
14
31
|
}
|
|
15
32
|
|
|
16
33
|
export interface PiSpawnCommand {
|
|
@@ -40,7 +57,11 @@ export function resolveWindowsPiCliScript(deps: PiSpawnDeps = {}): string | unde
|
|
|
40
57
|
}
|
|
41
58
|
|
|
42
59
|
try {
|
|
43
|
-
const resolvePackageJson = deps.resolvePackageJson ?? (() =>
|
|
60
|
+
const resolvePackageJson = deps.resolvePackageJson ?? (() => {
|
|
61
|
+
const root = deps.piPackageRoot ?? resolvePiPackageRoot();
|
|
62
|
+
if (root) return path.join(root, "package.json");
|
|
63
|
+
return require.resolve("@mariozechner/pi-coding-agent/package.json");
|
|
64
|
+
});
|
|
44
65
|
const packageJsonPath = resolvePackageJson();
|
|
45
66
|
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8")) as {
|
|
46
67
|
bin?: string | Record<string, string>;
|
package/subagent-runner.ts
CHANGED
|
@@ -44,6 +44,7 @@ interface SubagentRunConfig {
|
|
|
44
44
|
sessionDir?: string;
|
|
45
45
|
asyncDir: string;
|
|
46
46
|
sessionId?: string | null;
|
|
47
|
+
piPackageRoot?: string;
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
interface StepResult {
|
|
@@ -134,8 +135,8 @@ function runPiStreaming(
|
|
|
134
135
|
});
|
|
135
136
|
}
|
|
136
137
|
|
|
137
|
-
async function exportSessionHtml(sessionFile: string, outputDir: string): Promise<string> {
|
|
138
|
-
const pkgRoot = path.dirname(require.resolve("@mariozechner/pi-coding-agent/package.json"));
|
|
138
|
+
async function exportSessionHtml(sessionFile: string, outputDir: string, piPackageRoot?: string): Promise<string> {
|
|
139
|
+
const pkgRoot = piPackageRoot ?? path.dirname(require.resolve("@mariozechner/pi-coding-agent/package.json"));
|
|
139
140
|
const exportModulePath = path.join(pkgRoot, "dist", "core", "export-html", "index.js");
|
|
140
141
|
const moduleUrl = pathToFileURL(exportModulePath).href;
|
|
141
142
|
const mod = await import(moduleUrl);
|
|
@@ -516,7 +517,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
516
517
|
sessionFile = findLatestSessionFile(config.sessionDir) ?? undefined;
|
|
517
518
|
if (sessionFile) {
|
|
518
519
|
try {
|
|
519
|
-
const htmlPath = await exportSessionHtml(sessionFile, config.sessionDir);
|
|
520
|
+
const htmlPath = await exportSessionHtml(sessionFile, config.sessionDir, config.piPackageRoot);
|
|
520
521
|
const share = createShareLink(htmlPath);
|
|
521
522
|
if ("error" in share) shareError = share.error;
|
|
522
523
|
else {
|