@bacnh85/pi-subagent 0.7.0 → 0.7.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/agent-format.md +12 -0
- package/extensions/agents.ts +1 -1
- package/extensions/index.ts +48 -19
- package/extensions/render.ts +3 -2
- package/package.json +1 -1
package/agent-format.md
CHANGED
|
@@ -21,9 +21,21 @@ description: ... # Required. When to use this agent.
|
|
|
21
21
|
tools: read, grep, ... # Optional. Comma-separated tool names. Defaults to all.
|
|
22
22
|
model: provider/model # Optional. Defaults to parent's model.
|
|
23
23
|
thinking: low # Optional: off|minimal|low|medium|high|xhigh|max.
|
|
24
|
+
sandbox: read-only # Optional: read-only | workspace-write. Auto-derives tool restrictions.
|
|
25
|
+
color: cyan # Optional: red|blue|green|yellow|purple|orange|pink|cyan.
|
|
24
26
|
---
|
|
25
27
|
```
|
|
26
28
|
|
|
29
|
+
### `sandbox`
|
|
30
|
+
|
|
31
|
+
- `read-only`: Restricts tools to `read`, `grep`, `find`, `ls`. Overrides any `tools` field.
|
|
32
|
+
- `workspace-write` (default): Uses the agent's `tools` list or defaults to all tools.
|
|
33
|
+
|
|
34
|
+
### `color`
|
|
35
|
+
|
|
36
|
+
Display color for the agent name in the TUI thread picker, viewer, and result summary.
|
|
37
|
+
Accepted values: `red`, `blue`, `green`, `yellow`, `purple`, `orange`, `pink`, `cyan`.
|
|
38
|
+
|
|
27
39
|
Only `name` and `description` are required.
|
|
28
40
|
|
|
29
41
|
## Body
|
package/extensions/agents.ts
CHANGED
|
@@ -169,7 +169,7 @@ function loadAgentsFromDir(
|
|
|
169
169
|
if (!validSandboxes.includes(frontmatter.sandbox)) {
|
|
170
170
|
diagnostics.push({
|
|
171
171
|
filePath,
|
|
172
|
-
issue: `Invalid sandbox mode "${frontmatter.sandbox}". Valid values: ${validSandboxes.join(", ")}.
|
|
172
|
+
issue: `Invalid sandbox mode "${frontmatter.sandbox}". Valid values: ${validSandboxes.join(", ")}. Ignoring.`,
|
|
173
173
|
severity: "warn",
|
|
174
174
|
});
|
|
175
175
|
}
|
package/extensions/index.ts
CHANGED
|
@@ -25,11 +25,12 @@ import {
|
|
|
25
25
|
getAgentDir,
|
|
26
26
|
getMarkdownTheme,
|
|
27
27
|
ModelRegistry,
|
|
28
|
+
type ThemeColor,
|
|
28
29
|
} from "@earendil-works/pi-coding-agent";
|
|
29
30
|
import { Container, Markdown, SelectList, Spacer, Text } from "@earendil-works/pi-tui";
|
|
30
31
|
import { Type } from "typebox";
|
|
31
32
|
|
|
32
|
-
import { type AgentConfig, type AgentScope, discoverAgents, formatAgentList, invalidateAgentCache } from "./agents.ts";
|
|
33
|
+
import { type AgentColor, type AgentConfig, type AgentScope, discoverAgents, formatAgentList, invalidateAgentCache } from "./agents.ts";
|
|
33
34
|
import {
|
|
34
35
|
type SubAgentResult,
|
|
35
36
|
getFinalOutput,
|
|
@@ -44,6 +45,7 @@ import {
|
|
|
44
45
|
validateAgentTools,
|
|
45
46
|
truncateParallelOutput,
|
|
46
47
|
validateExecutionRequest,
|
|
48
|
+
READ_ONLY_TOOLS,
|
|
47
49
|
MAX_CONCURRENCY,
|
|
48
50
|
MAX_PARALLEL_TASKS,
|
|
49
51
|
MAX_CHAIN_LENGTH,
|
|
@@ -159,7 +161,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
159
161
|
// Inject available agent catalog into system prompt for semantic auto-delegation
|
|
160
162
|
pi.on("before_agent_start", async (event) => {
|
|
161
163
|
const ctx = currentCtx;
|
|
162
|
-
const discovery = discoverAgents(
|
|
164
|
+
const discovery = discoverAgents(ctx?.cwd ?? process.cwd(), "both", bundledAgentsDir);
|
|
163
165
|
const catalog = discovery.agents
|
|
164
166
|
.map((a) => {
|
|
165
167
|
const modelInfo = a.model ? ` (model: ${a.model})` : " (inherits parent)";
|
|
@@ -192,7 +194,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
192
194
|
request.respond({ id: request.id, ok: false, error: `Unknown agent: ${request.agent}` });
|
|
193
195
|
return;
|
|
194
196
|
}
|
|
195
|
-
const thread = threadStore.createThread({ agentName: agent.name, task: request.task, mode: "single", color: agent.color });
|
|
197
|
+
const thread = threadStore.createThread({ agentName: agent.name, task: request.task, mode: "single", color: agent.color ? AGENT_TO_THEME_COLOR[agent.color as AgentColor] : undefined });
|
|
196
198
|
void runNamedAgent({
|
|
197
199
|
agent: request.readOnly ? { ...agent, tools: ["read", "grep", "find", "ls"] } : agent,
|
|
198
200
|
task: request.task,
|
|
@@ -289,6 +291,34 @@ export default function (pi: ExtensionAPI) {
|
|
|
289
291
|
},
|
|
290
292
|
});
|
|
291
293
|
|
|
294
|
+
/** Map AgentColor (from agent frontmatter) to ThemeColor (for pi TUI). */
|
|
295
|
+
const AGENT_TO_THEME_COLOR: Record<AgentColor, ThemeColor> = {
|
|
296
|
+
red: "error",
|
|
297
|
+
blue: "accent",
|
|
298
|
+
green: "success",
|
|
299
|
+
yellow: "warning",
|
|
300
|
+
purple: "syntaxType",
|
|
301
|
+
orange: "syntaxString",
|
|
302
|
+
pink: "customMessageLabel",
|
|
303
|
+
cyan: "syntaxVariable",
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
/** Resolve agent-defined color to a valid ThemeColor for thread creation. */
|
|
307
|
+
const agentToThemeColor = (agentName: string): ThemeColor | undefined => {
|
|
308
|
+
const ctx = currentCtx;
|
|
309
|
+
if (!ctx) return undefined;
|
|
310
|
+
const agent = discoverAgents(ctx.cwd, "both", bundledAgentsDir).agents.find(a => a.name === agentName);
|
|
311
|
+
return agent?.color ? AGENT_TO_THEME_COLOR[agent.color] : undefined;
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
/** Look up agent color by name for TUI rendering. */
|
|
315
|
+
const resolveAgentColor = (name: string): ThemeColor => {
|
|
316
|
+
const ctx = currentCtx;
|
|
317
|
+
if (!ctx) return "accent";
|
|
318
|
+
const found = discoverAgents(ctx.cwd, "both", bundledAgentsDir).agents.find(a => a.name === name);
|
|
319
|
+
return found?.color ? AGENT_TO_THEME_COLOR[found.color] : "accent";
|
|
320
|
+
};
|
|
321
|
+
|
|
292
322
|
pi.registerTool({
|
|
293
323
|
name: "subagent",
|
|
294
324
|
label: "Subagent",
|
|
@@ -438,10 +468,16 @@ export default function (pi: ExtensionAPI) {
|
|
|
438
468
|
}
|
|
439
469
|
|
|
440
470
|
// Helper: validate and normalise tools for an agent.
|
|
441
|
-
function resolveChildTools(agentTools: string[] | undefined, readOnly?: boolean): string[] {
|
|
471
|
+
function resolveChildTools(agentTools: string[] | undefined, sandbox?: string, readOnly?: boolean): string[] {
|
|
442
472
|
const defaultTools = ["read", "bash", "edit", "write", "grep", "find", "ls"];
|
|
443
|
-
|
|
444
|
-
|
|
473
|
+
let rawTools = agentTools ?? defaultTools;
|
|
474
|
+
// sandbox overrides tools: silently strip mutation tools, not an error
|
|
475
|
+
if (sandbox === "read-only") {
|
|
476
|
+
rawTools = rawTools.filter(t => READ_ONLY_TOOLS.includes(t));
|
|
477
|
+
if (rawTools.length === 0) rawTools = [...READ_ONLY_TOOLS];
|
|
478
|
+
}
|
|
479
|
+
const effectiveReadOnly = readOnly || sandbox === "read-only";
|
|
480
|
+
const result = validateAgentTools({ tools: rawTools, readOnly: effectiveReadOnly });
|
|
445
481
|
if (result.errors.length > 0) {
|
|
446
482
|
throw new Error(`Tool validation errors: ${result.errors.join("; ")}`);
|
|
447
483
|
}
|
|
@@ -505,7 +541,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
505
541
|
try {
|
|
506
542
|
// Inject parent's API key so --api-key and other runtime overrides work
|
|
507
543
|
await injectApiKey(resolved.model);
|
|
508
|
-
tools = resolveChildTools(agent.tools, isReadOnly);
|
|
544
|
+
tools = resolveChildTools(agent.tools, agent.sandbox, isReadOnly);
|
|
509
545
|
effectiveTimeoutMs = resolveChildTimeout(timeoutMs, params.timeout);
|
|
510
546
|
safeCwd = resolveChildCwd(cwd);
|
|
511
547
|
} catch (err: unknown) {
|
|
@@ -554,7 +590,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
554
590
|
task: taskWithContext,
|
|
555
591
|
mode: "chain-step",
|
|
556
592
|
toolCallId: _toolCallId,
|
|
557
|
-
color:
|
|
593
|
+
color: agentToThemeColor(step.agent),
|
|
558
594
|
});
|
|
559
595
|
const result = await runOne(
|
|
560
596
|
step.agent, taskWithContext, step.cwd,
|
|
@@ -646,7 +682,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
646
682
|
task: t.task,
|
|
647
683
|
mode: "parallel-task",
|
|
648
684
|
toolCallId: _toolCallId,
|
|
649
|
-
color:
|
|
685
|
+
color: agentToThemeColor(t.agent),
|
|
650
686
|
}),
|
|
651
687
|
);
|
|
652
688
|
|
|
@@ -761,7 +797,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
761
797
|
task: params.task,
|
|
762
798
|
mode: "single",
|
|
763
799
|
toolCallId: _toolCallId,
|
|
764
|
-
color:
|
|
800
|
+
color: agentToThemeColor(params.agent),
|
|
765
801
|
});
|
|
766
802
|
const result = await runOne(
|
|
767
803
|
params.agent, params.task, params.cwd,
|
|
@@ -814,14 +850,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
814
850
|
// TUI rendering
|
|
815
851
|
// ------------------------------------------------------------------
|
|
816
852
|
|
|
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
|
-
|
|
825
853
|
renderCall(args, theme, _context) {
|
|
826
854
|
const scope: AgentScope = args.agentScope ?? "user";
|
|
827
855
|
const fg = theme.fg.bind(theme);
|
|
@@ -890,7 +918,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
890
918
|
|
|
891
919
|
// --- Single ---
|
|
892
920
|
if (details.mode === "single" && details.results.length === 1) {
|
|
893
|
-
|
|
921
|
+
const r = details.results[0];
|
|
922
|
+
return renderSingleResult(r, expanded, theme, resolveAgentColor(r.agent));
|
|
894
923
|
}
|
|
895
924
|
|
|
896
925
|
// --- Chain ---
|
package/extensions/render.ts
CHANGED
|
@@ -188,6 +188,7 @@ export function renderSingleResult(
|
|
|
188
188
|
result: SubAgentResult,
|
|
189
189
|
expanded: boolean,
|
|
190
190
|
theme: { fg: (c: any, t: string) => string; bold: (t: string) => string },
|
|
191
|
+
agentColor?: string,
|
|
191
192
|
): Container | Text {
|
|
192
193
|
const isError = isFailedResult(result);
|
|
193
194
|
const icon = isError ? theme.fg("error", "✗") : theme.fg("success", "✓");
|
|
@@ -197,7 +198,7 @@ export function renderSingleResult(
|
|
|
197
198
|
if (expanded) {
|
|
198
199
|
const mdTheme = getMarkdownTheme();
|
|
199
200
|
const container = new Container();
|
|
200
|
-
let header = `${icon} ${theme.fg("toolTitle", theme.bold(result.agent))}`;
|
|
201
|
+
let header = `${icon} ${theme.fg(agentColor ?? "toolTitle", theme.bold(result.agent))}`;
|
|
201
202
|
if (isError && result.stopReason) {
|
|
202
203
|
const reasonColor = result.stopReason === "timeout" ? "warning" : "error";
|
|
203
204
|
header += ` ${theme.fg(reasonColor, `[${result.stopReason}]`)}`;
|
|
@@ -239,7 +240,7 @@ export function renderSingleResult(
|
|
|
239
240
|
}
|
|
240
241
|
|
|
241
242
|
// Collapsed
|
|
242
|
-
let text = `${icon} ${theme.fg("toolTitle", theme.bold(result.agent))}`;
|
|
243
|
+
let text = `${icon} ${theme.fg(agentColor ?? "toolTitle", theme.bold(result.agent))}`;
|
|
243
244
|
if (isError && result.stopReason) {
|
|
244
245
|
const reasonColor = result.stopReason === "timeout" ? "warning" : "error";
|
|
245
246
|
text += ` ${theme.fg(reasonColor, `[${result.stopReason}]`)}`;
|
package/package.json
CHANGED