@bacnh85/pi-subagent 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agents/general-purpose.md +1 -0
- package/agents/reviewer.md +2 -0
- package/agents/scout.md +2 -0
- package/agents/worker.md +1 -0
- package/extensions/agents.ts +30 -0
- package/extensions/index.ts +45 -20
- package/extensions/thread-viewer.ts +2 -1
- package/extensions/threads.ts +3 -0
- package/package.json +1 -1
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
name: general-purpose
|
|
3
3
|
description: General-purpose sub-agent for any delegated task. Use when no specialized agent fits. Good for complex research, multi-step operations, and code modifications.
|
|
4
4
|
tools: read, bash, edit, write, grep, find, ls
|
|
5
|
+
color: yellow
|
|
5
6
|
---
|
|
6
7
|
|
|
7
8
|
You are a capable coding assistant running as a sub-agent. Complete the delegated task efficiently and return a concise summary of your findings or changes.
|
package/agents/reviewer.md
CHANGED
|
@@ -3,6 +3,8 @@ name: reviewer
|
|
|
3
3
|
description: Code review specialist. Use for correctness, security, regression, and meaningful test-gap review.
|
|
4
4
|
tools: read, grep, find, ls
|
|
5
5
|
thinking: high
|
|
6
|
+
color: purple
|
|
7
|
+
sandbox: read-only
|
|
6
8
|
---
|
|
7
9
|
|
|
8
10
|
You are an independent senior code reviewer. Inspect the requested Git scope with read-only tools.
|
package/agents/scout.md
CHANGED
|
@@ -3,6 +3,8 @@ name: scout
|
|
|
3
3
|
description: Fast codebase recon that returns compressed context for handoff. Use for finding files, understanding structure, locating symbols.
|
|
4
4
|
tools: read, grep, find, ls
|
|
5
5
|
thinking: low
|
|
6
|
+
color: cyan
|
|
7
|
+
sandbox: read-only
|
|
6
8
|
---
|
|
7
9
|
|
|
8
10
|
You are a scout. Quickly investigate a codebase and return structured findings that another agent can use without re-reading everything.
|
package/agents/worker.md
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
name: worker
|
|
3
3
|
description: General-purpose coding agent with full tool access. Use only when explicitly requested for isolated implementation.
|
|
4
4
|
thinking: medium
|
|
5
|
+
color: green
|
|
5
6
|
---
|
|
6
7
|
|
|
7
8
|
You are a skilled software engineer. Implement the requested task with care and precision.
|
package/extensions/agents.ts
CHANGED
|
@@ -13,12 +13,16 @@ import { CONFIG_DIR_NAME, getAgentDir, parseFrontmatter } from "@earendil-works/
|
|
|
13
13
|
|
|
14
14
|
export type AgentScope = "user" | "project" | "both";
|
|
15
15
|
|
|
16
|
+
export type AgentColor = "red" | "blue" | "green" | "yellow" | "purple" | "orange" | "pink" | "cyan";
|
|
17
|
+
|
|
16
18
|
export interface AgentConfig {
|
|
17
19
|
name: string;
|
|
18
20
|
description: string;
|
|
19
21
|
tools?: string[];
|
|
20
22
|
model?: string;
|
|
21
23
|
thinking?: "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
|
|
24
|
+
sandbox?: "read-only" | "workspace-write";
|
|
25
|
+
color?: AgentColor;
|
|
22
26
|
systemPrompt: string;
|
|
23
27
|
source: "user" | "project" | "bundled";
|
|
24
28
|
filePath: string;
|
|
@@ -160,6 +164,26 @@ function loadAgentsFromDir(
|
|
|
160
164
|
}
|
|
161
165
|
}
|
|
162
166
|
|
|
167
|
+
if (typeof frontmatter.sandbox === "string" && frontmatter.sandbox) {
|
|
168
|
+
const validSandboxes = ["read-only", "workspace-write"];
|
|
169
|
+
if (!validSandboxes.includes(frontmatter.sandbox)) {
|
|
170
|
+
diagnostics.push({
|
|
171
|
+
filePath,
|
|
172
|
+
issue: `Invalid sandbox mode "${frontmatter.sandbox}". Valid values: ${validSandboxes.join(", ")}. Using default.`,
|
|
173
|
+
severity: "warn",
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const VALID_COLORS = ["red", "blue", "green", "yellow", "purple", "orange", "pink", "cyan"] as const;
|
|
179
|
+
if (typeof frontmatter.color === "string" && frontmatter.color && !VALID_COLORS.includes(frontmatter.color as any)) {
|
|
180
|
+
diagnostics.push({
|
|
181
|
+
filePath,
|
|
182
|
+
issue: `Invalid color "${frontmatter.color}". Valid values: ${VALID_COLORS.join(", ")}. Ignoring.`,
|
|
183
|
+
severity: "warn",
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
163
187
|
agents.push({
|
|
164
188
|
name: frontmatter.name,
|
|
165
189
|
description: frontmatter.description,
|
|
@@ -168,6 +192,12 @@ function loadAgentsFromDir(
|
|
|
168
192
|
thinking: typeof frontmatter.thinking === "string" && ["off", "minimal", "low", "medium", "high", "xhigh", "max"].includes(frontmatter.thinking)
|
|
169
193
|
? frontmatter.thinking as AgentConfig["thinking"]
|
|
170
194
|
: undefined,
|
|
195
|
+
sandbox: typeof frontmatter.sandbox === "string" && ["read-only", "workspace-write"].includes(frontmatter.sandbox)
|
|
196
|
+
? frontmatter.sandbox as "read-only" | "workspace-write"
|
|
197
|
+
: undefined,
|
|
198
|
+
color: typeof frontmatter.color === "string" && VALID_COLORS.includes(frontmatter.color as any)
|
|
199
|
+
? frontmatter.color as AgentColor
|
|
200
|
+
: undefined,
|
|
171
201
|
systemPrompt: body,
|
|
172
202
|
source,
|
|
173
203
|
filePath,
|
package/extensions/index.ts
CHANGED
|
@@ -153,21 +153,34 @@ export default function (pi: ExtensionAPI) {
|
|
|
153
153
|
threadStore.clear();
|
|
154
154
|
});
|
|
155
155
|
|
|
156
|
-
// Proactively steer agents toward sub-agent delegation when users mention it
|
|
157
|
-
pi.on("before_agent_start", async (event) => {
|
|
158
|
-
const prompt = event.prompt.toLowerCase();
|
|
159
|
-
if (/\b(delegate to|use a subagent|run in parallel|spawn an agent|scout|review this|chain|worker agent)\b/.test(prompt)) {
|
|
160
|
-
return {
|
|
161
|
-
systemPrompt:
|
|
162
|
-
event.systemPrompt +
|
|
163
|
-
"\n\nThe subagent tool is available for delegating tasks to specialized agents with isolated context. Use /subagent to list available agents. Bundled: scout (fast recon), reviewer (code review), worker (implementation), general-purpose (fallback). Modes: single, parallel (max 8), chain.",
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
});
|
|
167
|
-
|
|
168
156
|
// Resolve bundled agents directory relative to this extension file
|
|
169
157
|
const bundledAgentsDir = path.resolve(__dirname, "../agents");
|
|
170
158
|
|
|
159
|
+
// Inject available agent catalog into system prompt for semantic auto-delegation
|
|
160
|
+
pi.on("before_agent_start", async (event) => {
|
|
161
|
+
const ctx = currentCtx;
|
|
162
|
+
const discovery = discoverAgents(event.cwd ?? ctx?.cwd ?? process.cwd(), "both", bundledAgentsDir);
|
|
163
|
+
const catalog = discovery.agents
|
|
164
|
+
.map((a) => {
|
|
165
|
+
const modelInfo = a.model ? ` (model: ${a.model})` : " (inherits parent)";
|
|
166
|
+
const thinkingInfo = a.thinking ? `, thinking: ${a.thinking}` : "";
|
|
167
|
+
const sandboxInfo = a.sandbox ? `, sandbox: ${a.sandbox}` : "";
|
|
168
|
+
return `- **${a.name}**: ${a.description}${modelInfo}${thinkingInfo}${sandboxInfo}`;
|
|
169
|
+
})
|
|
170
|
+
.join("\n");
|
|
171
|
+
return {
|
|
172
|
+
systemPrompt:
|
|
173
|
+
event.systemPrompt +
|
|
174
|
+
`\n\n## Available Subagents\n${catalog}\n\n` +
|
|
175
|
+
"The subagent tool can delegate tasks to these specialized agents with isolated context. " +
|
|
176
|
+
"Use for read-heavy exploration, parallel analysis, or work that would flood the main context.\n" +
|
|
177
|
+
"Prefer **scout** for fast read-only exploration. " +
|
|
178
|
+
"Prefer **reviewer** for code review (high thinking, read-only). " +
|
|
179
|
+
"Prefer **worker** for implementation (medium thinking, all tools). " +
|
|
180
|
+
"Modes: single, parallel (max 8 tasks, 4 concurrent), chain.",
|
|
181
|
+
};
|
|
182
|
+
});
|
|
183
|
+
|
|
171
184
|
// Public one-request/one-response service used by pi-review.
|
|
172
185
|
pi.events.on(SUBAGENT_REQUEST_EVENT, (raw) => {
|
|
173
186
|
const request = raw as SubagentRunRequest;
|
|
@@ -179,7 +192,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
179
192
|
request.respond({ id: request.id, ok: false, error: `Unknown agent: ${request.agent}` });
|
|
180
193
|
return;
|
|
181
194
|
}
|
|
182
|
-
const thread = threadStore.createThread({ agentName: agent.name, task: request.task, mode: "single" });
|
|
195
|
+
const thread = threadStore.createThread({ agentName: agent.name, task: request.task, mode: "single", color: agent.color });
|
|
183
196
|
void runNamedAgent({
|
|
184
197
|
agent: request.readOnly ? { ...agent, tools: ["read", "grep", "find", "ls"] } : agent,
|
|
185
198
|
task: request.task,
|
|
@@ -541,6 +554,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
541
554
|
task: taskWithContext,
|
|
542
555
|
mode: "chain-step",
|
|
543
556
|
toolCallId: _toolCallId,
|
|
557
|
+
color: agents.find(a => a.name === step.agent)?.color,
|
|
544
558
|
});
|
|
545
559
|
const result = await runOne(
|
|
546
560
|
step.agent, taskWithContext, step.cwd,
|
|
@@ -632,6 +646,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
632
646
|
task: t.task,
|
|
633
647
|
mode: "parallel-task",
|
|
634
648
|
toolCallId: _toolCallId,
|
|
649
|
+
color: agents.find(a => a.name === t.agent)?.color,
|
|
635
650
|
}),
|
|
636
651
|
);
|
|
637
652
|
|
|
@@ -746,6 +761,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
746
761
|
task: params.task,
|
|
747
762
|
mode: "single",
|
|
748
763
|
toolCallId: _toolCallId,
|
|
764
|
+
color: agents.find(a => a.name === params.agent)?.color,
|
|
749
765
|
});
|
|
750
766
|
const result = await runOne(
|
|
751
767
|
params.agent, params.task, params.cwd,
|
|
@@ -798,6 +814,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
798
814
|
// TUI rendering
|
|
799
815
|
// ------------------------------------------------------------------
|
|
800
816
|
|
|
817
|
+
/** Look up agent color by name for TUI rendering. */
|
|
818
|
+
const resolveAgentColor = (name: string): string => {
|
|
819
|
+
const ctx = currentCtx;
|
|
820
|
+
if (!ctx) return "accent";
|
|
821
|
+
const found = discoverAgents(ctx.cwd, "both", bundledAgentsDir).agents.find(a => a.name === name);
|
|
822
|
+
return found?.color ?? "accent";
|
|
823
|
+
};
|
|
824
|
+
|
|
801
825
|
renderCall(args, theme, _context) {
|
|
802
826
|
const scope: AgentScope = args.agentScope ?? "user";
|
|
803
827
|
const fg = theme.fg.bind(theme);
|
|
@@ -816,7 +840,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
816
840
|
"\n " +
|
|
817
841
|
fg("muted", `${i + 1}.`) +
|
|
818
842
|
" " +
|
|
819
|
-
fg(
|
|
843
|
+
fg(resolveAgentColor(step.agent), step.agent) +
|
|
820
844
|
fg("dim", ` ${preview}`);
|
|
821
845
|
}
|
|
822
846
|
if (args.chain.length > 3)
|
|
@@ -832,7 +856,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
832
856
|
fg("muted", ` [${scope}]`);
|
|
833
857
|
for (const t of args.tasks.slice(0, 3)) {
|
|
834
858
|
const preview = t.task.length > 40 ? `${t.task.slice(0, 40)}...` : t.task;
|
|
835
|
-
text += `\n ${fg(
|
|
859
|
+
text += `\n ${fg(resolveAgentColor(t.agent), t.agent)}${fg("dim", ` ${preview}`)}`;
|
|
836
860
|
}
|
|
837
861
|
if (args.tasks.length > 3)
|
|
838
862
|
text += `\n ${fg("muted", `... +${args.tasks.length - 3} more`)}`;
|
|
@@ -848,7 +872,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
848
872
|
: "...";
|
|
849
873
|
let text =
|
|
850
874
|
fg("toolTitle", theme.bold("subagent ")) +
|
|
851
|
-
fg(
|
|
875
|
+
fg(resolveAgentColor(agentName), agentName) +
|
|
852
876
|
fg("muted", ` [${scope}]`);
|
|
853
877
|
text += `\n ${fg("dim", preview)}`;
|
|
854
878
|
return new Text(text, 0, 0);
|
|
@@ -895,7 +919,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
895
919
|
container.addChild(
|
|
896
920
|
new Text(
|
|
897
921
|
fg("muted", `─── Step ${r.exitCode !== -1 ? "" : "?"}: `) +
|
|
898
|
-
fg(
|
|
922
|
+
fg(resolveAgentColor(r.agent), r.agent) +
|
|
899
923
|
` ${stepIcon}`,
|
|
900
924
|
0,
|
|
901
925
|
0,
|
|
@@ -930,7 +954,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
930
954
|
fg("accent", `${successCount}/${details.results.length} steps`);
|
|
931
955
|
for (const r of details.results) {
|
|
932
956
|
const stepIcon = isFailedResult(r) ? fg("error", "✗") : fg("success", "✓");
|
|
933
|
-
|
|
957
|
+
const color = resolveAgentColor(r.agent);
|
|
958
|
+
text += `\n ${stepIcon} ${fg(color, r.agent)}`;
|
|
934
959
|
}
|
|
935
960
|
const totalUsage = formatUsageStats(aggregateUsage(details.results));
|
|
936
961
|
if (totalUsage) text += `\n${fg("dim", totalUsage)}`;
|
|
@@ -973,7 +998,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
973
998
|
: fg("success", "✓");
|
|
974
999
|
container.addChild(
|
|
975
1000
|
new Text(
|
|
976
|
-
fg("muted", "─── ") + fg(
|
|
1001
|
+
fg("muted", "─── ") + fg(resolveAgentColor(r.agent), r.agent) + ` ${taskIcon}`,
|
|
977
1002
|
0,
|
|
978
1003
|
0,
|
|
979
1004
|
),
|
|
@@ -1013,7 +1038,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1013
1038
|
: isFailedResult(r)
|
|
1014
1039
|
? fg("error", "✗")
|
|
1015
1040
|
: fg("success", "✓");
|
|
1016
|
-
text += `\n ${taskIcon} ${fg(
|
|
1041
|
+
text += `\n ${taskIcon} ${fg(resolveAgentColor(r.agent), r.agent)}`;
|
|
1017
1042
|
}
|
|
1018
1043
|
if (!isRunning) {
|
|
1019
1044
|
const totalUsage = formatUsageStats(aggregateUsage(details.results));
|
|
@@ -123,7 +123,8 @@ export class ThreadViewer {
|
|
|
123
123
|
else if (this.thread.mode === "chain-step") modeLabel = t.fg("muted", " [chain]");
|
|
124
124
|
|
|
125
125
|
// Header
|
|
126
|
-
|
|
126
|
+
const agentColor = this.thread.color ?? "accent";
|
|
127
|
+
let header = `${icon} ${t.fg(agentColor, t.bold(this.thread.agentName))}${modeLabel}`;
|
|
127
128
|
if (status === "running") header += ` ${t.fg("warning", "(running...)")}`;
|
|
128
129
|
else if (status === "aborted") header += ` ${t.fg("error", "[aborted]")}`;
|
|
129
130
|
if (result && isErr && result.stopReason && result.stopReason !== "error" && result.stopReason !== "aborted") {
|
package/extensions/threads.ts
CHANGED
|
@@ -23,6 +23,7 @@ export interface SubagentThread {
|
|
|
23
23
|
status: ThreadStatus;
|
|
24
24
|
result?: SubAgentResult;
|
|
25
25
|
toolCallId?: string;
|
|
26
|
+
color?: string;
|
|
26
27
|
createdAt: number;
|
|
27
28
|
updatedAt: number;
|
|
28
29
|
}
|
|
@@ -53,6 +54,7 @@ export class ThreadStore {
|
|
|
53
54
|
task: string;
|
|
54
55
|
mode: ThreadMode;
|
|
55
56
|
toolCallId?: string;
|
|
57
|
+
color?: string;
|
|
56
58
|
}): SubagentThread {
|
|
57
59
|
const id = cryptoGenId();
|
|
58
60
|
const now = Date.now();
|
|
@@ -63,6 +65,7 @@ export class ThreadStore {
|
|
|
63
65
|
mode: params.mode,
|
|
64
66
|
status: "running",
|
|
65
67
|
toolCallId: params.toolCallId,
|
|
68
|
+
color: params.color,
|
|
66
69
|
createdAt: now,
|
|
67
70
|
updatedAt: now,
|
|
68
71
|
};
|
package/package.json
CHANGED