@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
package/src/child/process.ts
CHANGED
|
@@ -32,12 +32,14 @@ import {
|
|
|
32
32
|
detectMessageError,
|
|
33
33
|
getPiInvocation,
|
|
34
34
|
getSubagentDepth,
|
|
35
|
+
getSubagentRuntimeLimits,
|
|
35
36
|
resolveAgentSkillArgs,
|
|
36
37
|
subagentDepthEnv,
|
|
37
38
|
truncateOutput,
|
|
38
39
|
writePromptToTempFile,
|
|
39
40
|
} from "../shared/utils.js";
|
|
40
41
|
import {
|
|
42
|
+
type ChildEventParseResult,
|
|
41
43
|
type ChildKnownEvent,
|
|
42
44
|
parseChildEventLine,
|
|
43
45
|
TOOL_EXECUTION_UPDATE_EVENT,
|
|
@@ -48,8 +50,6 @@ import {
|
|
|
48
50
|
terminateChildProcess,
|
|
49
51
|
} from "./termination.js";
|
|
50
52
|
|
|
51
|
-
const MAX_STDERR_BYTES = 10_000;
|
|
52
|
-
const AGENT_END_GRACE_MS = 250;
|
|
53
53
|
export function resolveThinkingLevel(
|
|
54
54
|
requested: ThinkingLevel,
|
|
55
55
|
provider: string,
|
|
@@ -73,15 +73,17 @@ export function resolveThinkingLevel(
|
|
|
73
73
|
return { level: clamped, warning: mkWarning(clamped) };
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
const MAX_SUBAGENT_DEPTH = 2;
|
|
77
76
|
export const TOOL_RESULT_FAILED_MESSAGE = "Subagent tool result failed.";
|
|
78
77
|
|
|
78
|
+
type RuntimeLimits = ReturnType<typeof getSubagentRuntimeLimits>;
|
|
79
79
|
type RuntimeResult = SingleResult & { messages: Message[] };
|
|
80
80
|
|
|
81
81
|
export class SubagentAbortError extends Error {
|
|
82
|
-
|
|
82
|
+
readonly result: SingleResult;
|
|
83
|
+
constructor(result: SingleResult) {
|
|
83
84
|
super("Subagent was aborted");
|
|
84
85
|
this.name = "SubagentAbortError";
|
|
86
|
+
this.result = result;
|
|
85
87
|
}
|
|
86
88
|
}
|
|
87
89
|
|
|
@@ -91,6 +93,7 @@ type PromptSetupResult = { tmpPrompt: TempPrompt | null } | { error: unknown };
|
|
|
91
93
|
|
|
92
94
|
interface SubagentState {
|
|
93
95
|
result: RuntimeResult;
|
|
96
|
+
runtimeLimits: RuntimeLimits;
|
|
94
97
|
spawnError?: Error;
|
|
95
98
|
wasAborted: boolean;
|
|
96
99
|
agentEndGraceTimer?: ReturnType<typeof setTimeout>;
|
|
@@ -99,25 +102,41 @@ interface SubagentState {
|
|
|
99
102
|
|
|
100
103
|
function appendWithByteLimit(
|
|
101
104
|
current: string,
|
|
102
|
-
data: string,
|
|
105
|
+
data: string | Buffer,
|
|
103
106
|
max: number,
|
|
104
107
|
): string {
|
|
105
|
-
|
|
106
|
-
|
|
108
|
+
const currentBytes = Buffer.from(current, "utf-8");
|
|
109
|
+
if (currentBytes.length >= max) return current;
|
|
110
|
+
const incomingBytes = Buffer.isBuffer(data)
|
|
111
|
+
? data
|
|
112
|
+
: Buffer.from(data, "utf-8");
|
|
113
|
+
const combined = Buffer.concat([currentBytes, incomingBytes]);
|
|
114
|
+
if (combined.length <= max) return combined.toString("utf-8");
|
|
115
|
+
return truncateValidUtf8(combined, max);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function truncateValidUtf8(buffer: Buffer, max: number): string {
|
|
119
|
+
let end = Math.min(max, buffer.length);
|
|
120
|
+
while (end > 0) {
|
|
121
|
+
const candidate = buffer.subarray(0, end).toString("utf-8");
|
|
122
|
+
if (!candidate.endsWith("�")) return candidate;
|
|
123
|
+
end -= 1;
|
|
124
|
+
}
|
|
125
|
+
return "";
|
|
107
126
|
}
|
|
108
127
|
|
|
109
128
|
/**
|
|
110
|
-
* Attempts to resolve the context window token limit for a given message's model.
|
|
111
129
|
* Rationale: Subagent usage reporting needs context window awareness to provide
|
|
112
130
|
* meaningful "context full" indicators to the parent.
|
|
113
131
|
*/
|
|
114
132
|
function resolveContextWindowTokens(msg: Message): number | undefined {
|
|
115
133
|
const m = msg as unknown as Record<string, unknown>;
|
|
116
|
-
if (typeof m
|
|
134
|
+
if (typeof m["provider"] !== "string" || typeof m["model"] !== "string")
|
|
135
|
+
return;
|
|
117
136
|
try {
|
|
118
137
|
const contextWindow = getModel(
|
|
119
|
-
m
|
|
120
|
-
m
|
|
138
|
+
m["provider"] as never,
|
|
139
|
+
m["model"] as never,
|
|
121
140
|
)?.contextWindow;
|
|
122
141
|
return Number.isFinite(contextWindow) && contextWindow > 0
|
|
123
142
|
? contextWindow
|
|
@@ -134,11 +153,6 @@ function getAbortReason(signal: AbortSignal): string {
|
|
|
134
153
|
return "abort";
|
|
135
154
|
}
|
|
136
155
|
|
|
137
|
-
/**
|
|
138
|
-
* Verifies if the agent produced any textual output or final response.
|
|
139
|
-
* Precondition: Called after process exit to distinguish between clean completion
|
|
140
|
-
* and silent failures where the process exited 0 but did nothing.
|
|
141
|
-
*/
|
|
142
156
|
function hasCompletedAgentOutput(result: RuntimeResult): boolean {
|
|
143
157
|
if (result.finalOutput.trim()) return true;
|
|
144
158
|
return result.messages.some(
|
|
@@ -151,7 +165,6 @@ function hasCompletedAgentOutput(result: RuntimeResult): boolean {
|
|
|
151
165
|
}
|
|
152
166
|
|
|
153
167
|
/**
|
|
154
|
-
* Determines the exit code for processes terminated via the agent_end timeout.
|
|
155
168
|
* Rationale: `pi` processes in JSON mode might hang after finishing their task;
|
|
156
169
|
* we force-kill them after a grace period and treat it as success (0) if they
|
|
157
170
|
* actually produced output.
|
|
@@ -168,7 +181,6 @@ function getAgentEndTimeoutExitCode(
|
|
|
168
181
|
}
|
|
169
182
|
|
|
170
183
|
/**
|
|
171
|
-
* Orchestrates the cleanup and exit code capture of a child process.
|
|
172
184
|
* Safety: Implements a dual-timer strategy (idle and hard) to ensure streams
|
|
173
185
|
* are destroyed and promises settled even if the process or its pipes hang.
|
|
174
186
|
*/
|
|
@@ -278,8 +290,9 @@ function accumulateUsage(result: RuntimeResult, msg: Message): void {
|
|
|
278
290
|
result.usage.cacheWrite += usage.cacheWrite || 0;
|
|
279
291
|
result.usage.cost += usage.cost?.total || 0;
|
|
280
292
|
result.usage.contextTokens = usage.totalTokens || 0;
|
|
281
|
-
|
|
282
|
-
|
|
293
|
+
const ctxWindowTokens = resolveContextWindowTokens(msg);
|
|
294
|
+
if (ctxWindowTokens !== undefined)
|
|
295
|
+
result.usage.contextWindowTokens = ctxWindowTokens;
|
|
283
296
|
}
|
|
284
297
|
|
|
285
298
|
function addMessageToResult(result: RuntimeResult, msg: Message): void {
|
|
@@ -288,7 +301,7 @@ function addMessageToResult(result: RuntimeResult, msg: Message): void {
|
|
|
288
301
|
if (msg.role === "toolResult" && msg.isError) {
|
|
289
302
|
result.errorMessage ||= TOOL_RESULT_FAILED_MESSAGE;
|
|
290
303
|
} else if (result.errorMessage === TOOL_RESULT_FAILED_MESSAGE) {
|
|
291
|
-
result.errorMessage
|
|
304
|
+
delete result.errorMessage;
|
|
292
305
|
}
|
|
293
306
|
if (msg.role === "assistant") {
|
|
294
307
|
accumulateUsage(result, msg);
|
|
@@ -336,13 +349,14 @@ function errorForDepthLimit(
|
|
|
336
349
|
source: "user" | "project" | "unknown",
|
|
337
350
|
task: string,
|
|
338
351
|
depth: number,
|
|
352
|
+
maxDepth: number,
|
|
339
353
|
model?: string,
|
|
340
354
|
): SingleResult {
|
|
341
355
|
return createErrorResult(
|
|
342
356
|
agentName,
|
|
343
357
|
source,
|
|
344
358
|
task,
|
|
345
|
-
`Subagent nesting limit reached (depth ${depth}/${
|
|
359
|
+
`Subagent nesting limit reached (depth ${depth}/${maxDepth}).`,
|
|
346
360
|
model,
|
|
347
361
|
);
|
|
348
362
|
}
|
|
@@ -387,12 +401,6 @@ function findRecentMessagesAnchor(messages: Message[]): number {
|
|
|
387
401
|
return -1;
|
|
388
402
|
}
|
|
389
403
|
|
|
390
|
-
/**
|
|
391
|
-
* Derives current execution progress from accumulated messages.
|
|
392
|
-
* Maps tool calls to UI-safe previews for real-time feedback.
|
|
393
|
-
* Builds activeToolActivity from the most recent tool call, providing
|
|
394
|
-
* a compact parent summary for subagent tools before nested child data arrives.
|
|
395
|
-
*/
|
|
396
404
|
function deriveStreamingProgress(messages: Message[]): StreamingProgress {
|
|
397
405
|
const toolCalls: { id: string; preview: string }[] = [];
|
|
398
406
|
let lastToolPreview: string | undefined;
|
|
@@ -410,18 +418,15 @@ function deriveStreamingProgress(messages: Message[]): StreamingProgress {
|
|
|
410
418
|
activeToolActivity = { toolName: part.name, inputSummary: preview };
|
|
411
419
|
}
|
|
412
420
|
}
|
|
421
|
+
const activityText = renderToolActivity(activeToolActivity);
|
|
413
422
|
return {
|
|
414
423
|
activeToolActivity,
|
|
415
|
-
activityText
|
|
424
|
+
activityText,
|
|
416
425
|
toolCalls,
|
|
417
426
|
lastToolPreview,
|
|
418
427
|
};
|
|
419
428
|
}
|
|
420
429
|
|
|
421
|
-
/**
|
|
422
|
-
* Prevents leaking secrets in the CLI progress display.
|
|
423
|
-
* Redacts values if the preview contains sensitive keywords.
|
|
424
|
-
*/
|
|
425
430
|
function sanitizeProgressPreview(preview: string, toolName: string): string {
|
|
426
431
|
return SENSITIVE_PATTERN.test(preview) ? toolName : preview;
|
|
427
432
|
}
|
|
@@ -447,7 +452,9 @@ export function makeEmitUpdate(
|
|
|
447
452
|
// so the parent retains nested context until newer activity arrives
|
|
448
453
|
if (options?.toolResultCompleted && result.progress?.activeToolActivity) {
|
|
449
454
|
progress.activeToolActivity = result.progress.activeToolActivity;
|
|
450
|
-
|
|
455
|
+
const renderedText = renderToolActivity(progress.activeToolActivity);
|
|
456
|
+
if (renderedText !== undefined) progress.activityText = renderedText;
|
|
457
|
+
else delete progress.activityText;
|
|
451
458
|
}
|
|
452
459
|
// Handle parsed tool activity from child events
|
|
453
460
|
// Merge with parent activity if this is a nested update
|
|
@@ -474,7 +481,10 @@ export function makeEmitUpdate(
|
|
|
474
481
|
} else {
|
|
475
482
|
progress.activeToolActivity = options.toolActivity;
|
|
476
483
|
}
|
|
477
|
-
|
|
484
|
+
const renderedActivity = renderToolActivity(progress.activeToolActivity);
|
|
485
|
+
if (renderedActivity !== undefined)
|
|
486
|
+
progress.activityText = renderedActivity;
|
|
487
|
+
else delete progress.activityText;
|
|
478
488
|
}
|
|
479
489
|
if (options?.toolResultCompleted) {
|
|
480
490
|
progress.toolResultCompleted = true;
|
|
@@ -515,7 +525,7 @@ function makeRequestTerminator(
|
|
|
515
525
|
function clearGraceTimer(state: SubagentState): void {
|
|
516
526
|
if (!state.agentEndGraceTimer) return;
|
|
517
527
|
clearTimeout(state.agentEndGraceTimer);
|
|
518
|
-
state.agentEndGraceTimer
|
|
528
|
+
delete state.agentEndGraceTimer;
|
|
519
529
|
}
|
|
520
530
|
|
|
521
531
|
function handleMessageEvent(
|
|
@@ -563,12 +573,25 @@ function handleAgentEndEvent(
|
|
|
563
573
|
}
|
|
564
574
|
if (state.agentEndGraceTimer || state.terminationPromise) return;
|
|
565
575
|
state.agentEndGraceTimer = setTimeout(() => {
|
|
566
|
-
state.agentEndGraceTimer
|
|
576
|
+
delete state.agentEndGraceTimer;
|
|
567
577
|
void requestTermination("agent_end_timeout");
|
|
568
|
-
},
|
|
578
|
+
}, state.runtimeLimits.agentEndGraceMs);
|
|
569
579
|
state.agentEndGraceTimer.unref?.();
|
|
570
580
|
}
|
|
571
581
|
|
|
582
|
+
function formatUnknownEventDiagnostic(
|
|
583
|
+
line: string,
|
|
584
|
+
parseResult: Exclude<ChildEventParseResult, { kind: "known" }>,
|
|
585
|
+
): string {
|
|
586
|
+
if (parseResult.kind === "invalid" && !line.trim()) {
|
|
587
|
+
return "[pi-subagent:unknown-event] blank";
|
|
588
|
+
}
|
|
589
|
+
if (parseResult.kind === "invalid") {
|
|
590
|
+
return `[pi-subagent:unknown-event] malformed: ${line}`;
|
|
591
|
+
}
|
|
592
|
+
return `[pi-subagent:unknown-event] unknown: ${JSON.stringify(parseResult.event)}`;
|
|
593
|
+
}
|
|
594
|
+
|
|
572
595
|
function processEventLine(
|
|
573
596
|
line: string,
|
|
574
597
|
state: SubagentState,
|
|
@@ -577,9 +600,17 @@ function processEventLine(
|
|
|
577
600
|
toolResultCompleted?: boolean;
|
|
578
601
|
}) => void,
|
|
579
602
|
requestTermination: (reason: string) => Promise<unknown>,
|
|
603
|
+
debugEventDiagnostics: boolean,
|
|
580
604
|
): void {
|
|
581
605
|
const parseResult = parseChildEventLine(line);
|
|
582
|
-
if (parseResult.kind !== "known")
|
|
606
|
+
if (parseResult.kind !== "known") {
|
|
607
|
+
if (debugEventDiagnostics) {
|
|
608
|
+
process.stderr.write(
|
|
609
|
+
`${formatUnknownEventDiagnostic(line, parseResult)}\n`,
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
583
614
|
const { event } = parseResult;
|
|
584
615
|
handleMessageEvent(event, state, emitUpdate);
|
|
585
616
|
handleToolExecutionUpdateEvent(event, emitUpdate);
|
|
@@ -639,28 +670,35 @@ function setupChildProcess(
|
|
|
639
670
|
toolResultCompleted?: boolean;
|
|
640
671
|
}) => void,
|
|
641
672
|
requestTermination: (reason: string) => Promise<unknown>,
|
|
673
|
+
debugEventDiagnostics: boolean,
|
|
642
674
|
): void {
|
|
643
675
|
proc.once("error", (error) => {
|
|
644
676
|
state.spawnError = error;
|
|
645
677
|
state.result.stderr = appendWithByteLimit(
|
|
646
678
|
state.result.stderr,
|
|
647
679
|
error.message,
|
|
648
|
-
|
|
680
|
+
state.runtimeLimits.maxStderrBytes,
|
|
649
681
|
);
|
|
650
682
|
});
|
|
651
683
|
if (proc.stdout) {
|
|
652
684
|
readline
|
|
653
685
|
.createInterface({ input: proc.stdout })
|
|
654
686
|
.on("line", (line) =>
|
|
655
|
-
processEventLine(
|
|
687
|
+
processEventLine(
|
|
688
|
+
line,
|
|
689
|
+
state,
|
|
690
|
+
emitUpdate,
|
|
691
|
+
requestTermination,
|
|
692
|
+
debugEventDiagnostics,
|
|
693
|
+
),
|
|
656
694
|
);
|
|
657
695
|
}
|
|
658
696
|
if (proc.stderr) {
|
|
659
|
-
proc.stderr.on("data", (data) => {
|
|
697
|
+
proc.stderr.on("data", (data: Buffer) => {
|
|
660
698
|
state.result.stderr = appendWithByteLimit(
|
|
661
699
|
state.result.stderr,
|
|
662
|
-
data
|
|
663
|
-
|
|
700
|
+
data,
|
|
701
|
+
state.runtimeLimits.maxStderrBytes,
|
|
664
702
|
);
|
|
665
703
|
});
|
|
666
704
|
}
|
|
@@ -689,8 +727,6 @@ async function finalizeResult(
|
|
|
689
727
|
}
|
|
690
728
|
|
|
691
729
|
/**
|
|
692
|
-
* Executes a single subagent task.
|
|
693
|
-
*
|
|
694
730
|
* Rationale: Subagents run in isolated child processes to protect the parent's
|
|
695
731
|
* context window and allow specialized system prompts/tools without polluting
|
|
696
732
|
* the main conversation.
|
|
@@ -716,12 +752,20 @@ export async function runSingleAgent(
|
|
|
716
752
|
) => SubagentDetails,
|
|
717
753
|
parentModel: { provider: string; id: string } | undefined,
|
|
718
754
|
parentThinking: ThinkingLevel,
|
|
755
|
+
debugEventDiagnostics = false,
|
|
719
756
|
): Promise<SingleResult> {
|
|
720
757
|
const agent = agents.find((a) => a.name === agentName);
|
|
721
758
|
if (!agent) return errorForUnknownAgent(agentName, agents, task);
|
|
759
|
+
const runtimeLimits = getSubagentRuntimeLimits();
|
|
722
760
|
const depth = getSubagentDepth();
|
|
723
|
-
if (depth >=
|
|
724
|
-
return errorForDepthLimit(
|
|
761
|
+
if (depth >= runtimeLimits.maxDepth) {
|
|
762
|
+
return errorForDepthLimit(
|
|
763
|
+
agentName,
|
|
764
|
+
agent.source,
|
|
765
|
+
task,
|
|
766
|
+
depth,
|
|
767
|
+
runtimeLimits.maxDepth,
|
|
768
|
+
);
|
|
725
769
|
}
|
|
726
770
|
const requestedThinking = agent.thinking ?? parentThinking;
|
|
727
771
|
const { level: thinking, warning: thinkingWarning } = parentModel
|
|
@@ -754,6 +798,7 @@ export async function runSingleAgent(
|
|
|
754
798
|
const startedAt = Date.now();
|
|
755
799
|
const state: SubagentState = {
|
|
756
800
|
result: initRuntimeResult(agentName, agent.source, task, modelDisplay),
|
|
801
|
+
runtimeLimits,
|
|
757
802
|
wasAborted: false,
|
|
758
803
|
};
|
|
759
804
|
if (thinkingWarning) state.result.thinkingWarning = thinkingWarning;
|
|
@@ -787,7 +832,13 @@ export async function runSingleAgent(
|
|
|
787
832
|
terminateOptions,
|
|
788
833
|
state,
|
|
789
834
|
);
|
|
790
|
-
setupChildProcess(
|
|
835
|
+
setupChildProcess(
|
|
836
|
+
proc,
|
|
837
|
+
state,
|
|
838
|
+
emitUpdate,
|
|
839
|
+
requestTermination,
|
|
840
|
+
debugEventDiagnostics,
|
|
841
|
+
);
|
|
791
842
|
const onAbort = setupAbortHandler(
|
|
792
843
|
signal,
|
|
793
844
|
state,
|
package/src/child/termination.ts
CHANGED
|
@@ -4,12 +4,12 @@ export type TerminationSignal = "SIGTERM" | "SIGKILL";
|
|
|
4
4
|
|
|
5
5
|
export type TerminationMetadata = {
|
|
6
6
|
cancelRequestedAt: number;
|
|
7
|
-
cancelReason?: string;
|
|
8
|
-
terminationSignal?: TerminationSignal;
|
|
7
|
+
cancelReason?: string | undefined;
|
|
8
|
+
terminationSignal?: TerminationSignal | undefined;
|
|
9
9
|
escalated: boolean;
|
|
10
10
|
processTreeKilled: boolean;
|
|
11
11
|
target: "direct" | "tree";
|
|
12
|
-
fallbackCause?: string;
|
|
12
|
+
fallbackCause?: string | undefined;
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
type TimerHandle = unknown;
|
|
@@ -64,7 +64,7 @@ function settleState(state: TerminationState): void {
|
|
|
64
64
|
if (state.settled) return;
|
|
65
65
|
state.settled = true;
|
|
66
66
|
if (state.timer) state.clearTimeout(state.timer);
|
|
67
|
-
state.timer
|
|
67
|
+
delete state.timer;
|
|
68
68
|
state.resolve(state.metadata);
|
|
69
69
|
}
|
|
70
70
|
|
package/src/env.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
declare namespace NodeJS {
|
|
2
|
+
interface ProcessEnv {
|
|
3
|
+
PATH?: string;
|
|
4
|
+
PI_CODING_AGENT_DIR?: string;
|
|
5
|
+
PI_SUBAGENT_DEPTH?: string;
|
|
6
|
+
PI_SUBAGENT_MAX_DEPTH?: string;
|
|
7
|
+
PI_SUBAGENT_MAX_OUTPUT_BYTES?: string;
|
|
8
|
+
PI_SUBAGENT_MAX_OUTPUT_LINES?: string;
|
|
9
|
+
PI_SUBAGENT_AGENT_END_GRACE_MS?: string;
|
|
10
|
+
PI_SUBAGENT_MAX_STDERR_BYTES?: string;
|
|
11
|
+
PI_SUBAGENT_DEBUG_ENABLED?: string;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -48,7 +48,6 @@ export function cancelAllRunJobs(reason = "Cancelled"): number {
|
|
|
48
48
|
return count;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
export function
|
|
51
|
+
export function resetRunRegistry(): void {
|
|
52
52
|
jobs.clear();
|
|
53
53
|
}
|
|
54
|
-
export const resetRunRegistry = clearRunJobsForTests;
|
|
@@ -77,6 +77,10 @@ type DetailsBuilder = (
|
|
|
77
77
|
options?: DetailsOptions,
|
|
78
78
|
) => SubagentDetails;
|
|
79
79
|
|
|
80
|
+
function isDebugDetailsAuthorized(debugRequested: boolean): boolean {
|
|
81
|
+
return debugRequested && process.env.PI_SUBAGENT_DEBUG_ENABLED === "1";
|
|
82
|
+
}
|
|
83
|
+
|
|
80
84
|
interface LifecycleContext {
|
|
81
85
|
pi: ExtensionAPI;
|
|
82
86
|
ctx: ExtensionContext;
|
|
@@ -90,7 +94,7 @@ interface LifecycleContext {
|
|
|
90
94
|
task: string;
|
|
91
95
|
parentModel: { provider: string; id: string } | undefined;
|
|
92
96
|
parentThinking: ThinkingLevel;
|
|
93
|
-
hostOnUpdate?: AgentToolUpdateCallback<SubagentDetails
|
|
97
|
+
hostOnUpdate?: AgentToolUpdateCallback<SubagentDetails> | undefined;
|
|
94
98
|
}
|
|
95
99
|
|
|
96
100
|
function createDetailsBuilder(
|
|
@@ -123,7 +127,7 @@ function sanitizeResultDetails(
|
|
|
123
127
|
usage: { ...usageBase },
|
|
124
128
|
};
|
|
125
129
|
if (contextWindowTokens !== undefined) {
|
|
126
|
-
(sanitized
|
|
130
|
+
(sanitized["usage"] as Record<string, unknown>)["contextWindowTokens"] =
|
|
127
131
|
contextWindowTokens;
|
|
128
132
|
}
|
|
129
133
|
if (progress !== undefined) {
|
|
@@ -134,7 +138,7 @@ function sanitizeResultDetails(
|
|
|
134
138
|
toolResultCompleted,
|
|
135
139
|
...progBase
|
|
136
140
|
} = progress;
|
|
137
|
-
sanitized
|
|
141
|
+
sanitized["progress"] = {
|
|
138
142
|
toolCalls: progBase.toolCalls.map((tc) => ({
|
|
139
143
|
id: tc.id,
|
|
140
144
|
preview: tc.preview,
|
|
@@ -146,7 +150,7 @@ function sanitizeResultDetails(
|
|
|
146
150
|
};
|
|
147
151
|
}
|
|
148
152
|
if (includeMessages) {
|
|
149
|
-
sanitized
|
|
153
|
+
sanitized["messages"] = options?.recentMessages
|
|
150
154
|
? [...options.recentMessages]
|
|
151
155
|
: messages !== undefined
|
|
152
156
|
? [...messages]
|
|
@@ -154,7 +158,7 @@ function sanitizeResultDetails(
|
|
|
154
158
|
if (includeDebugMessages && termination !== undefined) {
|
|
155
159
|
const { cancelReason, terminationSignal, fallbackCause, ...termBase } =
|
|
156
160
|
termination;
|
|
157
|
-
sanitized
|
|
161
|
+
sanitized["termination"] = {
|
|
158
162
|
...termBase,
|
|
159
163
|
...(cancelReason !== undefined && { cancelReason }),
|
|
160
164
|
...(terminationSignal !== undefined && { terminationSignal }),
|
|
@@ -318,6 +322,7 @@ async function runSubagentLifecycle(
|
|
|
318
322
|
lc.makeDetails,
|
|
319
323
|
lc.parentModel,
|
|
320
324
|
lc.parentThinking,
|
|
325
|
+
lc.debug,
|
|
321
326
|
);
|
|
322
327
|
return finishLifecycleResult(lc, result);
|
|
323
328
|
} catch (error) {
|
|
@@ -358,7 +363,7 @@ type PrepareSubagentJobResult =
|
|
|
358
363
|
lc: LifecycleContext;
|
|
359
364
|
instanceName: string;
|
|
360
365
|
requestProgressRender: () => void;
|
|
361
|
-
hostOnUpdate?: AgentToolUpdateCallback<SubagentDetails
|
|
366
|
+
hostOnUpdate?: AgentToolUpdateCallback<SubagentDetails> | undefined;
|
|
362
367
|
}
|
|
363
368
|
| { kind: "not_found"; makeDetails: DetailsBuilder }
|
|
364
369
|
| { kind: "cancelled"; makeDetails: DetailsBuilder }
|
|
@@ -409,7 +414,7 @@ async function prepareSubagentJob(
|
|
|
409
414
|
const agentScope: AgentScope = params.agentScope ?? "both";
|
|
410
415
|
const discovery = await getCachedAgentDiscovery(ctx.cwd, agentScope);
|
|
411
416
|
const agents = discovery.agents;
|
|
412
|
-
const debug = params.debug === true;
|
|
417
|
+
const debug = isDebugDetailsAuthorized(params.debug === true);
|
|
413
418
|
const makeDetails = createDetailsBuilder(
|
|
414
419
|
agentScope,
|
|
415
420
|
discovery.projectAgentsDir,
|
|
@@ -44,21 +44,21 @@ export const STATUS_BG: Record<ProgressStatus, ThemeBg> = {
|
|
|
44
44
|
export interface SubagentProgressState {
|
|
45
45
|
requestId: string;
|
|
46
46
|
agent: string;
|
|
47
|
-
instanceName?: string;
|
|
47
|
+
instanceName?: string | undefined;
|
|
48
48
|
taskPreview: string;
|
|
49
49
|
status: ProgressStatus;
|
|
50
50
|
startTime: number;
|
|
51
|
-
durationMs?: number;
|
|
52
|
-
activeToolActivity?: ToolActivity;
|
|
53
|
-
lastToolPreview?: string;
|
|
54
|
-
toolResultCompleted?: boolean;
|
|
51
|
+
durationMs?: number | undefined;
|
|
52
|
+
activeToolActivity?: ToolActivity | undefined;
|
|
53
|
+
lastToolPreview?: string | undefined;
|
|
54
|
+
toolResultCompleted?: boolean | undefined;
|
|
55
55
|
toolCount: number;
|
|
56
|
-
inputTokens?: number;
|
|
57
|
-
outputTokens?: number;
|
|
58
|
-
contextTokens?: number;
|
|
59
|
-
contextWindowTokens?: number;
|
|
60
|
-
finalOutput?: string;
|
|
61
|
-
errorText?: string;
|
|
56
|
+
inputTokens?: number | undefined;
|
|
57
|
+
outputTokens?: number | undefined;
|
|
58
|
+
contextTokens?: number | undefined;
|
|
59
|
+
contextWindowTokens?: number | undefined;
|
|
60
|
+
finalOutput?: string | undefined;
|
|
61
|
+
errorText?: string | undefined;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
const store = new Map<string, SubagentProgressState>();
|
|
@@ -89,6 +89,23 @@ export function getAllProgressStates(): SubagentProgressState[] {
|
|
|
89
89
|
return [...store.values()].sort((a, b) => b.startTime - a.startTime);
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
type ProgressTransientFields = Pick<
|
|
93
|
+
SubagentProgressState,
|
|
94
|
+
"activeToolActivity" | "lastToolPreview" | "toolResultCompleted"
|
|
95
|
+
>;
|
|
96
|
+
|
|
97
|
+
function stripTransientFields(
|
|
98
|
+
merged: SubagentProgressState,
|
|
99
|
+
): Omit<SubagentProgressState, keyof ProgressTransientFields> {
|
|
100
|
+
const {
|
|
101
|
+
activeToolActivity: _a,
|
|
102
|
+
lastToolPreview: _l,
|
|
103
|
+
toolResultCompleted: _t,
|
|
104
|
+
...base
|
|
105
|
+
} = merged;
|
|
106
|
+
return base;
|
|
107
|
+
}
|
|
108
|
+
|
|
92
109
|
export function patchProgressState(
|
|
93
110
|
requestId: string,
|
|
94
111
|
patch: Partial<SubagentProgressState>,
|
|
@@ -96,13 +113,7 @@ export function patchProgressState(
|
|
|
96
113
|
const state = store.get(requestId);
|
|
97
114
|
if (!state) return;
|
|
98
115
|
if (state.status !== "running") {
|
|
99
|
-
store.set(requestId, {
|
|
100
|
-
...state,
|
|
101
|
-
...patch,
|
|
102
|
-
activeToolActivity: undefined,
|
|
103
|
-
lastToolPreview: undefined,
|
|
104
|
-
toolResultCompleted: undefined,
|
|
105
|
-
});
|
|
116
|
+
store.set(requestId, stripTransientFields({ ...state, ...patch }));
|
|
106
117
|
return;
|
|
107
118
|
}
|
|
108
119
|
store.set(requestId, { ...state, ...patch });
|
|
@@ -115,7 +126,10 @@ function storeTerminalProgressState(
|
|
|
115
126
|
const state = store.get(requestId);
|
|
116
127
|
if (!state) return;
|
|
117
128
|
const durationMs = state.durationMs ?? Date.now() - state.startTime;
|
|
118
|
-
store.set(requestId, {
|
|
129
|
+
store.set(requestId, {
|
|
130
|
+
...stripTransientFields({ ...state, ...patch }),
|
|
131
|
+
durationMs,
|
|
132
|
+
});
|
|
119
133
|
}
|
|
120
134
|
|
|
121
135
|
export function finalizeProgressState(
|
|
@@ -125,9 +139,6 @@ export function finalizeProgressState(
|
|
|
125
139
|
storeTerminalProgressState(requestId, {
|
|
126
140
|
status: "success",
|
|
127
141
|
finalOutput: makeProgressFinalOutput(finalOutput),
|
|
128
|
-
activeToolActivity: undefined,
|
|
129
|
-
lastToolPreview: undefined,
|
|
130
|
-
toolResultCompleted: undefined,
|
|
131
142
|
});
|
|
132
143
|
}
|
|
133
144
|
|
|
@@ -136,21 +147,13 @@ export function failProgressState(requestId: string, errorText: string): void {
|
|
|
136
147
|
storeTerminalProgressState(requestId, {
|
|
137
148
|
status: "error",
|
|
138
149
|
errorText: sentence,
|
|
139
|
-
activeToolActivity: undefined,
|
|
140
|
-
lastToolPreview: undefined,
|
|
141
|
-
toolResultCompleted: undefined,
|
|
142
150
|
});
|
|
143
151
|
}
|
|
144
152
|
|
|
145
153
|
export function cancelProgressState(requestId: string, reason?: string): void {
|
|
146
154
|
storeTerminalProgressState(requestId, {
|
|
147
155
|
status: "cancelled",
|
|
148
|
-
|
|
149
|
-
lastToolPreview: undefined,
|
|
150
|
-
toolResultCompleted: undefined,
|
|
151
|
-
...(reason !== undefined
|
|
152
|
-
? { errorText: normalizeTerminalSentence(reason) }
|
|
153
|
-
: {}),
|
|
156
|
+
errorText: reason ? normalizeTerminalSentence(reason) : undefined,
|
|
154
157
|
});
|
|
155
158
|
}
|
|
156
159
|
|
|
@@ -235,11 +238,11 @@ function trackNewToolCall(
|
|
|
235
238
|
|
|
236
239
|
function extractProgressFromExistingProgress(
|
|
237
240
|
progress: {
|
|
238
|
-
activityText?: string;
|
|
239
|
-
activeToolActivity?: ToolActivity;
|
|
240
|
-
lastToolPreview?: string;
|
|
241
|
+
activityText?: string | undefined;
|
|
242
|
+
activeToolActivity?: ToolActivity | undefined;
|
|
243
|
+
lastToolPreview?: string | undefined;
|
|
241
244
|
toolCalls: { id: string; preview: string }[];
|
|
242
|
-
toolResultCompleted?: boolean;
|
|
245
|
+
toolResultCompleted?: boolean | undefined;
|
|
243
246
|
},
|
|
244
247
|
seenToolCallIds: Set<string>,
|
|
245
248
|
state: DetailsProgress,
|
|
@@ -317,7 +320,7 @@ function isDerivedToolCall(part: unknown): part is {
|
|
|
317
320
|
preview: string;
|
|
318
321
|
} {
|
|
319
322
|
if (!isObjectWith(part)) return false;
|
|
320
|
-
return typeof part
|
|
323
|
+
return typeof part["id"] === "string" && typeof part["preview"] === "string";
|
|
321
324
|
}
|
|
322
325
|
|
|
323
326
|
export function isToolCallPart(part: unknown): part is {
|
|
@@ -328,9 +331,9 @@ export function isToolCallPart(part: unknown): part is {
|
|
|
328
331
|
} {
|
|
329
332
|
if (!isObjectWith(part)) return false;
|
|
330
333
|
return (
|
|
331
|
-
part
|
|
332
|
-
typeof part
|
|
333
|
-
typeof part
|
|
334
|
+
part["type"] === "toolCall" &&
|
|
335
|
+
typeof part["id"] === "string" &&
|
|
336
|
+
typeof part["name"] === "string"
|
|
334
337
|
);
|
|
335
338
|
}
|
|
336
339
|
|
package/src/progress/progress.ts
CHANGED
|
@@ -89,11 +89,18 @@ export function renderSubagentProgress(
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
class DynamicSubagentProgressText implements Component {
|
|
92
|
+
private readonly requestId: string;
|
|
93
|
+
private readonly options: { expanded: boolean };
|
|
94
|
+
private readonly theme: SubagentTheme;
|
|
92
95
|
constructor(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
) {
|
|
96
|
+
requestId: string,
|
|
97
|
+
options: { expanded: boolean },
|
|
98
|
+
theme: SubagentTheme,
|
|
99
|
+
) {
|
|
100
|
+
this.requestId = requestId;
|
|
101
|
+
this.options = options;
|
|
102
|
+
this.theme = theme;
|
|
103
|
+
}
|
|
97
104
|
invalidate(): void {}
|
|
98
105
|
render(width: number): string[] {
|
|
99
106
|
const state = getProgressState(this.requestId);
|