pi-fast-subagent 0.8.0 → 0.9.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/README.md CHANGED
@@ -14,6 +14,24 @@ 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
+ - Chronological expanded view (Ctrl+O): subagent tool calls and response text interleaved in execution order
18
+ - Collapsed view shows response + trailing tool calls as an indented tree
19
+
20
+ ## Settings
21
+
22
+ Configure preview sizes in `~/.pi/agent/settings.json` or `.pi/settings.json`:
23
+
24
+ ```json
25
+ {
26
+ "fastSubagent": {
27
+ "previewLines": 12,
28
+ "promptPreviewLines": 12
29
+ }
30
+ }
31
+ ```
32
+
33
+ - `previewLines` — response text preview lines in collapsed view (default 12)
34
+ - `promptPreviewLines` — task/prompt preview lines in collapsed view (default 12)
17
35
 
18
36
  ## Install
19
37
 
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
+ }