@mystilleef/pi-subagent 0.10.2 → 0.12.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.
@@ -1,89 +1,38 @@
1
+ /**
2
+ * Barrel export for shared utilities.
3
+ * Re-exports from focused modules; retains standalone utilities here.
4
+ */
5
+
1
6
  import * as fs from "node:fs";
2
7
  import * as os from "node:os";
3
8
  import * as path from "node:path";
4
- import type { Message } from "@earendil-works/pi-ai";
5
- import {
6
- DefaultResourceLoader,
7
- getAgentDir,
8
- } from "@earendil-works/pi-coding-agent";
9
- import type { SingleResult } from "./types.js";
10
-
11
- export const DEFAULT_MAX_OUTPUT_BYTES = 50_000;
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;
17
-
18
- export interface SubagentOutputLimits {
19
- maxBytes: number;
20
- maxLines: number;
21
- }
22
-
23
- export interface SubagentRuntimeLimits {
24
- agentEndGraceMs: number;
25
- maxStderrBytes: number;
26
- maxDepth: number;
27
- }
28
9
 
29
- type EnvLimitConfig = Partial<Record<string, string | number | undefined>>;
30
-
31
- function parsePositiveInteger(
32
- value: string | number | undefined,
33
- ): number | undefined {
34
- const parsed = typeof value === "number" ? value : Number(value);
35
- if (!Number.isFinite(parsed) || !Number.isInteger(parsed) || parsed < 1)
36
- return undefined;
37
- return parsed;
38
- }
39
-
40
- export function getSubagentOutputLimits(
41
- config: EnvLimitConfig = process.env,
42
- ): SubagentOutputLimits {
43
- return {
44
- maxBytes:
45
- parsePositiveInteger(config["PI_SUBAGENT_MAX_OUTPUT_BYTES"]) ??
46
- DEFAULT_MAX_OUTPUT_BYTES,
47
- maxLines:
48
- parsePositiveInteger(config["PI_SUBAGENT_MAX_OUTPUT_LINES"]) ??
49
- DEFAULT_MAX_OUTPUT_LINES,
50
- };
51
- }
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
-
70
- export function truncateOutput(
71
- text: string,
72
- limits: SubagentOutputLimits = getSubagentOutputLimits(),
73
- ): string {
74
- const lines = text.split("\n");
75
- const maxBytes = Math.max(1, Math.floor(limits.maxBytes));
76
- const maxLines = Math.max(1, Math.floor(limits.maxLines));
77
- if (lines.length <= maxLines && Buffer.byteLength(text, "utf-8") <= maxBytes)
78
- return text;
79
- let result = lines.slice(0, maxLines).join("\n");
80
- if (Buffer.byteLength(result, "utf-8") > maxBytes) {
81
- const buf = Buffer.from(result).subarray(0, maxBytes);
82
- result = buf.toString("utf-8").replace(/\uFFFD$/, "");
83
- }
84
- const kept = result.split("\n").length;
85
- return `[TRUNCATED: first ${kept} of ${lines.length} lines]\n${result}`;
86
- }
10
+ // Re-export all public symbols from focused modules
11
+ export {
12
+ DEFAULT_AGENT_END_GRACE_MS,
13
+ DEFAULT_MAX_OUTPUT_BYTES,
14
+ DEFAULT_MAX_OUTPUT_LINES,
15
+ DEFAULT_MAX_STDERR_BYTES,
16
+ DEFAULT_MAX_SUBAGENT_DEPTH,
17
+ getSubagentOutputLimits,
18
+ getSubagentRuntimeLimits,
19
+ truncateOutput,
20
+ } from "./limits.js";
21
+ export {
22
+ detectMessageError,
23
+ extractFinalOutputFromMessages,
24
+ findLastAssistantTextMessage,
25
+ hasSubagentFailed,
26
+ } from "./message-utils.js";
27
+ export {
28
+ EXTENSION_DISCOVERY_CACHE_TTL_MS,
29
+ resetResolvedAgentExtensionPathsCache,
30
+ resetResolvedAgentSkillArgsCache,
31
+ resolveAgentExtensionPaths,
32
+ resolveAgentSkillArgs,
33
+ } from "./resource-resolution.js";
34
+
35
+ // Standalone utilities retained in this module
87
36
 
88
37
  export async function writePromptToTempFile(
89
38
  agentName: string,
@@ -118,89 +67,6 @@ export function getPiInvocation(args: string[]): {
118
67
  return { command: "pi", args };
119
68
  }
120
69
 
121
- const SKILL_DISCOVERY_CACHE_TTL_MS = 300_000;
122
-
123
- type ResolvedSkillArgsCacheEntry = {
124
- skillPaths: Map<string, string>;
125
- ts: number;
126
- };
127
-
128
- const resolvedSkillArgsCache = new Map<string, ResolvedSkillArgsCacheEntry>();
129
-
130
- async function canonicalPath(filePath: string): Promise<string> {
131
- try {
132
- return await fs.promises.realpath(filePath);
133
- } catch {
134
- /* symlinks or missing paths fall back to absolute path resolution */
135
- return path.resolve(filePath);
136
- }
137
- }
138
-
139
- function buildSkillArgs(
140
- requested: string[],
141
- skillPaths: Map<string, string>,
142
- ): string[] {
143
- return requested.flatMap((name) => ["--skill", skillPaths.get(name) ?? name]);
144
- }
145
-
146
- export function resetResolvedAgentSkillArgsCache(): void {
147
- resolvedSkillArgsCache.clear();
148
- }
149
-
150
- export async function resolveAgentSkillArgs(
151
- cwd: string,
152
- skillNames: string[],
153
- ): Promise<{ args: string[] } | { error: string }> {
154
- const requested = Array.from(new Set(skillNames));
155
- if (requested.length === 0) return { args: [] };
156
- const cacheIdentitySkills = [...requested].sort();
157
- const agentDir = getAgentDir();
158
- const cacheKey = JSON.stringify({
159
- cwd: await canonicalPath(cwd),
160
- agentDir: await canonicalPath(agentDir),
161
- skills: cacheIdentitySkills,
162
- });
163
- const cached = resolvedSkillArgsCache.get(cacheKey);
164
- if (cached && Date.now() - cached.ts <= SKILL_DISCOVERY_CACHE_TTL_MS) {
165
- return { args: buildSkillArgs(requested, cached.skillPaths) };
166
- }
167
- const loader = new DefaultResourceLoader({
168
- cwd,
169
- agentDir,
170
- noContextFiles: true,
171
- noPromptTemplates: true,
172
- noThemes: true,
173
- });
174
- try {
175
- await loader.reload();
176
- } catch (error) {
177
- return {
178
- error: `Failed to discover skills: ${error instanceof Error ? error.message : String(error)}`,
179
- };
180
- }
181
- const { skills } = loader.getSkills();
182
- const skillMap = new Map(skills.map((skill) => [skill.name, skill]));
183
- const missing = requested.filter((name) => !skillMap.has(name));
184
- if (missing.length > 0) {
185
- const available =
186
- skills
187
- .map((skill) => skill.name)
188
- .sort()
189
- .join(", ") || "none";
190
- return {
191
- error: `Unknown skill${missing.length === 1 ? "" : "s"}: ${missing
192
- .map((name) => `"${name}"`)
193
- .join(", ")}. Available skills: ${available}.`,
194
- };
195
- }
196
- const skillPaths = new Map(
197
- requested.map((name) => [name, skillMap.get(name)?.filePath ?? name]),
198
- );
199
- const args = buildSkillArgs(requested, skillPaths);
200
- resolvedSkillArgsCache.set(cacheKey, { skillPaths, ts: Date.now() });
201
- return { args };
202
- }
203
-
204
70
  export function getSubagentDepth(): number {
205
71
  const d = Number(process.env.PI_SUBAGENT_DEPTH ?? "0");
206
72
  if (!Number.isFinite(d) || d < 0) return 0;
@@ -210,42 +76,3 @@ export function getSubagentDepth(): number {
210
76
  export function subagentDepthEnv(): Record<string, string> {
211
77
  return { PI_SUBAGENT_DEPTH: String(getSubagentDepth() + 1) };
212
78
  }
213
-
214
- export function findLastAssistantTextMessage(messages: Message[]): number {
215
- for (let i = messages.length - 1; i >= 0; i--) {
216
- const msg = messages[i];
217
- if (
218
- msg?.role === "assistant" &&
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
- )
226
- ) {
227
- return i;
228
- }
229
- }
230
- return -1;
231
- }
232
-
233
- export function detectMessageError(messages: Message[]): boolean {
234
- const lastAssistantIdx = findLastAssistantTextMessage(messages);
235
- const from = lastAssistantIdx >= 0 ? lastAssistantIdx + 1 : 0;
236
- for (let i = messages.length - 1; i >= from; i--) {
237
- const msg = messages[i];
238
- if (msg?.role === "toolResult" && msg.isError) return true;
239
- }
240
- return false;
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
- }