@arhen/pi-core-subagent 1.0.9 → 1.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arhen/pi-core-subagent",
3
- "version": "1.0.9",
3
+ "version": "1.1.1",
4
4
  "type": "module",
5
5
  "description": "pi extension: fast in-process subagents with single/parallel/chain, background runs, intercom and agent-to-agent mailbox. Leader defines agents inline.",
6
6
  "license": "MIT",
package/src/index.ts CHANGED
@@ -18,7 +18,6 @@ import { StringEnum, type Api, type AssistantMessage, type Model } from "@earend
18
18
  import { Text, truncateToWidth } from "@earendil-works/pi-tui";
19
19
  import type { Component, TUI } from "@earendil-works/pi-tui";
20
20
  import { Type } from "typebox";
21
- import { lookupAgent } from "./agents.ts";
22
21
  import { CHILD_TALK_TOOLS, createChildTools, createWatchdog, type ChildHandlers } from "./child.ts";
23
22
  import { createMailbox, type Mailbox } from "./mailbox.ts";
24
23
 
@@ -631,17 +630,16 @@ class SubagentManager {
631
630
 
632
631
  // Inline params win; otherwise fall back to an existing agent file
633
632
  // (~/.agents, .pi/agents, user dir). Never creates files.
634
- const fileAgent = input.prompt?.trim() ? undefined : lookupAgent(task.agent, task.cwd);
635
- const prompt = input.prompt?.trim() || fileAgent?.prompt;
636
- const thinking = input.thinking ?? fileAgent?.thinking;
637
- const baseTools = input.tools ?? (input.write ? WRITE_TOOLS : fileAgent?.tools ?? READONLY_TOOLS);
633
+ const prompt = input.prompt?.trim();
634
+ const thinking = input.thinking;
635
+ const baseTools = input.tools ?? (input.write ? WRITE_TOOLS : READONLY_TOOLS);
638
636
  const tools = [...baseTools, ...(run.allowIntercom ? CHILD_TALK_TOOLS : [])];
639
637
 
640
638
  // Model + thinking resolve against the pi model registry; a bad request
641
639
  // fails the TASK with a helpful message, not the whole run.
642
640
  let model: Model<Api> | undefined;
643
641
  try {
644
- model = resolveChildModel(ctx, input.model ?? fileAgent?.model);
642
+ model = resolveChildModel(ctx, input.model);
645
643
  validateThinking(model, thinking);
646
644
  } catch (err) {
647
645
  this.updateTask(run, task, {
@@ -655,7 +653,7 @@ class SubagentManager {
655
653
  this.updateTask(run, task, {
656
654
  status: "starting",
657
655
  startedAt: Date.now(),
658
- model: input.model ?? fileAgent?.model,
656
+ model: input.model,
659
657
  thinking,
660
658
  tools,
661
659
  }, ctx, onUpdate);
@@ -1009,7 +1007,7 @@ let eventSeq = 0;
1009
1007
 
1010
1008
  const TaskItem = Type.Object({
1011
1009
  id: Type.Optional(Type.String({ description: "Optional stable task id" })),
1012
- agent: Type.String({ minLength: 1, description: "Agent name. Invent it (inline prompt below), or reuse a name from .agents/, .pi/agents/, or ~/.pi/agent/agents/ to inherit its prompt and toolset. Never creates files." }),
1010
+ agent: Type.String({ minLength: 1, description: "Agent name you invent. Always define the agent inline: prompt (system prompt) + toolset (write: true for write access). Never create agent files." }),
1013
1011
  task: Type.String({ minLength: 1, description: "Task for this agent" }),
1014
1012
  prompt: Type.Optional(Type.String({ description: "System prompt defining this agent's behavior. Optional — a minimal default is used." })),
1015
1013
  write: Type.Optional(Type.Boolean({ description: "true = write toolset (read, bash, edit, write); default false = read-only (read, grep, find, ls)" })),
@@ -1135,7 +1133,19 @@ export default function (pi: ExtensionAPI) {
1135
1133
  renderCall(args, theme) {
1136
1134
  const mode = args.chain?.length ? `chain ${args.chain.length}` : args.tasks?.length ? `parallel ${args.tasks.length}` : `single ${args.agent ?? "?"}`;
1137
1135
  const flags = [args.background ? "bg" : "", args.allowIntercom ? "talk" : ""].filter(Boolean).join(" ");
1138
- return new Text(`${theme.fg("toolTitle", theme.bold("subagent"))} ${theme.fg("accent", mode)}${flags ? ` ${theme.fg("muted", `[${flags}]`)}` : ""}`, 0, 0);
1136
+ // Params used, dimmed: model, thinking, toolset, per-task write count.
1137
+ const tasks = args.tasks ?? args.chain ?? [];
1138
+ const writeCount = tasks.filter((t: any) => t.write).length;
1139
+ const parts: string[] = [];
1140
+ if (args.model) parts.push(args.model);
1141
+ if (args.thinking) parts.push(args.thinking);
1142
+ if (args.write) parts.push("write");
1143
+ else if (writeCount === 0) parts.push("read-only");
1144
+ if (writeCount > 0) parts.push(`${writeCount} write`);
1145
+ if (args.concurrency) parts.push(`c${args.concurrency}`);
1146
+ if (args.maxRuntimeMs) parts.push(`${Math.round(args.maxRuntimeMs / 60000)}m`);
1147
+ const params = parts.length > 0 ? `\n ${theme.fg("dim", parts.join(" · "))}` : "";
1148
+ return new Text(`${theme.fg("toolTitle", theme.bold("subagent"))} ${theme.fg("accent", mode)}${flags ? ` ${theme.fg("muted", `[${flags}]`)}` : ""}${params}`, 0, 0);
1139
1149
  },
1140
1150
  renderResult(result, { expanded }, theme) {
1141
1151
  const run = result.details?.run;
package/src/agents.ts DELETED
@@ -1,70 +0,0 @@
1
- /**
2
- * Optional agent-file lookup — fallback for inline definitions.
3
- *
4
- * The leader defines agents inline (prompt + toolset). If a task has no inline
5
- * prompt, we LOOK UP a file by agent name — never create one:
6
- * <cwd>/.agents/<name>.md (nearest project dir first)
7
- * <cwd>/.pi/agents/<name>.md
8
- * ~/.pi/agent/agents/<name>.md (user)
9
- * First match wins; explicit inline params always override file contents.
10
- */
11
-
12
- import * as fs from "node:fs";
13
- import * as path from "node:path";
14
- import { getAgentDir, parseFrontmatter } from "@earendil-works/pi-coding-agent";
15
-
16
- export interface FileAgent {
17
- prompt: string;
18
- tools?: string[];
19
- model?: string;
20
- thinking?: string;
21
- }
22
-
23
- function projectAgentDirs(cwd: string): string[] {
24
- const out: string[] = [];
25
- let dir = cwd;
26
- for (;;) {
27
- for (const sub of [".agents", path.join(".pi", "agents")]) {
28
- const candidate = path.join(dir, sub);
29
- try {
30
- if (fs.statSync(candidate).isDirectory()) out.push(candidate);
31
- } catch {
32
- /* missing dir is fine */
33
- }
34
- }
35
- const parent = path.dirname(dir);
36
- if (parent === dir) break;
37
- dir = parent;
38
- }
39
- return out;
40
- }
41
-
42
- export function lookupAgent(name: string, cwd: string): FileAgent | undefined {
43
- const dirs = [...projectAgentDirs(cwd), path.join(getAgentDir(), "agents")];
44
- for (const dir of dirs) {
45
- const filePath = path.join(dir, `${name}.md`);
46
- let content: string;
47
- try {
48
- content = fs.readFileSync(filePath, "utf-8");
49
- } catch {
50
- continue;
51
- }
52
- try {
53
- const { frontmatter, body } = parseFrontmatter<Record<string, string>>(content);
54
- if (!frontmatter.name || frontmatter.name !== name) continue;
55
- const tools = frontmatter.tools
56
- ?.split(",")
57
- .map((t) => t.trim())
58
- .filter(Boolean);
59
- return {
60
- prompt: body,
61
- tools: tools && tools.length > 0 ? tools : undefined,
62
- model: frontmatter.model,
63
- thinking: frontmatter.thinking,
64
- };
65
- } catch {
66
- continue;
67
- }
68
- }
69
- return undefined;
70
- }