@mystilleef/pi-subagent 0.7.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 +26 -5
- package/package.json +10 -11
- package/src/agent/agent-cache.ts +48 -20
- package/src/agent/agents.ts +14 -9
- package/src/child/child-events.ts +20 -20
- package/src/child/process.ts +100 -49
- package/src/child/termination.ts +4 -4
- package/src/env.d.ts +13 -0
- package/src/orchestration/run-registry.ts +1 -2
- package/src/orchestration/subagent-orchestrator.ts +12 -7
- package/src/progress/progress-state.ts +42 -39
- package/src/progress/progress.ts +11 -4
- package/src/progress/result-details.ts +55 -11
- package/src/shared/types.ts +16 -16
- package/src/shared/utils.ts +80 -13
- package/tsconfig.json +9 -11
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
patchProgressState,
|
|
17
17
|
renderToolActivity,
|
|
18
18
|
} from "./progress.js";
|
|
19
|
+
import { SENSITIVE_PATTERN } from "./progress-state.js";
|
|
19
20
|
|
|
20
21
|
export function hasSubagentFailed(result: SingleResult): boolean {
|
|
21
22
|
return (
|
|
@@ -41,6 +42,46 @@ export function createSubagentError(result: SingleResult): Error {
|
|
|
41
42
|
return new Error(`Agent ${result.stopReason || "failed"}: ${msg}`);
|
|
42
43
|
}
|
|
43
44
|
|
|
45
|
+
const DEBUG_REDACTED_PLACEHOLDER = "[redacted]";
|
|
46
|
+
const SENSITIVE_ASSIGNMENT_PATTERN =
|
|
47
|
+
/\b(?:secret|password|[A-Za-z0-9_-]*token)(?:\s*[:=]\s*)(?:"[^"]*"|'[^']*'|[^\s,;]+)/gi;
|
|
48
|
+
const SENSITIVE_TERM_PATTERN = new RegExp(SENSITIVE_PATTERN.source, "gi");
|
|
49
|
+
const TOKEN_COUNT_KEY_PATTERN = /tokens$/i;
|
|
50
|
+
|
|
51
|
+
function redactSensitiveDebugString(text: string): string {
|
|
52
|
+
return text
|
|
53
|
+
.replace(SENSITIVE_ASSIGNMENT_PATTERN, DEBUG_REDACTED_PLACEHOLDER)
|
|
54
|
+
.replace(SENSITIVE_TERM_PATTERN, DEBUG_REDACTED_PLACEHOLDER);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function isSensitiveDebugKey(key: string): boolean {
|
|
58
|
+
const lowerKey = key.toLowerCase();
|
|
59
|
+
if (TOKEN_COUNT_KEY_PATTERN.test(lowerKey)) return false;
|
|
60
|
+
return (
|
|
61
|
+
lowerKey.includes("secret") ||
|
|
62
|
+
lowerKey.includes("password") ||
|
|
63
|
+
lowerKey.endsWith("token")
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function redactSensitiveDebugValue(value: unknown): unknown {
|
|
68
|
+
if (typeof value === "string") return redactSensitiveDebugString(value);
|
|
69
|
+
if (Array.isArray(value)) return value.map(redactSensitiveDebugValue);
|
|
70
|
+
if (typeof value !== "object" || value === null) return value;
|
|
71
|
+
const redacted: Record<string, unknown> = {};
|
|
72
|
+
for (const [key, child] of Object.entries(value)) {
|
|
73
|
+
redacted[key] = isSensitiveDebugKey(key)
|
|
74
|
+
? DEBUG_REDACTED_PLACEHOLDER
|
|
75
|
+
: redactSensitiveDebugValue(child);
|
|
76
|
+
}
|
|
77
|
+
return redacted;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function redactSensitiveDebugMessages(messages: unknown): unknown {
|
|
81
|
+
if (!Array.isArray(messages)) return messages;
|
|
82
|
+
return messages.map(redactSensitiveDebugValue);
|
|
83
|
+
}
|
|
84
|
+
|
|
44
85
|
export function sanitizeDetailsForDisplay(
|
|
45
86
|
details: SubagentDetails,
|
|
46
87
|
includeMessages = false,
|
|
@@ -50,9 +91,11 @@ export function sanitizeDetailsForDisplay(
|
|
|
50
91
|
results: details.results.map(({ messages, termination, ...result }) => ({
|
|
51
92
|
...result,
|
|
52
93
|
stderr: includeMessages ? result.stderr : "",
|
|
53
|
-
...(includeMessages
|
|
94
|
+
...(includeMessages
|
|
95
|
+
? { messages: redactSensitiveDebugMessages(messages), termination }
|
|
96
|
+
: {}),
|
|
54
97
|
})),
|
|
55
|
-
};
|
|
98
|
+
} as SubagentDetails;
|
|
56
99
|
}
|
|
57
100
|
|
|
58
101
|
export function getLatestResult(
|
|
@@ -90,26 +133,27 @@ export function patchProgressFromDetails(
|
|
|
90
133
|
nextActivity = current.activeToolActivity;
|
|
91
134
|
}
|
|
92
135
|
if (toolResultCompleted && nextActivity?.child) {
|
|
93
|
-
|
|
136
|
+
const { child: _child, ...rest } = nextActivity;
|
|
137
|
+
nextActivity = rest;
|
|
94
138
|
} else if (toolResultCompleted) {
|
|
95
139
|
nextActivity = undefined;
|
|
96
140
|
}
|
|
97
|
-
patch
|
|
141
|
+
patch["activeToolActivity"] = nextActivity;
|
|
98
142
|
const renderedPreview = renderToolActivity(nextActivity);
|
|
99
143
|
if (renderedPreview) {
|
|
100
|
-
patch
|
|
144
|
+
patch["lastToolPreview"] = renderedPreview;
|
|
101
145
|
} else if (toolResultCompleted && !nextActivity) {
|
|
102
|
-
patch
|
|
146
|
+
patch["lastToolPreview"] = undefined;
|
|
103
147
|
}
|
|
104
148
|
if (toolResultCompleted) {
|
|
105
|
-
patch
|
|
149
|
+
patch["toolResultCompleted"] = true;
|
|
106
150
|
}
|
|
107
151
|
// Token accounting always applies when usage data is available
|
|
108
152
|
if (latestResult?.usage) {
|
|
109
|
-
patch
|
|
110
|
-
patch
|
|
111
|
-
patch
|
|
112
|
-
patch
|
|
153
|
+
patch["inputTokens"] = latestResult.usage.input;
|
|
154
|
+
patch["outputTokens"] = latestResult.usage.output;
|
|
155
|
+
patch["contextTokens"] = latestResult.usage.contextTokens;
|
|
156
|
+
patch["contextWindowTokens"] = latestResult.usage.contextWindowTokens;
|
|
113
157
|
}
|
|
114
158
|
patchProgressState(
|
|
115
159
|
requestId,
|
package/src/shared/types.ts
CHANGED
|
@@ -15,9 +15,9 @@ export interface UsageStats {
|
|
|
15
15
|
|
|
16
16
|
export interface ToolActivity {
|
|
17
17
|
toolName: string;
|
|
18
|
-
inputSummary?: string;
|
|
19
|
-
instanceName?: string;
|
|
20
|
-
child?: ToolActivity;
|
|
18
|
+
inputSummary?: string | undefined;
|
|
19
|
+
instanceName?: string | undefined;
|
|
20
|
+
child?: ToolActivity | undefined;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
export interface StreamingProgressToolCall {
|
|
@@ -26,30 +26,30 @@ export interface StreamingProgressToolCall {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
export interface StreamingProgress {
|
|
29
|
-
activityText?: string;
|
|
30
|
-
activeToolActivity?: ToolActivity;
|
|
29
|
+
activityText?: string | undefined;
|
|
30
|
+
activeToolActivity?: ToolActivity | undefined;
|
|
31
31
|
toolCalls: StreamingProgressToolCall[];
|
|
32
|
-
lastToolPreview?: string;
|
|
33
|
-
toolResultCompleted?: boolean;
|
|
32
|
+
lastToolPreview?: string | undefined;
|
|
33
|
+
toolResultCompleted?: boolean | undefined;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
export interface SingleResult {
|
|
37
37
|
agent: string;
|
|
38
|
-
instanceName?: string;
|
|
38
|
+
instanceName?: string | undefined;
|
|
39
39
|
agentSource: "user" | "project" | "unknown";
|
|
40
40
|
task: string;
|
|
41
41
|
exitCode: number;
|
|
42
42
|
finalOutput: string;
|
|
43
43
|
stderr: string;
|
|
44
44
|
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;
|
|
45
|
+
model?: string | undefined;
|
|
46
|
+
stopReason?: string | undefined;
|
|
47
|
+
errorMessage?: string | undefined;
|
|
48
|
+
durationMs?: number | undefined;
|
|
49
|
+
progress?: StreamingProgress | undefined;
|
|
50
|
+
messages?: Message[] | undefined;
|
|
51
|
+
termination?: TerminationMetadata | undefined;
|
|
52
|
+
thinkingWarning?: string | undefined;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
export interface SubagentDetails {
|
package/src/shared/utils.ts
CHANGED
|
@@ -9,35 +9,63 @@ import {
|
|
|
9
9
|
|
|
10
10
|
export const DEFAULT_MAX_OUTPUT_BYTES = 50_000;
|
|
11
11
|
export const DEFAULT_MAX_OUTPUT_LINES = 500;
|
|
12
|
+
export const DEFAULT_AGENT_END_GRACE_MS = 250;
|
|
13
|
+
export const DEFAULT_MAX_STDERR_BYTES = 10_000;
|
|
14
|
+
export const DEFAULT_MAX_SUBAGENT_DEPTH = 3;
|
|
15
|
+
export const MAX_SUBAGENT_DEPTH_CEILING = 10;
|
|
12
16
|
|
|
13
17
|
export interface SubagentOutputLimits {
|
|
14
18
|
maxBytes: number;
|
|
15
19
|
maxLines: number;
|
|
16
20
|
}
|
|
17
21
|
|
|
18
|
-
|
|
22
|
+
export interface SubagentRuntimeLimits {
|
|
23
|
+
agentEndGraceMs: number;
|
|
24
|
+
maxStderrBytes: number;
|
|
25
|
+
maxDepth: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
type EnvLimitConfig = Partial<Record<string, string | number | undefined>>;
|
|
19
29
|
|
|
20
30
|
function parsePositiveInteger(
|
|
21
31
|
value: string | number | undefined,
|
|
22
32
|
): number | undefined {
|
|
23
33
|
const parsed = typeof value === "number" ? value : Number(value);
|
|
24
|
-
if (!Number.isFinite(parsed) || parsed < 1)
|
|
25
|
-
|
|
34
|
+
if (!Number.isFinite(parsed) || !Number.isInteger(parsed) || parsed < 1)
|
|
35
|
+
return undefined;
|
|
36
|
+
return parsed;
|
|
26
37
|
}
|
|
27
38
|
|
|
28
39
|
export function getSubagentOutputLimits(
|
|
29
|
-
config:
|
|
40
|
+
config: EnvLimitConfig = process.env,
|
|
30
41
|
): SubagentOutputLimits {
|
|
31
42
|
return {
|
|
32
43
|
maxBytes:
|
|
33
|
-
parsePositiveInteger(config
|
|
44
|
+
parsePositiveInteger(config["PI_SUBAGENT_MAX_OUTPUT_BYTES"]) ??
|
|
34
45
|
DEFAULT_MAX_OUTPUT_BYTES,
|
|
35
46
|
maxLines:
|
|
36
|
-
parsePositiveInteger(config
|
|
47
|
+
parsePositiveInteger(config["PI_SUBAGENT_MAX_OUTPUT_LINES"]) ??
|
|
37
48
|
DEFAULT_MAX_OUTPUT_LINES,
|
|
38
49
|
};
|
|
39
50
|
}
|
|
40
51
|
|
|
52
|
+
export function getSubagentRuntimeLimits(
|
|
53
|
+
config: EnvLimitConfig = process.env,
|
|
54
|
+
): SubagentRuntimeLimits {
|
|
55
|
+
const maxDepth =
|
|
56
|
+
parsePositiveInteger(config["PI_SUBAGENT_MAX_DEPTH"]) ??
|
|
57
|
+
DEFAULT_MAX_SUBAGENT_DEPTH;
|
|
58
|
+
return {
|
|
59
|
+
agentEndGraceMs:
|
|
60
|
+
parsePositiveInteger(config["PI_SUBAGENT_AGENT_END_GRACE_MS"]) ??
|
|
61
|
+
DEFAULT_AGENT_END_GRACE_MS,
|
|
62
|
+
maxStderrBytes:
|
|
63
|
+
parsePositiveInteger(config["PI_SUBAGENT_MAX_STDERR_BYTES"]) ??
|
|
64
|
+
DEFAULT_MAX_STDERR_BYTES,
|
|
65
|
+
maxDepth: Math.min(maxDepth, MAX_SUBAGENT_DEPTH_CEILING),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
41
69
|
export function truncateOutput(
|
|
42
70
|
text: string,
|
|
43
71
|
limits: SubagentOutputLimits = getSubagentOutputLimits(),
|
|
@@ -89,15 +117,54 @@ export function getPiInvocation(args: string[]): {
|
|
|
89
117
|
return { command: "pi", args };
|
|
90
118
|
}
|
|
91
119
|
|
|
120
|
+
const SKILL_DISCOVERY_CACHE_TTL_MS = 300_000;
|
|
121
|
+
|
|
122
|
+
type ResolvedSkillArgsCacheEntry = {
|
|
123
|
+
skillPaths: Map<string, string>;
|
|
124
|
+
ts: number;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const resolvedSkillArgsCache = new Map<string, ResolvedSkillArgsCacheEntry>();
|
|
128
|
+
|
|
129
|
+
async function canonicalPath(filePath: string): Promise<string> {
|
|
130
|
+
try {
|
|
131
|
+
return await fs.promises.realpath(filePath);
|
|
132
|
+
} catch {
|
|
133
|
+
return path.resolve(filePath);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function buildSkillArgs(
|
|
138
|
+
requested: string[],
|
|
139
|
+
skillPaths: Map<string, string>,
|
|
140
|
+
): string[] {
|
|
141
|
+
return requested.flatMap((name) => ["--skill", skillPaths.get(name) ?? name]);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function resetResolvedAgentSkillArgsCache(): void {
|
|
145
|
+
resolvedSkillArgsCache.clear();
|
|
146
|
+
}
|
|
147
|
+
|
|
92
148
|
export async function resolveAgentSkillArgs(
|
|
93
149
|
cwd: string,
|
|
94
150
|
skillNames: string[],
|
|
95
151
|
): Promise<{ args: string[] } | { error: string }> {
|
|
96
152
|
const requested = Array.from(new Set(skillNames));
|
|
97
153
|
if (requested.length === 0) return { args: [] };
|
|
154
|
+
const cacheIdentitySkills = [...requested].sort();
|
|
155
|
+
const agentDir = getAgentDir();
|
|
156
|
+
const cacheKey = JSON.stringify({
|
|
157
|
+
cwd: await canonicalPath(cwd),
|
|
158
|
+
agentDir: await canonicalPath(agentDir),
|
|
159
|
+
skills: cacheIdentitySkills,
|
|
160
|
+
});
|
|
161
|
+
const cached = resolvedSkillArgsCache.get(cacheKey);
|
|
162
|
+
if (cached && Date.now() - cached.ts <= SKILL_DISCOVERY_CACHE_TTL_MS) {
|
|
163
|
+
return { args: buildSkillArgs(requested, cached.skillPaths) };
|
|
164
|
+
}
|
|
98
165
|
const loader = new DefaultResourceLoader({
|
|
99
166
|
cwd,
|
|
100
|
-
agentDir
|
|
167
|
+
agentDir,
|
|
101
168
|
noContextFiles: true,
|
|
102
169
|
noPromptTemplates: true,
|
|
103
170
|
noThemes: true,
|
|
@@ -124,12 +191,12 @@ export async function resolveAgentSkillArgs(
|
|
|
124
191
|
.join(", ")}. Available skills: ${available}.`,
|
|
125
192
|
};
|
|
126
193
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
};
|
|
194
|
+
const skillPaths = new Map(
|
|
195
|
+
requested.map((name) => [name, skillMap.get(name)?.filePath ?? name]),
|
|
196
|
+
);
|
|
197
|
+
const args = buildSkillArgs(requested, skillPaths);
|
|
198
|
+
resolvedSkillArgsCache.set(cacheKey, { skillPaths, ts: Date.now() });
|
|
199
|
+
return { args };
|
|
133
200
|
}
|
|
134
201
|
|
|
135
202
|
export function getSubagentDepth(): number {
|
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
|
-
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
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
|
}
|