@arhen/pi-core-subagent 1.0.8 → 1.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arhen/pi-core-subagent",
3
- "version": "1.0.8",
3
+ "version": "1.1.0",
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
 
@@ -251,6 +250,10 @@ function compactLines(run: RunSnapshot): string[] {
251
250
  * └─ ✓ reviewer · 6 tools · 44s
252
251
  * Static icons (no animation); latest activity + tool count + runtime per agent.
253
252
  */
253
+ const WIDGET_MAX_RUNS = 3;
254
+ const WIDGET_MAX_TASKS_PER_RUN = 4;
255
+ const WIDGET_MAX_LINES = 10;
256
+
254
257
  class SubagentsWidget implements Component {
255
258
  constructor(
256
259
  private readonly getRuns: () => RunSnapshot[],
@@ -265,17 +268,18 @@ class SubagentsWidget implements Component {
265
268
  const runs = this.getRuns();
266
269
  if (runs.length === 0) return [];
267
270
  const lines: string[] = [];
268
- runs.forEach((run, ri) => {
269
- if (run.tasks.length === 0) return;
270
- if (ri > 0) lines.push("");
271
+ let renderedRuns = 0;
272
+ for (const run of runs.slice(0, WIDGET_MAX_RUNS)) {
273
+ if (run.tasks.length === 0) continue;
274
+ if (lines.length > 0) lines.push("");
271
275
  const done = run.tasks.filter((t) => TERMINAL.includes(t.status)).length;
272
276
  const active = !TERMINAL.includes(run.status);
273
277
  const head = active ? "accent" : "dim";
274
278
  lines.push(truncateToWidth(`${this.theme.fg(head, active ? "●" : "○")} ${this.theme.fg(head, `Subagents (${done}/${run.tasks.length})`)}`, width, "…"));
275
279
  const allDone = TERMINAL.includes(run.status);
276
- const visible = run.tasks.slice(0, MAX_TASKS);
280
+ const visible = run.tasks.slice(0, WIDGET_MAX_TASKS_PER_RUN);
277
281
  visible.forEach((task, i) => {
278
- const last = i === visible.length - 1 && run.tasks.length <= MAX_TASKS;
282
+ const last = i === visible.length - 1 && run.tasks.length <= WIDGET_MAX_TASKS_PER_RUN;
279
283
  const conn = this.theme.fg("dim", last ? "└─" : "├─");
280
284
  const activity =
281
285
  !TERMINAL.includes(task.status) && task.lastActivity
@@ -287,8 +291,12 @@ class SubagentsWidget implements Component {
287
291
  : `${statusIcon(task.status)} ${task.agent} · ${activity}${taskStatsWithUsage(task)} · ${taskTimer(task)}`;
288
292
  lines.push(truncateToWidth(`${conn} ${line}`, width, "…"));
289
293
  });
290
- if (run.tasks.length > MAX_TASKS) lines.push(`${this.theme.fg("dim", "└─")} ${this.theme.fg("dim", `+${run.tasks.length - MAX_TASKS} more`)}`);
291
- });
294
+ if (run.tasks.length > WIDGET_MAX_TASKS_PER_RUN) lines.push(`${this.theme.fg("dim", "└─")} ${this.theme.fg("dim", `+${run.tasks.length - WIDGET_MAX_TASKS_PER_RUN} more tasks`)}`);
295
+ renderedRuns += 1;
296
+ if (lines.length >= WIDGET_MAX_LINES) break; // keep the editor visible
297
+ }
298
+ const hiddenRuns = runs.length - renderedRuns;
299
+ if (hiddenRuns > 0) lines.push(`${this.theme.fg("dim", "└─")} ${this.theme.fg("dim", `+${hiddenRuns} more run${hiddenRuns > 1 ? "s" : ""}`)}`);
292
300
  return lines;
293
301
  }
294
302
  }
@@ -622,17 +630,16 @@ class SubagentManager {
622
630
 
623
631
  // Inline params win; otherwise fall back to an existing agent file
624
632
  // (~/.agents, .pi/agents, user dir). Never creates files.
625
- const fileAgent = input.prompt?.trim() ? undefined : lookupAgent(task.agent, task.cwd);
626
- const prompt = input.prompt?.trim() || fileAgent?.prompt;
627
- const thinking = input.thinking ?? fileAgent?.thinking;
628
- 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);
629
636
  const tools = [...baseTools, ...(run.allowIntercom ? CHILD_TALK_TOOLS : [])];
630
637
 
631
638
  // Model + thinking resolve against the pi model registry; a bad request
632
639
  // fails the TASK with a helpful message, not the whole run.
633
640
  let model: Model<Api> | undefined;
634
641
  try {
635
- model = resolveChildModel(ctx, input.model ?? fileAgent?.model);
642
+ model = resolveChildModel(ctx, input.model);
636
643
  validateThinking(model, thinking);
637
644
  } catch (err) {
638
645
  this.updateTask(run, task, {
@@ -646,7 +653,7 @@ class SubagentManager {
646
653
  this.updateTask(run, task, {
647
654
  status: "starting",
648
655
  startedAt: Date.now(),
649
- model: input.model ?? fileAgent?.model,
656
+ model: input.model,
650
657
  thinking,
651
658
  tools,
652
659
  }, ctx, onUpdate);
@@ -1000,7 +1007,7 @@ let eventSeq = 0;
1000
1007
 
1001
1008
  const TaskItem = Type.Object({
1002
1009
  id: Type.Optional(Type.String({ description: "Optional stable task id" })),
1003
- 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." }),
1004
1011
  task: Type.String({ minLength: 1, description: "Task for this agent" }),
1005
1012
  prompt: Type.Optional(Type.String({ description: "System prompt defining this agent's behavior. Optional — a minimal default is used." })),
1006
1013
  write: Type.Optional(Type.Boolean({ description: "true = write toolset (read, bash, edit, write); default false = read-only (read, grep, find, ls)" })),
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
- }