pi-fast-subagent 0.8.0 → 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/README.md CHANGED
@@ -14,6 +14,25 @@ Runs subagents with `createAgentSession()` in same process instead of spawning `
14
14
  - User + project agent discovery
15
15
  - Project agents override user agents
16
16
  - Max nesting depth guard
17
+ - Streamed prompt preview while the parent LLM is writing the subagent task
18
+ - Chronological expanded view (Ctrl+O): subagent tool calls and response text interleaved in execution order
19
+ - Collapsed view shows response + trailing tool calls as an indented tree
20
+
21
+ ## Settings
22
+
23
+ Configure preview sizes in `~/.pi/agent/settings.json` or `.pi/settings.json`:
24
+
25
+ ```json
26
+ {
27
+ "fastSubagent": {
28
+ "previewLines": 12,
29
+ "promptPreviewLines": 12
30
+ }
31
+ }
32
+ ```
33
+
34
+ - `previewLines` — response text preview lines in collapsed view (default 12)
35
+ - `promptPreviewLines` — task/prompt preview lines in collapsed view (default 12)
17
36
 
18
37
  ## Install
19
38
 
package/agents.ts CHANGED
@@ -48,7 +48,7 @@ export function agentNeedsExtensions(tools: AgentTools): boolean {
48
48
 
49
49
  // Default: all tools, matching pi-subagents behavior. Agents opt into lean mode
50
50
  // with `tools: builtins` or explicit built-in allowlists.
51
- function parseToolsField(raw: unknown): AgentTools {
51
+ export function parseToolsField(raw: unknown): AgentTools {
52
52
  if (raw === undefined || raw === null) return "all";
53
53
  const str = String(raw).trim();
54
54
  if (!str) return "all";
@@ -60,7 +60,7 @@ function parseToolsField(raw: unknown): AgentTools {
60
60
  return list.length ? list : "all";
61
61
  }
62
62
 
63
- function parseMaxDepthField(raw: unknown): number {
63
+ export function parseMaxDepthField(raw: unknown): number {
64
64
  if (raw === undefined || raw === null || raw === "") return 0;
65
65
  const n = Number(raw);
66
66
  if (!Number.isFinite(n) || n < 0) return 0;
package/format.ts ADDED
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Pure formatting helpers used by runner + render + command layers.
3
+ */
4
+
5
+ import type { AgentConfig } from "./agents.js";
6
+ import type { BackgroundSubagentJob } from "./background-types.js";
7
+ import type { RunResult } from "./types.js";
8
+
9
+ export function formatTools(tools: AgentConfig["tools"]): string {
10
+ if (tools === "all") return "all";
11
+ if (tools === "builtins") return "builtins (default)";
12
+ if (tools === "none") return "none";
13
+ return tools.join(", ");
14
+ }
15
+
16
+ export function shortPath(p: unknown): string {
17
+ if (typeof p !== "string") return "";
18
+ const cwd = process.cwd();
19
+ if (p.startsWith(cwd + "/")) return p.slice(cwd.length + 1);
20
+ return p.replace(/^\/Users\/[^/]+\/[^/]+\//, "");
21
+ }
22
+
23
+ export function summarizeToolArgs(toolName: unknown, toolInput: unknown): string {
24
+ const name = String(toolName ?? "");
25
+ const input =
26
+ toolInput && typeof toolInput === "object" ? (toolInput as Record<string, unknown>) : {};
27
+ const filePath = (): string => shortPath(input.path ?? input.file_path) || "";
28
+ switch (name) {
29
+ case "Read":
30
+ case "read":
31
+ case "Write":
32
+ case "write":
33
+ case "Edit":
34
+ case "edit":
35
+ return filePath();
36
+ case "Bash":
37
+ case "bash": {
38
+ const cmd = String(input.command ?? "");
39
+ return cmd.length > 80 ? cmd.slice(0, 77) + "..." : cmd;
40
+ }
41
+ case "Glob":
42
+ case "glob":
43
+ return String(input.pattern ?? "");
44
+ case "find": {
45
+ const pat = String(input.pattern ?? "");
46
+ const p = shortPath(input.path);
47
+ return p ? `${pat} in ${p}` : pat;
48
+ }
49
+ case "Grep":
50
+ case "grep": {
51
+ const pat = String(input.pattern ?? "");
52
+ const g = input.glob ? ` ${input.glob}` : "";
53
+ return `${pat}${g}`;
54
+ }
55
+ case "ls":
56
+ return shortPath(input.path) || "";
57
+ case "subagent": {
58
+ const agent = String(input.agent ?? "");
59
+ const t = String(input.task ?? "");
60
+ const summary = t.length > 50 ? t.slice(0, 47) + "..." : t;
61
+ return agent ? `${agent}: ${summary}` : summary;
62
+ }
63
+ default: {
64
+ for (const v of Object.values(input)) {
65
+ if (typeof v === "string" && v.length > 0)
66
+ return v.length > 60 ? v.slice(0, 57) + "..." : v;
67
+ }
68
+ return "";
69
+ }
70
+ }
71
+ }
72
+
73
+ export function formatDuration(ms: number): string {
74
+ const s = Math.max(0, Math.floor(ms / 1000));
75
+ const m = Math.floor(s / 60);
76
+ const rem = s % 60;
77
+ return m > 0 ? `${m}m ${rem}s` : `${rem}s`;
78
+ }
79
+
80
+ export function summarizeTask(task: string, max = 60): string {
81
+ return task.length > max ? task.slice(0, max - 3) + "..." : task;
82
+ }
83
+
84
+ export function formatTokens(n: number): string {
85
+ if (n < 1000) return String(n);
86
+ if (n < 10000) return `${(n / 1000).toFixed(1)}k`;
87
+ return `${Math.round(n / 1000)}k`;
88
+ }
89
+
90
+ export function formatUsage(usage: RunResult["usage"], model?: string): string {
91
+ const parts: string[] = [];
92
+ if (usage.turns) parts.push(`${usage.turns} turn${usage.turns > 1 ? "s" : ""}`);
93
+ if (usage.input) parts.push(`↑${formatTokens(usage.input)}`);
94
+ if (usage.output) parts.push(`↓${formatTokens(usage.output)}`);
95
+ if (usage.cost) parts.push(`$${usage.cost.toFixed(4)}`);
96
+ if (model) parts.push(model);
97
+ return parts.join(" ");
98
+ }
99
+
100
+ export function getFinalText(r: RunResult): string {
101
+ if (r.exitCode !== 0) return `Error: ${r.error ?? r.output ?? "(no output)"}`;
102
+ return r.output || "(no output)";
103
+ }
104
+
105
+ export function formatBgJobSummary(job: BackgroundSubagentJob, now = Date.now()): string {
106
+ const dur = job.completedAt ? formatDuration(job.completedAt - job.startedAt) : formatDuration(now - job.startedAt);
107
+ return `${job.id} [${job.status}] ${job.agentName} · ${dur} · ${summarizeTask(job.task)}`;
108
+ }
109
+
110
+ export function formatBgJobDetails(job: BackgroundSubagentJob, now = Date.now()): string {
111
+ const dur = job.completedAt ? formatDuration(job.completedAt - job.startedAt) : formatDuration(now - job.startedAt);
112
+ const lines = [`${job.id} [${job.status}] ${job.agentName} · ${dur}`, `Task: ${job.task}`];
113
+ if (job.model) lines.push(`Model: ${job.model}`);
114
+ if (job.status === "completed") lines.push(`\nResult:\n${job.resultSummary ?? "(no output)"}`);
115
+ if (job.status === "failed") lines.push(`\nError: ${job.error ?? "(unknown)"}`);
116
+ if (job.status === "cancelled") lines.push("\nCancelled.");
117
+ if (job.status === "running") lines.push("\nStill running.");
118
+ return lines.join("\n");
119
+ }