pi-subagents 0.14.0 → 0.15.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.
@@ -503,10 +503,12 @@ async function runSingleStep(
503
503
  sessionDir,
504
504
  sessionFile: step.sessionFile,
505
505
  model: candidate,
506
+ inheritProjectContext: step.inheritProjectContext,
507
+ inheritSkills: step.inheritSkills,
506
508
  tools: step.tools,
507
509
  extensions: step.extensions,
508
- skills: step.skills,
509
510
  systemPrompt: step.systemPrompt,
511
+ systemPromptMode: step.systemPromptMode,
510
512
  mcpDirectTools: step.mcpDirectTools,
511
513
  promptFileStem: step.agent,
512
514
  });
@@ -555,12 +557,12 @@ async function runSingleStep(
555
557
  }
556
558
  if (resolvedOutput.savedPath) {
557
559
  outputForSummary = outputForSummary
558
- ? `${outputForSummary}\n\nšŸ“„ Output saved to: ${resolvedOutput.savedPath}`
559
- : `šŸ“„ Output saved to: ${resolvedOutput.savedPath}`;
560
+ ? `${outputForSummary}\n\nOutput saved to: ${resolvedOutput.savedPath}`
561
+ : `Output saved to: ${resolvedOutput.savedPath}`;
560
562
  } else if (resolvedOutput.saveError && step.outputPath && finalResult?.exitCode === 0) {
561
563
  outputForSummary = outputForSummary
562
- ? `${outputForSummary}\n\nāš ļø Failed to save output to: ${step.outputPath}\n${resolvedOutput.saveError}`
563
- : `āš ļø Failed to save output to: ${step.outputPath}\n${resolvedOutput.saveError}`;
564
+ ? `${outputForSummary}\n\nFailed to save output to: ${step.outputPath}\n${resolvedOutput.saveError}`
565
+ : `Failed to save output to: ${step.outputPath}\n${resolvedOutput.saveError}`;
564
566
  }
565
567
 
566
568
  if (artifactPaths && ctx.artifactConfig?.enabled !== false) {
@@ -740,7 +742,6 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
740
742
  let previousCumulativeTokens: TokenUsage = { input: 0, output: 0, total: 0 };
741
743
  let latestSessionFile: string | undefined;
742
744
 
743
- // Flatten steps for status tracking (parallel groups expand to individual entries)
744
745
  const flatSteps = flattenSteps(steps);
745
746
  const sessionEnabled = Boolean(config.sessionDir)
746
747
  || shareEnabled
@@ -780,14 +781,12 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
780
781
  }),
781
782
  );
782
783
 
783
- // Track the flat index into statusPayload.steps across sequential + parallel steps
784
784
  let flatIndex = 0;
785
785
 
786
786
  for (let stepIndex = 0; stepIndex < steps.length; stepIndex++) {
787
787
  const step = steps[stepIndex];
788
788
 
789
789
  if (isParallelGroup(step)) {
790
- // === PARALLEL STEP GROUP ===
791
790
  const group = step;
792
791
  const concurrency = group.concurrency ?? MAX_PARALLEL_CONCURRENCY;
793
792
  const failFast = group.failFast ?? false;
@@ -915,7 +914,6 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
915
914
 
916
915
  flatIndex += group.parallel.length;
917
916
 
918
- // Aggregate token usage from parallel task session dirs
919
917
  if (config.sessionDir) {
920
918
  for (let t = 0; t < group.parallel.length; t++) {
921
919
  const taskSessionDir = path.join(config.sessionDir, `parallel-${t}`);
@@ -935,7 +933,6 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
935
933
  writeJson(statusPath, statusPayload);
936
934
  }
937
935
 
938
- // Collect results
939
936
  for (const pr of parallelResults) {
940
937
  results.push({
941
938
  agent: pr.agent,
@@ -969,7 +966,6 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
969
966
  success: parallelResults.every((r) => r.exitCode === 0 || r.exitCode === -1),
970
967
  }));
971
968
 
972
- // If any parallel task failed (not skipped), stop the chain
973
969
  if (parallelResults.some((r) => r.exitCode !== 0 && r.exitCode !== -1)) {
974
970
  break;
975
971
  }
@@ -977,7 +973,6 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
977
973
  if (worktreeSetup) cleanupWorktrees(worktreeSetup);
978
974
  }
979
975
  } else {
980
- // === SEQUENTIAL STEP ===
981
976
  const seqStep = step as SubagentStep;
982
977
  const stepStartTime = Date.now();
983
978
  statusPayload.currentStep = flatIndex;
package/types.ts CHANGED
@@ -85,7 +85,7 @@ export interface SingleResult {
85
85
  exitCode: number;
86
86
  detached?: boolean;
87
87
  detachedReason?: string;
88
- messages: Message[];
88
+ messages?: Message[];
89
89
  usage: Usage;
90
90
  model?: string;
91
91
  attemptedModels?: string[];
package/utils.ts CHANGED
@@ -6,19 +6,23 @@ import * as fs from "node:fs";
6
6
  import * as os from "node:os";
7
7
  import * as path from "node:path";
8
8
  import type { Message } from "@mariozechner/pi-ai";
9
- import type { AsyncStatus, DisplayItem, ErrorInfo, SingleResult } from "./types.ts";
9
+ import type { AgentProgress, AsyncStatus, Details, DisplayItem, ErrorInfo, SingleResult } from "./types.ts";
10
10
 
11
11
  // ============================================================================
12
12
  // File System Utilities
13
13
  // ============================================================================
14
14
 
15
- // Cache for status file reads - avoid re-reading unchanged files
16
15
  const statusCache = new Map<string, { mtime: number; status: AsyncStatus }>();
17
16
 
18
17
  function getErrorMessage(error: unknown): string {
19
18
  return error instanceof Error ? error.message : String(error);
20
19
  }
21
20
 
21
+ export function resolveChildCwd(baseCwd: string, childCwd: string | undefined): string {
22
+ if (!childCwd) return baseCwd;
23
+ return path.isAbsolute(childCwd) ? childCwd : path.resolve(baseCwd, childCwd);
24
+ }
25
+
22
26
  function isNotFoundError(error: unknown): boolean {
23
27
  return typeof error === "object"
24
28
  && error !== null
@@ -74,7 +78,6 @@ export function readStatus(asyncDir: string): AsyncStatus | null {
74
78
  return status;
75
79
  }
76
80
 
77
- // Cache for output tail reads - avoid re-reading unchanged files
78
81
  const outputTailCache = new Map<string, { mtime: number; size: number; lines: string[] }>();
79
82
 
80
83
  /**
@@ -87,7 +90,6 @@ export function getOutputTail(outputFile: string | undefined, maxLines: number =
87
90
  const stat = fs.statSync(outputFile);
88
91
  if (stat.size === 0) return [];
89
92
 
90
- // Check cache using both mtime and size (size changes more frequently during writes)
91
93
  const cached = outputTailCache.get(outputFile);
92
94
  if (cached && cached.mtime === stat.mtimeMs && cached.size === stat.size) {
93
95
  return cached.lines;
@@ -102,9 +104,7 @@ export function getOutputTail(outputFile: string | undefined, maxLines: number =
102
104
  const allLines = content.split("\n").filter((l) => l.trim());
103
105
  const lines = allLines.slice(-maxLines).map((l) => l.slice(0, 120) + (l.length > 120 ? "..." : ""));
104
106
 
105
- // Cache the result
106
107
  outputTailCache.set(outputFile, { mtime: stat.mtimeMs, size: stat.size, lines });
107
- // Limit cache size
108
108
  if (outputTailCache.size > 20) {
109
109
  const firstKey = outputTailCache.keys().next().value;
110
110
  if (firstKey) outputTailCache.delete(firstKey);
@@ -112,12 +112,15 @@ export function getOutputTail(outputFile: string | undefined, maxLines: number =
112
112
 
113
113
  return lines;
114
114
  } catch {
115
+ // Output tails are UI-only hints; unreadable or missing files should render as no tail.
115
116
  return [];
116
117
  } finally {
117
118
  if (fd !== null) {
118
119
  try {
119
120
  fs.closeSync(fd);
120
- } catch {}
121
+ } catch {
122
+ // Closing the best-effort tail file handle should not surface over the main status view.
123
+ }
121
124
  }
122
125
  }
123
126
  }
@@ -125,16 +128,16 @@ export function getOutputTail(outputFile: string | undefined, maxLines: number =
125
128
  /**
126
129
  * Get human-readable last activity time for a file
127
130
  */
128
- export function getLastActivity(outputFile: string | undefined): string {
131
+ export function getLastActivity(outputFile: string | undefined): string {
129
132
  if (!outputFile) return "";
130
133
  try {
131
- // Single stat call - throws if file doesn't exist
132
134
  const stat = fs.statSync(outputFile);
133
135
  const ago = Date.now() - stat.mtimeMs;
134
136
  if (ago < 1000) return "active now";
135
137
  if (ago < 60000) return `active ${Math.floor(ago / 1000)}s ago`;
136
138
  return `active ${Math.floor(ago / 60000)}m ago`;
137
139
  } catch {
140
+ // Last-activity text is best effort; missing files should simply omit the hint.
138
141
  return "";
139
142
  }
140
143
  }
@@ -201,13 +204,14 @@ export function getFinalOutput(messages: Message[]): string {
201
204
  }
202
205
 
203
206
  export function getSingleResultOutput(result: Pick<SingleResult, "finalOutput" | "messages">): string {
204
- return result.finalOutput ?? getFinalOutput(result.messages);
207
+ return result.finalOutput ?? getFinalOutput(result.messages ?? []);
205
208
  }
206
209
 
207
210
  /**
208
211
  * Extract display items (text and tool calls) from messages
209
212
  */
210
- export function getDisplayItems(messages: Message[]): DisplayItem[] {
213
+ export function getDisplayItems(messages: Message[] | undefined): DisplayItem[] {
214
+ if (!messages || messages.length === 0) return [];
211
215
  const items: DisplayItem[] = [];
212
216
  for (const msg of messages) {
213
217
  if (msg.role === "assistant") {
@@ -220,6 +224,43 @@ export function getDisplayItems(messages: Message[]): DisplayItem[] {
220
224
  return items;
221
225
  }
222
226
 
227
+ function compactCompletedProgress(progress: AgentProgress): AgentProgress {
228
+ if (progress.status === "running") return progress;
229
+ return {
230
+ index: progress.index,
231
+ agent: progress.agent,
232
+ status: progress.status,
233
+ task: progress.task,
234
+ skills: progress.skills,
235
+ toolCount: progress.toolCount,
236
+ tokens: progress.tokens,
237
+ durationMs: progress.durationMs,
238
+ error: progress.error,
239
+ failedTool: progress.failedTool,
240
+ recentTools: [],
241
+ recentOutput: [],
242
+ };
243
+ }
244
+
245
+ export function compactForegroundResult(result: SingleResult): SingleResult {
246
+ if (result.progress?.status === "running") return result;
247
+ return {
248
+ ...result,
249
+ messages: undefined,
250
+ progress: undefined,
251
+ };
252
+ }
253
+
254
+ export function compactForegroundDetails(details: Details): Details {
255
+ return {
256
+ ...details,
257
+ results: details.results.map(compactForegroundResult),
258
+ progress: details.progress
259
+ ? details.progress.map(compactCompletedProgress)
260
+ : undefined,
261
+ };
262
+ }
263
+
223
264
  /**
224
265
  * Detect errors in subagent execution from messages (only errors with no subsequent success)
225
266
  */
@@ -358,4 +399,4 @@ export function extractTextFromContent(content: unknown): string {
358
399
  // Concurrency Utilities
359
400
  // ============================================================================
360
401
 
361
- export { mapConcurrent } from "./parallel-utils.js";
402
+ export { mapConcurrent } from "./parallel-utils.ts";
package/worktree.ts CHANGED
@@ -139,7 +139,8 @@ export function findWorktreeTaskCwdConflict(
139
139
  for (let index = 0; index < tasks.length; index++) {
140
140
  const task = tasks[index]!;
141
141
  if (!task.cwd) continue;
142
- if (normalizeComparableCwd(task.cwd) === normalizedSharedCwd) continue;
142
+ const taskCwd = path.isAbsolute(task.cwd) ? task.cwd : path.resolve(sharedCwd, task.cwd);
143
+ if (normalizeComparableCwd(taskCwd) === normalizedSharedCwd) continue;
143
144
  return { index, agent: task.agent, cwd: task.cwd };
144
145
  }
145
146
  return undefined;