@mystilleef/pi-subagent 0.8.0 → 0.10.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.
@@ -2,6 +2,8 @@ import type { Message } from "@earendil-works/pi-ai";
2
2
  import type { AgentScope } from "../agent/agents.js";
3
3
  import type { TerminationMetadata } from "../child/termination.js";
4
4
 
5
+ export const TOOL_RESULT_FAILED_MESSAGE = "Subagent tool result failed.";
6
+
5
7
  export interface UsageStats {
6
8
  input: number;
7
9
  output: number;
@@ -15,9 +17,9 @@ export interface UsageStats {
15
17
 
16
18
  export interface ToolActivity {
17
19
  toolName: string;
18
- inputSummary?: string;
19
- instanceName?: string;
20
- child?: ToolActivity;
20
+ inputSummary?: string | undefined;
21
+ instanceName?: string | undefined;
22
+ child?: ToolActivity | undefined;
21
23
  }
22
24
 
23
25
  export interface StreamingProgressToolCall {
@@ -26,30 +28,30 @@ export interface StreamingProgressToolCall {
26
28
  }
27
29
 
28
30
  export interface StreamingProgress {
29
- activityText?: string;
30
- activeToolActivity?: ToolActivity;
31
+ activityText?: string | undefined;
32
+ activeToolActivity?: ToolActivity | undefined;
31
33
  toolCalls: StreamingProgressToolCall[];
32
- lastToolPreview?: string;
33
- toolResultCompleted?: boolean;
34
+ lastToolPreview?: string | undefined;
35
+ toolResultCompleted?: boolean | undefined;
34
36
  }
35
37
 
36
38
  export interface SingleResult {
37
39
  agent: string;
38
- instanceName?: string;
40
+ instanceName?: string | undefined;
39
41
  agentSource: "user" | "project" | "unknown";
40
42
  task: string;
41
43
  exitCode: number;
42
44
  finalOutput: string;
43
45
  stderr: string;
44
46
  usage: UsageStats;
45
- model?: string;
46
- stopReason?: string;
47
- errorMessage?: string;
48
- durationMs?: number;
49
- progress?: StreamingProgress;
50
- messages?: Message[];
51
- termination?: TerminationMetadata;
52
- thinkingWarning?: string;
47
+ model?: string | undefined;
48
+ stopReason?: string | undefined;
49
+ errorMessage?: string | undefined;
50
+ durationMs?: number | undefined;
51
+ progress?: StreamingProgress | undefined;
52
+ messages?: Message[] | undefined;
53
+ termination?: TerminationMetadata | undefined;
54
+ thinkingWarning?: string | undefined;
53
55
  }
54
56
 
55
57
  export interface SubagentDetails {
@@ -6,38 +6,67 @@ import {
6
6
  DefaultResourceLoader,
7
7
  getAgentDir,
8
8
  } from "@earendil-works/pi-coding-agent";
9
+ import type { SingleResult } from "./types.js";
9
10
 
10
11
  export const DEFAULT_MAX_OUTPUT_BYTES = 50_000;
11
12
  export const DEFAULT_MAX_OUTPUT_LINES = 500;
13
+ export const DEFAULT_AGENT_END_GRACE_MS = 250;
14
+ export const DEFAULT_MAX_STDERR_BYTES = 10_000;
15
+ export const DEFAULT_MAX_SUBAGENT_DEPTH = 3;
16
+ const MAX_SUBAGENT_DEPTH_CEILING = 10;
12
17
 
13
18
  export interface SubagentOutputLimits {
14
19
  maxBytes: number;
15
20
  maxLines: number;
16
21
  }
17
22
 
18
- type OutputLimitConfig = Partial<Record<string, string | number | undefined>>;
23
+ export interface SubagentRuntimeLimits {
24
+ agentEndGraceMs: number;
25
+ maxStderrBytes: number;
26
+ maxDepth: number;
27
+ }
28
+
29
+ type EnvLimitConfig = Partial<Record<string, string | number | undefined>>;
19
30
 
20
31
  function parsePositiveInteger(
21
32
  value: string | number | undefined,
22
33
  ): number | undefined {
23
34
  const parsed = typeof value === "number" ? value : Number(value);
24
- if (!Number.isFinite(parsed) || parsed < 1) return undefined;
25
- return Math.floor(parsed);
35
+ if (!Number.isFinite(parsed) || !Number.isInteger(parsed) || parsed < 1)
36
+ return undefined;
37
+ return parsed;
26
38
  }
27
39
 
28
40
  export function getSubagentOutputLimits(
29
- config: OutputLimitConfig = process.env,
41
+ config: EnvLimitConfig = process.env,
30
42
  ): SubagentOutputLimits {
31
43
  return {
32
44
  maxBytes:
33
- parsePositiveInteger(config.PI_SUBAGENT_MAX_OUTPUT_BYTES) ??
45
+ parsePositiveInteger(config["PI_SUBAGENT_MAX_OUTPUT_BYTES"]) ??
34
46
  DEFAULT_MAX_OUTPUT_BYTES,
35
47
  maxLines:
36
- parsePositiveInteger(config.PI_SUBAGENT_MAX_OUTPUT_LINES) ??
48
+ parsePositiveInteger(config["PI_SUBAGENT_MAX_OUTPUT_LINES"]) ??
37
49
  DEFAULT_MAX_OUTPUT_LINES,
38
50
  };
39
51
  }
40
52
 
53
+ export function getSubagentRuntimeLimits(
54
+ config: EnvLimitConfig = process.env,
55
+ ): SubagentRuntimeLimits {
56
+ const maxDepth =
57
+ parsePositiveInteger(config["PI_SUBAGENT_MAX_DEPTH"]) ??
58
+ DEFAULT_MAX_SUBAGENT_DEPTH;
59
+ return {
60
+ agentEndGraceMs:
61
+ parsePositiveInteger(config["PI_SUBAGENT_AGENT_END_GRACE_MS"]) ??
62
+ DEFAULT_AGENT_END_GRACE_MS,
63
+ maxStderrBytes:
64
+ parsePositiveInteger(config["PI_SUBAGENT_MAX_STDERR_BYTES"]) ??
65
+ DEFAULT_MAX_STDERR_BYTES,
66
+ maxDepth: Math.min(maxDepth, MAX_SUBAGENT_DEPTH_CEILING),
67
+ };
68
+ }
69
+
41
70
  export function truncateOutput(
42
71
  text: string,
43
72
  limits: SubagentOutputLimits = getSubagentOutputLimits(),
@@ -102,6 +131,7 @@ async function canonicalPath(filePath: string): Promise<string> {
102
131
  try {
103
132
  return await fs.promises.realpath(filePath);
104
133
  } catch {
134
+ /* symlinks or missing paths fall back to absolute path resolution */
105
135
  return path.resolve(filePath);
106
136
  }
107
137
  }
@@ -181,18 +211,27 @@ export function subagentDepthEnv(): Record<string, string> {
181
211
  return { PI_SUBAGENT_DEPTH: String(getSubagentDepth() + 1) };
182
212
  }
183
213
 
184
- export function detectMessageError(messages: Message[]): boolean {
185
- let lastAssistantIdx = -1;
214
+ export function findLastAssistantTextMessage(messages: Message[]): number {
186
215
  for (let i = messages.length - 1; i >= 0; i--) {
187
216
  const msg = messages[i];
188
217
  if (
189
218
  msg?.role === "assistant" &&
190
- msg.content.some((c) => c.type === "text" && c.text.trim().length > 0)
219
+ Array.isArray(msg.content) &&
220
+ msg.content.some(
221
+ (c) =>
222
+ c.type === "text" &&
223
+ typeof c.text === "string" &&
224
+ c.text.trim().length > 0,
225
+ )
191
226
  ) {
192
- lastAssistantIdx = i;
193
- break;
227
+ return i;
194
228
  }
195
229
  }
230
+ return -1;
231
+ }
232
+
233
+ export function detectMessageError(messages: Message[]): boolean {
234
+ const lastAssistantIdx = findLastAssistantTextMessage(messages);
196
235
  const from = lastAssistantIdx >= 0 ? lastAssistantIdx + 1 : 0;
197
236
  for (let i = messages.length - 1; i >= from; i--) {
198
237
  const msg = messages[i];
@@ -200,3 +239,13 @@ export function detectMessageError(messages: Message[]): boolean {
200
239
  }
201
240
  return false;
202
241
  }
242
+
243
+ export function hasSubagentFailed(result: SingleResult): boolean {
244
+ return (
245
+ result.exitCode !== 0 ||
246
+ result.stopReason === "error" ||
247
+ result.stopReason === "aborted" ||
248
+ Boolean(result.errorMessage?.trim()) ||
249
+ detectMessageError(result.messages ?? [])
250
+ );
251
+ }
package/tsconfig.json CHANGED
@@ -1,30 +1,28 @@
1
1
  {
2
2
  "compilerOptions": {
3
- // Environment setup & latest features
4
3
  "lib": ["ESNext"],
5
4
  "target": "ESNext",
6
5
  "module": "Preserve",
7
6
  "moduleDetection": "force",
8
7
  "jsx": "react-jsx",
9
- "allowJs": true,
10
8
  "types": ["bun"],
11
-
12
- // Bundler mode
13
9
  "moduleResolution": "bundler",
14
10
  "allowImportingTsExtensions": true,
15
11
  "verbatimModuleSyntax": true,
16
12
  "noEmit": true,
17
-
18
- // Best practices
19
13
  "strict": true,
20
14
  "skipLibCheck": true,
21
15
  "noFallthroughCasesInSwitch": true,
22
16
  "noUncheckedIndexedAccess": true,
23
17
  "noImplicitOverride": true,
24
-
25
- // Some stricter flags (disabled by default)
26
- "noUnusedLocals": false,
27
- "noUnusedParameters": false,
28
- "noPropertyAccessFromIndexSignature": false
18
+ "noImplicitReturns": true,
19
+ "erasableSyntaxOnly": true,
20
+ "forceConsistentCasingInFileNames": true,
21
+ "noUnusedLocals": true,
22
+ "noUnusedParameters": true,
23
+ "noPropertyAccessFromIndexSignature": true,
24
+ "exactOptionalPropertyTypes": true,
25
+ "allowUnreachableCode": false,
26
+ "allowUnusedLabels": false
29
27
  }
30
28
  }