@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.
- package/README.md +84 -10
- package/package.json +8 -8
- package/src/agent/agents.ts +75 -9
- package/src/child/complete-extension.ts +36 -0
- package/src/child/complete-outcome.ts +35 -0
- package/src/child/model-resolution.ts +81 -0
- package/src/child/process-utils.ts +114 -0
- package/src/child/process.ts +225 -471
- package/src/child/prompt-contract.ts +22 -10
- package/src/child/prompt-setup.ts +36 -0
- package/src/child/result-builder.ts +142 -0
- package/src/child/sampling-extension.ts +49 -0
- package/src/child/streaming-progress.ts +138 -0
- package/src/orchestration/subagent-orchestrator.ts +20 -16
- package/src/output/normalize.ts +1 -1
- package/src/output/summary.ts +22 -6
- package/src/output/ui.ts +19 -21
- package/src/progress/progress-state.ts +10 -12
- package/src/progress/result-details.ts +54 -45
- package/src/shared/limits.ts +81 -0
- package/src/shared/message-utils.ts +56 -0
- package/src/shared/resource-resolution.ts +289 -0
- package/src/shared/sampling.ts +56 -0
- package/src/shared/types.ts +1 -0
- package/src/shared/utils.ts +31 -204
package/src/shared/utils.ts
CHANGED
|
@@ -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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
export
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
}
|