@ryan_nookpi/pi-extension-subagent 0.5.1 → 0.5.3
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/activity.ts +22 -5
- package/agents.ts +14 -1
- package/claude-args.ts +2 -1
- package/claude-sdk-runner.ts +8 -4
- package/commands.ts +32 -6
- package/package.json +1 -1
- package/runner.ts +32 -5
- package/tool-execute.ts +37 -6
- package/utils/agent-utils.ts +29 -11
- package/utils/short-label.ts +1 -1
package/activity.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
9
9
|
import type { CommandRunState } from "./types.js";
|
|
10
|
+
import { getUsedContextPercent } from "./utils/format-utils.js";
|
|
10
11
|
|
|
11
12
|
export const SUBAGENT_ACTIVITY_CUSTOM_TYPE = "subagent-activity";
|
|
12
13
|
export const SUBAGENT_ACTIVITY_SCHEMA_VERSION = 1;
|
|
@@ -24,6 +25,7 @@ export interface ActivitySnapshot {
|
|
|
24
25
|
toolCallCount: number;
|
|
25
26
|
lastLine?: string;
|
|
26
27
|
contextTokens?: number;
|
|
28
|
+
contextWindow?: number;
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
export interface ActivityPayload {
|
|
@@ -36,12 +38,15 @@ export interface ActivityPayload {
|
|
|
36
38
|
toolCallCount: number;
|
|
37
39
|
lastLine?: string;
|
|
38
40
|
contextTokens?: number;
|
|
41
|
+
contextWindow?: number;
|
|
42
|
+
contextPercent?: number;
|
|
39
43
|
}
|
|
40
44
|
|
|
41
45
|
export interface ActivityThrottleState {
|
|
42
46
|
lastEmittedAt: number;
|
|
43
47
|
lastEmittedToolName?: string;
|
|
44
48
|
lastEmittedToolCallCount?: number;
|
|
49
|
+
lastEmittedContextPercent?: number;
|
|
45
50
|
}
|
|
46
51
|
|
|
47
52
|
export function createActivityThrottleState(): ActivityThrottleState {
|
|
@@ -69,8 +74,8 @@ export function normalizeActivityLine(text: string | undefined): string | undefi
|
|
|
69
74
|
/**
|
|
70
75
|
* Change-driven throttle policy. Returns a payload to emit, or null.
|
|
71
76
|
*
|
|
72
|
-
* Emits only while the run is `running`,
|
|
73
|
-
*
|
|
77
|
+
* Emits only while the run is `running`, when tool metadata or the displayed
|
|
78
|
+
* context percentage changes, and at most once per
|
|
74
79
|
* `ACTIVITY_MIN_INTERVAL_MS` per run. A change suppressed by the interval
|
|
75
80
|
* stays pending and emits on a later evaluation.
|
|
76
81
|
*/
|
|
@@ -84,14 +89,18 @@ export function evaluateActivityEmission(
|
|
|
84
89
|
// on the run state, so a positive count is required regardless of the name.
|
|
85
90
|
if (snapshot.toolCallCount <= 0) return null;
|
|
86
91
|
|
|
92
|
+
const contextPercent = getUsedContextPercent(snapshot.contextTokens, snapshot.contextWindow);
|
|
87
93
|
const changed =
|
|
88
|
-
snapshot.lastToolName !== state.lastEmittedToolName ||
|
|
94
|
+
snapshot.lastToolName !== state.lastEmittedToolName ||
|
|
95
|
+
snapshot.toolCallCount !== state.lastEmittedToolCallCount ||
|
|
96
|
+
contextPercent !== state.lastEmittedContextPercent;
|
|
89
97
|
if (!changed) return null;
|
|
90
98
|
if (state.lastEmittedAt !== 0 && now - state.lastEmittedAt < ACTIVITY_MIN_INTERVAL_MS) return null;
|
|
91
99
|
|
|
92
100
|
state.lastEmittedAt = now;
|
|
93
101
|
state.lastEmittedToolName = snapshot.lastToolName;
|
|
94
102
|
state.lastEmittedToolCallCount = snapshot.toolCallCount;
|
|
103
|
+
state.lastEmittedContextPercent = contextPercent;
|
|
95
104
|
|
|
96
105
|
const payload: ActivityPayload = {
|
|
97
106
|
runId: snapshot.runId,
|
|
@@ -105,10 +114,12 @@ export function evaluateActivityEmission(
|
|
|
105
114
|
const lastLine = normalizeActivityLine(snapshot.lastLine);
|
|
106
115
|
if (lastLine) payload.lastLine = lastLine;
|
|
107
116
|
if (snapshot.contextTokens != null && snapshot.contextTokens > 0) payload.contextTokens = snapshot.contextTokens;
|
|
117
|
+
if (snapshot.contextWindow != null && snapshot.contextWindow > 0) payload.contextWindow = snapshot.contextWindow;
|
|
118
|
+
if (contextPercent !== undefined) payload.contextPercent = contextPercent;
|
|
108
119
|
return payload;
|
|
109
120
|
}
|
|
110
121
|
|
|
111
|
-
export function snapshotFromRunState(runState: CommandRunState): ActivitySnapshot {
|
|
122
|
+
export function snapshotFromRunState(runState: CommandRunState, contextWindow?: number): ActivitySnapshot {
|
|
112
123
|
return {
|
|
113
124
|
status: runState.status,
|
|
114
125
|
runId: runState.id,
|
|
@@ -120,6 +131,7 @@ export function snapshotFromRunState(runState: CommandRunState): ActivitySnapsho
|
|
|
120
131
|
toolCallCount: runState.toolCalls,
|
|
121
132
|
lastLine: runState.lastLine,
|
|
122
133
|
contextTokens: runState.usage?.contextTokens,
|
|
134
|
+
contextWindow,
|
|
123
135
|
};
|
|
124
136
|
}
|
|
125
137
|
|
|
@@ -130,11 +142,16 @@ export function snapshotFromRunState(runState: CommandRunState): ActivitySnapsho
|
|
|
130
142
|
export function createRunActivityRecorder(
|
|
131
143
|
pi: Pick<ExtensionAPI, "appendEntry">,
|
|
132
144
|
runState: CommandRunState,
|
|
145
|
+
resolveContextWindow?: (model?: string) => number | undefined,
|
|
133
146
|
): () => void {
|
|
134
147
|
const throttleState = createActivityThrottleState();
|
|
135
148
|
return () => {
|
|
136
149
|
try {
|
|
137
|
-
const payload = evaluateActivityEmission(
|
|
150
|
+
const payload = evaluateActivityEmission(
|
|
151
|
+
throttleState,
|
|
152
|
+
snapshotFromRunState(runState, resolveContextWindow?.(runState.model)),
|
|
153
|
+
Date.now(),
|
|
154
|
+
);
|
|
138
155
|
if (!payload) return;
|
|
139
156
|
pi.appendEntry(SUBAGENT_ACTIVITY_CUSTOM_TYPE, {
|
|
140
157
|
schemaVersion: SUBAGENT_ACTIVITY_SCHEMA_VERSION,
|
package/agents.ts
CHANGED
|
@@ -21,6 +21,10 @@ export interface AgentConfig {
|
|
|
21
21
|
name: string;
|
|
22
22
|
description: string;
|
|
23
23
|
tools?: string[];
|
|
24
|
+
/** Whether the tools field was explicitly present. Explicit [] disables all tools. */
|
|
25
|
+
toolsConfigured?: boolean;
|
|
26
|
+
/** Discovery-time validation failure. Invalid higher-priority definitions still mask lower-priority agents. */
|
|
27
|
+
configurationError?: string;
|
|
24
28
|
model?: string;
|
|
25
29
|
thinking?: AgentThinkingLevel;
|
|
26
30
|
systemPrompt: string;
|
|
@@ -145,7 +149,14 @@ function loadAgentsFromDir(dir: string, source: "user" | "project", options: Loa
|
|
|
145
149
|
if (typeof frontmatter.name !== "string" || typeof frontmatter.description !== "string") continue;
|
|
146
150
|
if (!frontmatter.name || !frontmatter.description) continue;
|
|
147
151
|
|
|
148
|
-
const
|
|
152
|
+
const toolsConfigured = Object.hasOwn(frontmatter, "tools");
|
|
153
|
+
let tools: string[] | undefined;
|
|
154
|
+
let configurationError: string | undefined;
|
|
155
|
+
try {
|
|
156
|
+
tools = toolsConfigured ? normalizeTools(frontmatter.tools, format) : undefined;
|
|
157
|
+
} catch (error) {
|
|
158
|
+
configurationError = `${filePath}: ${error instanceof Error ? error.message : String(error)}`;
|
|
159
|
+
}
|
|
149
160
|
const model = normalizeModel(typeof frontmatter.model === "string" ? frontmatter.model : undefined, format);
|
|
150
161
|
const thinking = normalizeThinkingLevel(
|
|
151
162
|
typeof frontmatter.thinking === "string" ? frontmatter.thinking : undefined,
|
|
@@ -156,6 +167,8 @@ function loadAgentsFromDir(dir: string, source: "user" | "project", options: Loa
|
|
|
156
167
|
name: frontmatter.name,
|
|
157
168
|
description: frontmatter.description,
|
|
158
169
|
tools,
|
|
170
|
+
toolsConfigured,
|
|
171
|
+
configurationError,
|
|
159
172
|
model,
|
|
160
173
|
thinking,
|
|
161
174
|
systemPrompt: attachCommonSubagentRule(body, runtime),
|
package/claude-args.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { mapPiToolsToClaude } from "./utils/agent-utils.js";
|
|
|
5
5
|
export interface ClaudeArgsConfig {
|
|
6
6
|
prompt: string;
|
|
7
7
|
tools: string[];
|
|
8
|
+
toolsConfigured?: boolean;
|
|
8
9
|
model?: string;
|
|
9
10
|
thinking?: string;
|
|
10
11
|
resumeSessionId?: string;
|
|
@@ -63,7 +64,7 @@ export function buildClaudeArgs(config: ClaudeArgsConfig): string[] {
|
|
|
63
64
|
|
|
64
65
|
const claudeTools = mapPiToolsToClaude(config.tools);
|
|
65
66
|
|
|
66
|
-
if (claudeTools.length > 0) {
|
|
67
|
+
if (config.toolsConfigured || claudeTools.length > 0) {
|
|
67
68
|
args.push("--tools", claudeTools.join(","));
|
|
68
69
|
args.push("--allowedTools", claudeTools.join(","));
|
|
69
70
|
}
|
package/claude-sdk-runner.ts
CHANGED
|
@@ -31,8 +31,12 @@ function normalizeTaskForSubagentPrompt(task: string): string {
|
|
|
31
31
|
return task;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
function resolveClaudeSdkBuiltInTools(
|
|
35
|
-
|
|
34
|
+
function resolveClaudeSdkBuiltInTools(
|
|
35
|
+
requestedTools: string[] | undefined,
|
|
36
|
+
toolsConfigured: boolean,
|
|
37
|
+
): string[] | undefined {
|
|
38
|
+
if (!toolsConfigured) return undefined;
|
|
39
|
+
if (!requestedTools || requestedTools.length === 0) return [];
|
|
36
40
|
return mapPiToolsToClaude(requestedTools);
|
|
37
41
|
}
|
|
38
42
|
|
|
@@ -74,7 +78,7 @@ export async function runClaudeAgentViaSdk(
|
|
|
74
78
|
|
|
75
79
|
let enabledTools: string[] | undefined;
|
|
76
80
|
try {
|
|
77
|
-
enabledTools = resolveClaudeSdkBuiltInTools(agent.tools);
|
|
81
|
+
enabledTools = resolveClaudeSdkBuiltInTools(agent.tools, agent.toolsConfigured ?? agent.tools !== undefined);
|
|
78
82
|
} catch (error: any) {
|
|
79
83
|
return {
|
|
80
84
|
agent: agent.name,
|
|
@@ -140,7 +144,7 @@ export async function runClaudeAgentViaSdk(
|
|
|
140
144
|
settingSources: [],
|
|
141
145
|
includePartialMessages: true,
|
|
142
146
|
persistSession: true,
|
|
143
|
-
...(enabledTools ? { tools: enabledTools, allowedTools: enabledTools } : {}),
|
|
147
|
+
...(enabledTools !== undefined ? { tools: enabledTools, allowedTools: enabledTools } : {}),
|
|
144
148
|
resume: resumeSessionId,
|
|
145
149
|
stderr: (data) => {
|
|
146
150
|
stderrBuf += data;
|
package/commands.ts
CHANGED
|
@@ -35,7 +35,14 @@ import {
|
|
|
35
35
|
shouldSummarizeSubagentTask,
|
|
36
36
|
summarizeSubagentDisplayTask,
|
|
37
37
|
} from "./display-task.js";
|
|
38
|
-
import {
|
|
38
|
+
import {
|
|
39
|
+
AGENT_NAME_PALETTE,
|
|
40
|
+
agentBgIndex,
|
|
41
|
+
formatUsageStats,
|
|
42
|
+
resolveContextWindow,
|
|
43
|
+
truncateLines,
|
|
44
|
+
truncatePlainToWidth,
|
|
45
|
+
} from "./format.js";
|
|
39
46
|
import {
|
|
40
47
|
clearPendingGroupCompletion,
|
|
41
48
|
consumePendingGroupCompletionsForSession,
|
|
@@ -962,9 +969,15 @@ export function registerAll(pi: ExtensionAPI, store: SubagentStore): SubagentReg
|
|
|
962
969
|
const lines = agents.map((agent) => {
|
|
963
970
|
const model = agent.model ?? "(inherit current model)";
|
|
964
971
|
const thinking = agent.thinking ?? "(inherit current thinking)";
|
|
965
|
-
const tools =
|
|
972
|
+
const tools =
|
|
973
|
+
(agent.toolsConfigured ?? agent.tools !== undefined)
|
|
974
|
+
? agent.tools?.length
|
|
975
|
+
? agent.tools.join(",")
|
|
976
|
+
: "none"
|
|
977
|
+
: "default";
|
|
966
978
|
const description = agent.description ? ` · ${agent.description}` : "";
|
|
967
|
-
|
|
979
|
+
const invalid = agent.configurationError ? ` · INVALID: ${agent.configurationError}` : "";
|
|
980
|
+
return `${agent.name} [${agent.source}] · model: ${model} · thinking: ${thinking} · tools: ${tools}${description}${invalid}`;
|
|
968
981
|
});
|
|
969
982
|
|
|
970
983
|
return {
|
|
@@ -1223,6 +1236,15 @@ export function registerAll(pi: ExtensionAPI, store: SubagentStore): SubagentReg
|
|
|
1223
1236
|
taskForAgent = taskForDisplay;
|
|
1224
1237
|
}
|
|
1225
1238
|
|
|
1239
|
+
const selectedConfig = agents.find((agent) => agent.name === selectedAgent);
|
|
1240
|
+
if (selectedConfig?.configurationError) {
|
|
1241
|
+
ctx.ui.notify(
|
|
1242
|
+
`Invalid agent configuration for "${selectedAgent}": ${selectedConfig.configurationError}`,
|
|
1243
|
+
"error",
|
|
1244
|
+
);
|
|
1245
|
+
return;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1226
1248
|
let runId: number;
|
|
1227
1249
|
let runState: CommandRunState;
|
|
1228
1250
|
|
|
@@ -1402,7 +1424,9 @@ export function registerAll(pi: ExtensionAPI, store: SubagentStore): SubagentReg
|
|
|
1402
1424
|
}, RUN_TICK_INTERVAL_MS);
|
|
1403
1425
|
|
|
1404
1426
|
let claudeCheckpointSent = !!runState.claudeSessionId;
|
|
1405
|
-
const recordActivity = hiddenFromMain
|
|
1427
|
+
const recordActivity = hiddenFromMain
|
|
1428
|
+
? () => {}
|
|
1429
|
+
: createRunActivityRecorder(pi, runState, (model) => resolveContextWindow(ctx, model));
|
|
1406
1430
|
void (async () => {
|
|
1407
1431
|
try {
|
|
1408
1432
|
const { result, retryCount } = await invokeWithAutoRetry({
|
|
@@ -1662,14 +1686,16 @@ export function registerAll(pi: ExtensionAPI, store: SubagentStore): SubagentReg
|
|
|
1662
1686
|
if (starterPackNotice) ctx.ui.notify(starterPackNotice, "info");
|
|
1663
1687
|
|
|
1664
1688
|
const lines = agents.map((a) => {
|
|
1665
|
-
const tools =
|
|
1689
|
+
const tools =
|
|
1690
|
+
(a.toolsConfigured ?? a.tools !== undefined) ? (a.tools?.length ? a.tools.join(",") : "none") : "default";
|
|
1666
1691
|
const model = a.model ?? "(inherit current model)";
|
|
1667
1692
|
const thinking = a.thinking ?? "(inherit current thinking)";
|
|
1668
1693
|
const description = a.description ? ` · ${a.description}` : "";
|
|
1694
|
+
const invalid = a.configurationError ? ` · INVALID: ${a.configurationError}` : "";
|
|
1669
1695
|
const colorCode = AGENT_NAME_PALETTE[agentBgIndex(a.name)];
|
|
1670
1696
|
const coloredName = `\x1b[38;5;${colorCode}m${a.name}\x1b[39m`;
|
|
1671
1697
|
return truncateText(
|
|
1672
|
-
`${coloredName} [${a.source}] · model: ${model} · thinking: ${thinking} · tools: ${tools}${description}`,
|
|
1698
|
+
`${coloredName} [${a.source}] · model: ${model} · thinking: ${thinking} · tools: ${tools}${description}${invalid}`,
|
|
1673
1699
|
220,
|
|
1674
1700
|
);
|
|
1675
1701
|
});
|
package/package.json
CHANGED
package/runner.ts
CHANGED
|
@@ -82,6 +82,12 @@ function appendStderrDiagnostic(result: SingleResult, message: string): void {
|
|
|
82
82
|
result.stderr = result.stderr ? `${result.stderr.trimEnd()}\n${line}\n` : `${line}\n`;
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
function normalizeChildExitCode(code: number | null, signal: NodeJS.Signals | null, fallbackCode = 0): number {
|
|
86
|
+
if (code !== null) return code;
|
|
87
|
+
if (signal !== null) return 1;
|
|
88
|
+
return fallbackCode;
|
|
89
|
+
}
|
|
90
|
+
|
|
85
91
|
/**
|
|
86
92
|
* Prevent tasks starting with `/` from being treated as slash commands
|
|
87
93
|
* inside the spawned pi process.
|
|
@@ -225,6 +231,19 @@ export async function runSingleAgent(
|
|
|
225
231
|
};
|
|
226
232
|
}
|
|
227
233
|
|
|
234
|
+
if (agent.configurationError) {
|
|
235
|
+
return {
|
|
236
|
+
agent: agent.name,
|
|
237
|
+
agentSource: agent.source,
|
|
238
|
+
task,
|
|
239
|
+
exitCode: 1,
|
|
240
|
+
messages: [],
|
|
241
|
+
stderr: `Invalid agent configuration: ${agent.configurationError}`,
|
|
242
|
+
usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 },
|
|
243
|
+
step,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
228
247
|
if (agent.runtime === "claude") {
|
|
229
248
|
if (resolveClaudeRuntimeMode(defaultCwd) === "sdk") {
|
|
230
249
|
const { runClaudeAgentViaSdk } = await import("./claude-sdk-runner.js");
|
|
@@ -364,6 +383,7 @@ async function runClaudeAgent(
|
|
|
364
383
|
const args = buildClaudeArgs({
|
|
365
384
|
prompt: normalizeTaskForSubagentPrompt(task),
|
|
366
385
|
tools: agent.tools ?? [],
|
|
386
|
+
toolsConfigured: agent.toolsConfigured ?? agent.tools !== undefined,
|
|
367
387
|
model: agent.model,
|
|
368
388
|
thinking: agent.thinking,
|
|
369
389
|
resumeSessionId: resumeId,
|
|
@@ -545,7 +565,7 @@ async function runClaudeAgent(
|
|
|
545
565
|
|
|
546
566
|
proc.on("exit", (code, terminationSignal) => {
|
|
547
567
|
procExited = true;
|
|
548
|
-
lastExitCode = code
|
|
568
|
+
lastExitCode = normalizeChildExitCode(code, terminationSignal, lastExitCode);
|
|
549
569
|
lastProcessSignal = terminationSignal;
|
|
550
570
|
diagnose({ event: "exit", childPid: proc.pid, code, signal: terminationSignal });
|
|
551
571
|
if (streamState.resultReceived) {
|
|
@@ -553,6 +573,9 @@ async function runClaudeAgent(
|
|
|
553
573
|
resolveOnce(0);
|
|
554
574
|
return;
|
|
555
575
|
}
|
|
576
|
+
if (terminationSignal && !settled && !wasAborted) {
|
|
577
|
+
stderrBuf += `[runner] process terminated by signal ${terminationSignal}\n`;
|
|
578
|
+
}
|
|
556
579
|
exitFallbackTimer = setTimeout(() => {
|
|
557
580
|
settleReason = "exit_fallback_timeout";
|
|
558
581
|
resolveOnce(lastExitCode);
|
|
@@ -565,7 +588,7 @@ async function runClaudeAgent(
|
|
|
565
588
|
diagnose({ event: "close", childPid: proc.pid, code, signal: terminationSignal });
|
|
566
589
|
if (!settled) {
|
|
567
590
|
settleReason = "close";
|
|
568
|
-
resolveOnce(streamState.resultReceived ? 0 : (code
|
|
591
|
+
resolveOnce(streamState.resultReceived ? 0 : normalizeChildExitCode(code, lastProcessSignal, lastExitCode));
|
|
569
592
|
}
|
|
570
593
|
});
|
|
571
594
|
|
|
@@ -640,7 +663,8 @@ async function runPiAgent(
|
|
|
640
663
|
else args.push("--no-session");
|
|
641
664
|
if (agent.model) args.push("--model", agent.model);
|
|
642
665
|
if (agent.thinking) args.push("--thinking", agent.thinking);
|
|
643
|
-
if (agent.tools && agent.tools
|
|
666
|
+
if ((agent.toolsConfigured ?? agent.tools !== undefined) && agent.tools?.length === 0) args.push("--no-tools");
|
|
667
|
+
else if (agent.tools && agent.tools.length > 0) args.push("--tools", agent.tools.join(","));
|
|
644
668
|
|
|
645
669
|
let tmpPromptDir: string | null = null;
|
|
646
670
|
let tmpPromptPath: string | null = null;
|
|
@@ -1066,9 +1090,12 @@ async function runPiAgent(
|
|
|
1066
1090
|
|
|
1067
1091
|
proc.on("exit", (code, terminationSignal) => {
|
|
1068
1092
|
procExited = true;
|
|
1069
|
-
lastExitCode = code
|
|
1093
|
+
lastExitCode = normalizeChildExitCode(code, terminationSignal, lastExitCode);
|
|
1070
1094
|
lastProcessSignal = terminationSignal;
|
|
1071
1095
|
diagnose({ event: "exit", childPid: proc.pid, code, signal: terminationSignal });
|
|
1096
|
+
if (terminationSignal && !settled && !wasAborted) {
|
|
1097
|
+
appendStderrDiagnostic(currentResult, `process terminated by signal ${terminationSignal}`);
|
|
1098
|
+
}
|
|
1072
1099
|
// In rare cases stdout/stderr pipes may stay open after process exit.
|
|
1073
1100
|
// Use a short fallback so runs cannot stay "running" forever.
|
|
1074
1101
|
exitFallbackTimer = setTimeout(() => {
|
|
@@ -1082,7 +1109,7 @@ async function runPiAgent(
|
|
|
1082
1109
|
lastProcessSignal = terminationSignal ?? lastProcessSignal;
|
|
1083
1110
|
diagnose({ event: "close", childPid: proc.pid, code, signal: terminationSignal });
|
|
1084
1111
|
settleReason = "close";
|
|
1085
|
-
resolveOnce(code
|
|
1112
|
+
resolveOnce(normalizeChildExitCode(code, lastProcessSignal, lastExitCode));
|
|
1086
1113
|
});
|
|
1087
1114
|
|
|
1088
1115
|
proc.on("error", (error) => {
|
package/tool-execute.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import * as fs from "node:fs";
|
|
10
10
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
11
11
|
import { createRunActivityRecorder } from "./activity.js";
|
|
12
|
-
import { discoverAgents } from "./agents.js";
|
|
12
|
+
import { type AgentConfig, discoverAgents } from "./agents.js";
|
|
13
13
|
import { parseSubagentToolCommand, SUBAGENT_CLI_HELP_TEXT } from "./cli.js";
|
|
14
14
|
import {
|
|
15
15
|
DEFAULT_TURN_COUNT,
|
|
@@ -313,8 +313,15 @@ export function diagnoseResultFailure(result: SingleResult): ResultFailureDiagno
|
|
|
313
313
|
};
|
|
314
314
|
}
|
|
315
315
|
}
|
|
316
|
-
if (result.exitCode !== 0)
|
|
317
|
-
|
|
316
|
+
if (result.exitCode !== 0) {
|
|
317
|
+
const detail = (result.errorMessage || result.stderr || "").trim();
|
|
318
|
+
const detailPreview = detail ? truncateLines(detail, 4).slice(0, STATUS_OUTPUT_PREVIEW_MAX_CHARS).trimEnd() : "";
|
|
319
|
+
return {
|
|
320
|
+
failed: true,
|
|
321
|
+
errorClass,
|
|
322
|
+
reason: `Subagent process exited with code ${result.exitCode}.${detailPreview ? ` Cause: ${detailPreview}` : ""}`,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
318
325
|
if (result.stopReason === "error")
|
|
319
326
|
return { failed: true, errorClass, reason: result.errorMessage || "Subagent reported stopReason=error." };
|
|
320
327
|
if (result.stopReason === "aborted")
|
|
@@ -810,9 +817,15 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
810
817
|
const lines = agents.map((agent) => {
|
|
811
818
|
const model = agent.model ?? "(inherit current model)";
|
|
812
819
|
const thinking = agent.thinking ?? "(inherit current thinking)";
|
|
813
|
-
const tools =
|
|
820
|
+
const tools =
|
|
821
|
+
(agent.toolsConfigured ?? agent.tools !== undefined)
|
|
822
|
+
? agent.tools?.length
|
|
823
|
+
? agent.tools.join(",")
|
|
824
|
+
: "none"
|
|
825
|
+
: "default";
|
|
814
826
|
const description = agent.description ? ` · ${agent.description}` : "";
|
|
815
|
-
|
|
827
|
+
const invalid = agent.configurationError ? ` · INVALID: ${agent.configurationError}` : "";
|
|
828
|
+
return `${agent.name} [${agent.source}] · model: ${model} · thinking: ${thinking} · tools: ${tools}${description}${invalid}`;
|
|
816
829
|
});
|
|
817
830
|
|
|
818
831
|
return {
|
|
@@ -902,6 +915,24 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
902
915
|
isError: true,
|
|
903
916
|
};
|
|
904
917
|
}
|
|
918
|
+
|
|
919
|
+
const invalidAgents = [...new Set(requestedNames)]
|
|
920
|
+
.map((name) => agents.find((agent) => agent.name === name))
|
|
921
|
+
.filter((agent): agent is AgentConfig => Boolean(agent?.configurationError));
|
|
922
|
+
if (invalidAgents.length > 0) {
|
|
923
|
+
return {
|
|
924
|
+
content: [
|
|
925
|
+
{
|
|
926
|
+
type: "text",
|
|
927
|
+
text: `❌ Invalid agent configuration:\n${invalidAgents
|
|
928
|
+
.map((agent) => `- ${agent.name}: ${agent.configurationError}`)
|
|
929
|
+
.join("\n")}`,
|
|
930
|
+
},
|
|
931
|
+
],
|
|
932
|
+
details: makeDetails(),
|
|
933
|
+
isError: true,
|
|
934
|
+
};
|
|
935
|
+
}
|
|
905
936
|
}
|
|
906
937
|
|
|
907
938
|
if (asyncAction === "list") {
|
|
@@ -1319,7 +1350,7 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
1319
1350
|
|
|
1320
1351
|
function launchRunInBackground(runState: CommandRunState, taskForAgent: string): Promise<FinalizedRun> {
|
|
1321
1352
|
let claudeCheckpointSent = !!runState.claudeSessionId;
|
|
1322
|
-
const recordActivity = createRunActivityRecorder(pi, runState);
|
|
1353
|
+
const recordActivity = createRunActivityRecorder(pi, runState, (model) => resolveContextWindow(ctx, model));
|
|
1323
1354
|
return enqueueSubagentInvocation(() =>
|
|
1324
1355
|
runSingleAgent(
|
|
1325
1356
|
ctx.cwd,
|
package/utils/agent-utils.ts
CHANGED
|
@@ -89,20 +89,38 @@ export const CLAUDE_MODEL_ALIAS_MAP: Record<string, string> = {
|
|
|
89
89
|
* For "claude" format, maps Claude tool names to pi equivalents.
|
|
90
90
|
*/
|
|
91
91
|
export function normalizeTools(rawTools: unknown, format: "pi" | "claude"): string[] | undefined {
|
|
92
|
-
|
|
93
|
-
.filter((tool): tool is string => typeof tool === "string")
|
|
94
|
-
.map((tool) => tool.trim())
|
|
95
|
-
.filter(Boolean);
|
|
92
|
+
if (rawTools === undefined) return undefined;
|
|
96
93
|
|
|
97
|
-
|
|
98
|
-
if (
|
|
94
|
+
let parsed: string[];
|
|
95
|
+
if (Array.isArray(rawTools)) {
|
|
96
|
+
if (rawTools.some((tool) => typeof tool !== "string" || tool.trim().length === 0)) {
|
|
97
|
+
throw new Error("Explicit tools must contain only non-empty strings.");
|
|
98
|
+
}
|
|
99
|
+
parsed = rawTools.map((tool) => (tool as string).trim());
|
|
100
|
+
} else if (typeof rawTools === "string") {
|
|
101
|
+
parsed = rawTools
|
|
102
|
+
.split(",")
|
|
103
|
+
.map((tool) => tool.trim())
|
|
104
|
+
.filter(Boolean);
|
|
105
|
+
if (parsed.length === 0) throw new Error("Explicit tools must not be an empty string.");
|
|
106
|
+
} else {
|
|
107
|
+
throw new Error("Explicit tools must be a comma-separated string or an array of strings.");
|
|
108
|
+
}
|
|
99
109
|
|
|
100
|
-
|
|
101
|
-
.map((tool) => CLAUDE_TOOL_MAP[tool.toLowerCase()] ?? undefined)
|
|
102
|
-
.filter((tool): tool is string => Boolean(tool));
|
|
110
|
+
if (format === "pi") return Array.from(new Set(parsed));
|
|
103
111
|
|
|
104
|
-
|
|
105
|
-
|
|
112
|
+
const unsupported = parsed.filter((tool) => !CLAUDE_TOOL_MAP[tool.toLowerCase()]);
|
|
113
|
+
if (unsupported.length > 0) {
|
|
114
|
+
throw new Error(
|
|
115
|
+
`Unsupported Claude tool${unsupported.length > 1 ? "s" : ""}: ${unsupported.join(", ")}. Supported tools: ${Object.keys(
|
|
116
|
+
CLAUDE_TOOL_MAP,
|
|
117
|
+
)
|
|
118
|
+
.filter((tool) => CLAUDE_TOOL_MAP[tool])
|
|
119
|
+
.join(", ")}`,
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return Array.from(new Set(parsed.map((tool) => CLAUDE_TOOL_MAP[tool.toLowerCase()] as string)));
|
|
106
124
|
}
|
|
107
125
|
|
|
108
126
|
/**
|
package/utils/short-label.ts
CHANGED