@mystilleef/pi-subagent 0.6.0 → 0.8.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 +1 -1
- package/package.json +8 -9
- package/src/agent/agent-cache.ts +253 -8
- package/src/agent/agents.ts +88 -47
- package/src/child/child-events.ts +90 -3
- package/src/child/process.ts +191 -101
- package/src/index.ts +36 -17
- package/src/orchestration/subagent-orchestrator.ts +228 -99
- package/src/output/normalize.ts +35 -28
- package/src/output/ui.ts +25 -20
- package/src/progress/progress-state.ts +163 -51
- package/src/progress/progress.ts +19 -19
- package/src/progress/result-details.ts +46 -8
- package/src/shared/types.ts +10 -0
- package/src/shared/utils.ts +46 -7
|
@@ -3,12 +3,18 @@ import {
|
|
|
3
3
|
isStatusOnlyFailure,
|
|
4
4
|
isStatusOnlySuccess,
|
|
5
5
|
makeToolPreview,
|
|
6
|
+
normalizeAndTruncate,
|
|
6
7
|
normalizeSummaryValue,
|
|
7
8
|
normalizeTerminalSentence,
|
|
8
|
-
TOOL_PREVIEW_MAX_CHARS,
|
|
9
9
|
truncateText,
|
|
10
10
|
} from "../output/normalize.js";
|
|
11
|
-
import type {
|
|
11
|
+
import type {
|
|
12
|
+
SingleResult,
|
|
13
|
+
SubagentDetails,
|
|
14
|
+
ToolActivity,
|
|
15
|
+
} from "../shared/types.js";
|
|
16
|
+
|
|
17
|
+
export const SENSITIVE_PATTERN = /secret|token|password/i;
|
|
12
18
|
|
|
13
19
|
export type ThemeBg = "toolPendingBg" | "toolSuccessBg" | "toolErrorBg";
|
|
14
20
|
|
|
@@ -43,7 +49,9 @@ export interface SubagentProgressState {
|
|
|
43
49
|
status: ProgressStatus;
|
|
44
50
|
startTime: number;
|
|
45
51
|
durationMs?: number;
|
|
52
|
+
activeToolActivity?: ToolActivity;
|
|
46
53
|
lastToolPreview?: string;
|
|
54
|
+
toolResultCompleted?: boolean;
|
|
47
55
|
toolCount: number;
|
|
48
56
|
inputTokens?: number;
|
|
49
57
|
outputTokens?: number;
|
|
@@ -59,7 +67,7 @@ export function createProgressState(
|
|
|
59
67
|
requestId: string,
|
|
60
68
|
agent: string,
|
|
61
69
|
task: string,
|
|
62
|
-
instanceName
|
|
70
|
+
instanceName?: string,
|
|
63
71
|
): void {
|
|
64
72
|
store.set(requestId, {
|
|
65
73
|
requestId,
|
|
@@ -91,7 +99,9 @@ export function patchProgressState(
|
|
|
91
99
|
store.set(requestId, {
|
|
92
100
|
...state,
|
|
93
101
|
...patch,
|
|
102
|
+
activeToolActivity: undefined,
|
|
94
103
|
lastToolPreview: undefined,
|
|
104
|
+
toolResultCompleted: undefined,
|
|
95
105
|
});
|
|
96
106
|
return;
|
|
97
107
|
}
|
|
@@ -115,7 +125,9 @@ export function finalizeProgressState(
|
|
|
115
125
|
storeTerminalProgressState(requestId, {
|
|
116
126
|
status: "success",
|
|
117
127
|
finalOutput: makeProgressFinalOutput(finalOutput),
|
|
128
|
+
activeToolActivity: undefined,
|
|
118
129
|
lastToolPreview: undefined,
|
|
130
|
+
toolResultCompleted: undefined,
|
|
119
131
|
});
|
|
120
132
|
}
|
|
121
133
|
|
|
@@ -124,14 +136,18 @@ export function failProgressState(requestId: string, errorText: string): void {
|
|
|
124
136
|
storeTerminalProgressState(requestId, {
|
|
125
137
|
status: "error",
|
|
126
138
|
errorText: sentence,
|
|
139
|
+
activeToolActivity: undefined,
|
|
127
140
|
lastToolPreview: undefined,
|
|
141
|
+
toolResultCompleted: undefined,
|
|
128
142
|
});
|
|
129
143
|
}
|
|
130
144
|
|
|
131
145
|
export function cancelProgressState(requestId: string, reason?: string): void {
|
|
132
146
|
storeTerminalProgressState(requestId, {
|
|
133
147
|
status: "cancelled",
|
|
148
|
+
activeToolActivity: undefined,
|
|
134
149
|
lastToolPreview: undefined,
|
|
150
|
+
toolResultCompleted: undefined,
|
|
135
151
|
...(reason !== undefined
|
|
136
152
|
? { errorText: normalizeTerminalSentence(reason) }
|
|
137
153
|
: {}),
|
|
@@ -198,53 +214,110 @@ function isMeaningfulProgressErrorLine(line: string): boolean {
|
|
|
198
214
|
|
|
199
215
|
export interface DetailsProgress {
|
|
200
216
|
lastToolPreview?: string;
|
|
217
|
+
activityText?: string;
|
|
218
|
+
activeToolActivity?: ToolActivity;
|
|
219
|
+
progressLastToolPreview?: string;
|
|
220
|
+
toolResultCompleted?: boolean;
|
|
201
221
|
newToolCallIds: string[];
|
|
202
222
|
}
|
|
203
223
|
|
|
224
|
+
function trackNewToolCall(
|
|
225
|
+
id: string,
|
|
226
|
+
preview: string,
|
|
227
|
+
seenToolCallIds: Set<string>,
|
|
228
|
+
state: DetailsProgress,
|
|
229
|
+
): void {
|
|
230
|
+
if (seenToolCallIds.has(id)) return;
|
|
231
|
+
seenToolCallIds.add(id);
|
|
232
|
+
state.newToolCallIds.push(id);
|
|
233
|
+
state.lastToolPreview = preview;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function extractProgressFromExistingProgress(
|
|
237
|
+
progress: {
|
|
238
|
+
activityText?: string;
|
|
239
|
+
activeToolActivity?: ToolActivity;
|
|
240
|
+
lastToolPreview?: string;
|
|
241
|
+
toolCalls: { id: string; preview: string }[];
|
|
242
|
+
toolResultCompleted?: boolean;
|
|
243
|
+
},
|
|
244
|
+
seenToolCallIds: Set<string>,
|
|
245
|
+
state: DetailsProgress,
|
|
246
|
+
): void {
|
|
247
|
+
if (
|
|
248
|
+
typeof progress.activityText === "string" &&
|
|
249
|
+
progress.activityText.trim()
|
|
250
|
+
) {
|
|
251
|
+
state.activityText = normalizeAndTruncate(progress.activityText);
|
|
252
|
+
}
|
|
253
|
+
if (progress.activeToolActivity) {
|
|
254
|
+
state.activeToolActivity = progress.activeToolActivity;
|
|
255
|
+
}
|
|
256
|
+
if (
|
|
257
|
+
typeof progress.lastToolPreview === "string" &&
|
|
258
|
+
progress.lastToolPreview.trim()
|
|
259
|
+
) {
|
|
260
|
+
state.progressLastToolPreview = normalizeAndTruncate(
|
|
261
|
+
progress.lastToolPreview,
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
if (progress.toolResultCompleted) {
|
|
265
|
+
state.toolResultCompleted = true;
|
|
266
|
+
}
|
|
267
|
+
for (const toolCall of progress.toolCalls) {
|
|
268
|
+
if (!isDerivedToolCall(toolCall)) continue;
|
|
269
|
+
const preview = normalizeAndTruncate(toolCall.preview);
|
|
270
|
+
trackNewToolCall(toolCall.id, preview, seenToolCallIds, state);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function extractProgressFromMessages(
|
|
275
|
+
messages: SingleResult["messages"] = [],
|
|
276
|
+
seenToolCallIds: Set<string>,
|
|
277
|
+
state: DetailsProgress,
|
|
278
|
+
): void {
|
|
279
|
+
for (const msg of messages) {
|
|
280
|
+
if (msg.role !== "assistant" || !Array.isArray(msg.content)) continue;
|
|
281
|
+
for (const part of msg.content) {
|
|
282
|
+
if (isToolCallPart(part)) {
|
|
283
|
+
const preview = makeToolPreview(part.name, part.arguments);
|
|
284
|
+
trackNewToolCall(part.id, preview, seenToolCallIds, state);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
204
290
|
export function extractProgressFromDetails(
|
|
205
291
|
details: SubagentDetails,
|
|
206
292
|
seenToolCallIds: Set<string>,
|
|
207
293
|
): DetailsProgress {
|
|
208
|
-
const
|
|
209
|
-
let lastToolPreview: string | undefined;
|
|
294
|
+
const state: DetailsProgress = { newToolCallIds: [] };
|
|
210
295
|
const results = Array.isArray(details.results) ? details.results : [];
|
|
211
296
|
for (const result of results) {
|
|
212
297
|
if (result.progress) {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
);
|
|
219
|
-
if (seenToolCallIds.has(toolCall.id)) continue;
|
|
220
|
-
seenToolCallIds.add(toolCall.id);
|
|
221
|
-
newToolCallIds.push(toolCall.id);
|
|
222
|
-
}
|
|
298
|
+
extractProgressFromExistingProgress(
|
|
299
|
+
result.progress,
|
|
300
|
+
seenToolCallIds,
|
|
301
|
+
state,
|
|
302
|
+
);
|
|
223
303
|
continue;
|
|
224
304
|
}
|
|
225
305
|
const messages = Array.isArray(result.messages) ? result.messages : [];
|
|
226
|
-
|
|
227
|
-
if (msg.role !== "assistant" || !Array.isArray(msg.content)) continue;
|
|
228
|
-
for (const part of msg.content) {
|
|
229
|
-
if (isToolCallPart(part)) {
|
|
230
|
-
lastToolPreview = makeToolPreview(part.name, part.arguments);
|
|
231
|
-
if (seenToolCallIds.has(part.id)) continue;
|
|
232
|
-
seenToolCallIds.add(part.id);
|
|
233
|
-
newToolCallIds.push(part.id);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
}
|
|
306
|
+
extractProgressFromMessages(messages, seenToolCallIds, state);
|
|
237
307
|
}
|
|
238
|
-
return
|
|
308
|
+
return state;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function isObjectWith(part: unknown): part is Record<string, unknown> {
|
|
312
|
+
return typeof part === "object" && part !== null;
|
|
239
313
|
}
|
|
240
314
|
|
|
241
315
|
function isDerivedToolCall(part: unknown): part is {
|
|
242
316
|
id: string;
|
|
243
317
|
preview: string;
|
|
244
318
|
} {
|
|
245
|
-
if (
|
|
246
|
-
|
|
247
|
-
return typeof maybe.id === "string" && typeof maybe.preview === "string";
|
|
319
|
+
if (!isObjectWith(part)) return false;
|
|
320
|
+
return typeof part.id === "string" && typeof part.preview === "string";
|
|
248
321
|
}
|
|
249
322
|
|
|
250
323
|
export function isToolCallPart(part: unknown): part is {
|
|
@@ -253,12 +326,11 @@ export function isToolCallPart(part: unknown): part is {
|
|
|
253
326
|
name: string;
|
|
254
327
|
arguments?: Record<string, unknown>;
|
|
255
328
|
} {
|
|
256
|
-
if (
|
|
257
|
-
const maybe = part as { type?: unknown; id?: unknown; name?: unknown };
|
|
329
|
+
if (!isObjectWith(part)) return false;
|
|
258
330
|
return (
|
|
259
|
-
|
|
260
|
-
typeof
|
|
261
|
-
typeof
|
|
331
|
+
part.type === "toolCall" &&
|
|
332
|
+
typeof part.id === "string" &&
|
|
333
|
+
typeof part.name === "string"
|
|
262
334
|
);
|
|
263
335
|
}
|
|
264
336
|
|
|
@@ -274,22 +346,6 @@ export function formatElapsed(ms: number): string {
|
|
|
274
346
|
return `${mins}m ${secs}s`;
|
|
275
347
|
}
|
|
276
348
|
|
|
277
|
-
/**
|
|
278
|
-
* Format a raw token count for compact inline display.
|
|
279
|
-
* Values below 1000 rendered as-is. Larger counts use `k`
|
|
280
|
-
* or `M` suffixes with one decimal place, stripping trailing `.0`.
|
|
281
|
-
*/
|
|
282
|
-
export function formatTokenCount(count: number): string {
|
|
283
|
-
if (count < 1000) return String(count);
|
|
284
|
-
const unit = count >= 1_000_000 ? "M" : "k";
|
|
285
|
-
const divisor = count >= 1_000_000 ? 1_000_000 : 1000;
|
|
286
|
-
return `${trimTrailingZero((count / divisor).toFixed(1))}${unit}`;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
function trimTrailingZero(value: string): string {
|
|
290
|
-
return value.endsWith(".0") ? value.slice(0, -2) : value;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
349
|
export function formatContextPercent(state: SubagentProgressState): string {
|
|
294
350
|
const d = state.contextWindowTokens;
|
|
295
351
|
if (!d || d <= 0 || !Number.isFinite(d)) return "--%";
|
|
@@ -311,3 +367,59 @@ export function formatHeaderStats(state: SubagentProgressState): string {
|
|
|
311
367
|
const toolLabel = state.toolCount === 1 ? "tool" : "tools";
|
|
312
368
|
return `${state.toolCount} ${toolLabel} · ${formatContextPercent(state)} ctx · ${formatElapsed(elapsedMs)}\n`;
|
|
313
369
|
}
|
|
370
|
+
|
|
371
|
+
const REDACTED_PLACEHOLDER = "(running...)";
|
|
372
|
+
const REDACTED_PLACEHOLDER_LENGTH = REDACTED_PLACEHOLDER.length;
|
|
373
|
+
|
|
374
|
+
function redactOrTruncate(text: string, maxChars: number): string {
|
|
375
|
+
if (SENSITIVE_PATTERN.test(text))
|
|
376
|
+
return maxChars >= REDACTED_PLACEHOLDER_LENGTH ? REDACTED_PLACEHOLDER : "";
|
|
377
|
+
return truncateText(text, maxChars);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function walkActivityTree(activity: ToolActivity): string[] {
|
|
381
|
+
const parts: string[] = [];
|
|
382
|
+
let current: ToolActivity | undefined = activity;
|
|
383
|
+
while (current) {
|
|
384
|
+
if (current.inputSummary) {
|
|
385
|
+
const annotated = current.instanceName
|
|
386
|
+
? `${current.inputSummary} [${current.instanceName}]`
|
|
387
|
+
: current.inputSummary;
|
|
388
|
+
parts.push(annotated);
|
|
389
|
+
}
|
|
390
|
+
current = current.child;
|
|
391
|
+
}
|
|
392
|
+
return parts;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Renders a ToolActivity tree for storage. Each segment is
|
|
397
|
+
* independently normalized and truncated to TOOL_PREVIEW_MAX_CHARS (120).
|
|
398
|
+
*/
|
|
399
|
+
export function renderToolActivity(
|
|
400
|
+
activity: ToolActivity | undefined,
|
|
401
|
+
): string | undefined {
|
|
402
|
+
if (!activity) return undefined;
|
|
403
|
+
const parts = walkActivityTree(activity);
|
|
404
|
+
if (parts.length === 0) return activity.toolName;
|
|
405
|
+
const result = parts.map((p) => normalizeAndTruncate(p)).join(" - ");
|
|
406
|
+
if (SENSITIVE_PATTERN.test(result)) return REDACTED_PLACEHOLDER;
|
|
407
|
+
return result;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Renders a ToolActivity tree for display with a caller-provided truncation
|
|
412
|
+
* budget. Segments are normalized without individual truncation so the
|
|
413
|
+
* joined result shares one post-join display budget.
|
|
414
|
+
*/
|
|
415
|
+
export function renderToolActivityForDisplay(
|
|
416
|
+
activity: ToolActivity | undefined,
|
|
417
|
+
maxChars: number,
|
|
418
|
+
): string | undefined {
|
|
419
|
+
if (!activity) return undefined;
|
|
420
|
+
if (maxChars <= 0) return "";
|
|
421
|
+
const parts = walkActivityTree(activity);
|
|
422
|
+
if (parts.length === 0) return redactOrTruncate(activity.toolName, maxChars);
|
|
423
|
+
const joined = parts.map((p) => normalizeSummaryValue(p)).join(" - ");
|
|
424
|
+
return redactOrTruncate(joined, maxChars);
|
|
425
|
+
}
|
package/src/progress/progress.ts
CHANGED
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
formatHeaderStats,
|
|
24
24
|
getProgressState,
|
|
25
25
|
type ProgressStatus,
|
|
26
|
+
renderToolActivityForDisplay,
|
|
26
27
|
STATUS_BG,
|
|
27
28
|
STATUS_COLOR,
|
|
28
29
|
STATUS_ICON,
|
|
@@ -41,11 +42,12 @@ export {
|
|
|
41
42
|
formatContextPercent,
|
|
42
43
|
formatElapsed,
|
|
43
44
|
formatHeaderStats,
|
|
44
|
-
formatTokenCount,
|
|
45
45
|
getProgressState,
|
|
46
46
|
makeTaskPreview,
|
|
47
47
|
type ProgressStatus,
|
|
48
48
|
patchProgressState,
|
|
49
|
+
renderToolActivity,
|
|
50
|
+
renderToolActivityForDisplay,
|
|
49
51
|
resetProgressStore,
|
|
50
52
|
STATUS_COLOR,
|
|
51
53
|
STATUS_ICON,
|
|
@@ -96,7 +98,9 @@ class DynamicSubagentProgressText implements Component {
|
|
|
96
98
|
render(width: number): string[] {
|
|
97
99
|
const state = getProgressState(this.requestId);
|
|
98
100
|
if (!state) return [];
|
|
99
|
-
return renderProgressBox(state, this.options, this.theme).render(
|
|
101
|
+
return renderProgressBox(state, this.options, this.theme, width).render(
|
|
102
|
+
width,
|
|
103
|
+
);
|
|
100
104
|
}
|
|
101
105
|
}
|
|
102
106
|
|
|
@@ -108,6 +112,7 @@ function renderProgressBox(
|
|
|
108
112
|
state: SubagentProgressState,
|
|
109
113
|
options: { expanded: boolean },
|
|
110
114
|
theme: SubagentTheme,
|
|
115
|
+
width: number,
|
|
111
116
|
): Box {
|
|
112
117
|
const status = state.status;
|
|
113
118
|
const title = formatSubagentTitle(state.agent, state.instanceName, theme);
|
|
@@ -116,28 +121,19 @@ function renderProgressBox(
|
|
|
116
121
|
theme.bg(getProgressBackground(status), line),
|
|
117
122
|
);
|
|
118
123
|
box.addChild(new Text(header, 0, 0));
|
|
119
|
-
|
|
120
|
-
return box;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function addProgressBody(
|
|
124
|
-
box: Box,
|
|
125
|
-
state: SubagentProgressState,
|
|
126
|
-
options: { expanded: boolean },
|
|
127
|
-
theme: SubagentTheme,
|
|
128
|
-
): void {
|
|
129
|
-
const body = makeProgressBody(state, options, theme);
|
|
130
|
-
if (body.length === 0) return;
|
|
124
|
+
const body = makeProgressBody(state, options, theme, width);
|
|
131
125
|
for (const line of body) box.addChild(line);
|
|
126
|
+
return box;
|
|
132
127
|
}
|
|
133
128
|
|
|
134
129
|
function makeProgressBody(
|
|
135
130
|
state: SubagentProgressState,
|
|
136
131
|
options: { expanded: boolean },
|
|
137
132
|
theme: SubagentTheme,
|
|
133
|
+
width: number,
|
|
138
134
|
): Text[] {
|
|
139
135
|
if (state.status === "running")
|
|
140
|
-
return makeRunningProgressBody(state, options, theme);
|
|
136
|
+
return makeRunningProgressBody(state, options, theme, width);
|
|
141
137
|
if (state.status === "error" || state.status === "cancelled") {
|
|
142
138
|
return makeStoppedProgressBody(state, options, theme);
|
|
143
139
|
}
|
|
@@ -150,12 +146,16 @@ function makeRunningProgressBody(
|
|
|
150
146
|
state: SubagentProgressState,
|
|
151
147
|
options: { expanded: boolean },
|
|
152
148
|
theme: SubagentTheme,
|
|
149
|
+
width: number,
|
|
153
150
|
): Text[] {
|
|
154
151
|
const body: Text[] = [];
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
152
|
+
const activityBudget = Math.max(0, width - 8);
|
|
153
|
+
const activityPreview = renderToolActivityForDisplay(
|
|
154
|
+
state.activeToolActivity,
|
|
155
|
+
activityBudget,
|
|
156
|
+
);
|
|
157
|
+
if (activityPreview) {
|
|
158
|
+
body.push(new Text(formatRunningToolPreview(activityPreview, theme), 2, 0));
|
|
159
159
|
}
|
|
160
160
|
if (options.expanded)
|
|
161
161
|
body.push(new Text(theme.fg("dim", state.taskPreview), 2, 0));
|
|
@@ -7,12 +7,14 @@ import type {
|
|
|
7
7
|
SingleResult,
|
|
8
8
|
SubagentDetails,
|
|
9
9
|
SubagentToolResult,
|
|
10
|
+
ToolActivity,
|
|
10
11
|
} from "../shared/types.js";
|
|
11
12
|
import { detectMessageError } from "../shared/utils.js";
|
|
12
13
|
import {
|
|
13
14
|
extractProgressFromDetails,
|
|
14
15
|
getProgressState,
|
|
15
16
|
patchProgressState,
|
|
17
|
+
renderToolActivity,
|
|
16
18
|
} from "./progress.js";
|
|
17
19
|
|
|
18
20
|
export function hasSubagentFailed(result: SingleResult): boolean {
|
|
@@ -53,22 +55,56 @@ export function sanitizeDetailsForDisplay(
|
|
|
53
55
|
};
|
|
54
56
|
}
|
|
55
57
|
|
|
58
|
+
export function getLatestResult(
|
|
59
|
+
details: SubagentDetails,
|
|
60
|
+
): SingleResult | undefined {
|
|
61
|
+
return details.results[0];
|
|
62
|
+
}
|
|
63
|
+
|
|
56
64
|
export function patchProgressFromDetails(
|
|
57
65
|
requestId: string,
|
|
58
66
|
details: SubagentDetails,
|
|
59
67
|
seenToolCallIds: Set<string>,
|
|
60
68
|
): void {
|
|
61
|
-
const latestResult = details
|
|
62
|
-
const {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
69
|
+
const latestResult = getLatestResult(details);
|
|
70
|
+
const {
|
|
71
|
+
newToolCallIds,
|
|
72
|
+
lastToolPreview,
|
|
73
|
+
activityText,
|
|
74
|
+
activeToolActivity,
|
|
75
|
+
toolResultCompleted,
|
|
76
|
+
} = extractProgressFromDetails(details, seenToolCallIds);
|
|
66
77
|
const current = getProgressState(requestId);
|
|
67
78
|
if (!current) return;
|
|
68
79
|
const patch: Record<string, unknown> = {
|
|
69
80
|
toolCount: current.toolCount + newToolCallIds.length,
|
|
70
81
|
};
|
|
71
|
-
|
|
82
|
+
let nextActivity: ToolActivity | undefined;
|
|
83
|
+
if (newToolCallIds.length > 0 && lastToolPreview) {
|
|
84
|
+
nextActivity = { toolName: "tool", inputSummary: lastToolPreview };
|
|
85
|
+
} else if (activeToolActivity) {
|
|
86
|
+
nextActivity = activeToolActivity;
|
|
87
|
+
} else if (activityText) {
|
|
88
|
+
nextActivity = { toolName: "tool", inputSummary: activityText };
|
|
89
|
+
} else if (current.activeToolActivity) {
|
|
90
|
+
nextActivity = current.activeToolActivity;
|
|
91
|
+
}
|
|
92
|
+
if (toolResultCompleted && nextActivity?.child) {
|
|
93
|
+
nextActivity = { ...nextActivity, child: undefined };
|
|
94
|
+
} else if (toolResultCompleted) {
|
|
95
|
+
nextActivity = undefined;
|
|
96
|
+
}
|
|
97
|
+
patch.activeToolActivity = nextActivity;
|
|
98
|
+
const renderedPreview = renderToolActivity(nextActivity);
|
|
99
|
+
if (renderedPreview) {
|
|
100
|
+
patch.lastToolPreview = renderedPreview;
|
|
101
|
+
} else if (toolResultCompleted && !nextActivity) {
|
|
102
|
+
patch.lastToolPreview = undefined;
|
|
103
|
+
}
|
|
104
|
+
if (toolResultCompleted) {
|
|
105
|
+
patch.toolResultCompleted = true;
|
|
106
|
+
}
|
|
107
|
+
// Token accounting always applies when usage data is available
|
|
72
108
|
if (latestResult?.usage) {
|
|
73
109
|
patch.inputTokens = latestResult.usage.input;
|
|
74
110
|
patch.outputTokens = latestResult.usage.output;
|
|
@@ -86,11 +122,13 @@ export function getSubagentText(result: SubagentToolResult): string {
|
|
|
86
122
|
}
|
|
87
123
|
|
|
88
124
|
export function getResultDisplayText(result: SubagentToolResult): string {
|
|
89
|
-
return
|
|
125
|
+
return (
|
|
126
|
+
getLatestResult(result.details)?.finalOutput ?? getSubagentText(result)
|
|
127
|
+
);
|
|
90
128
|
}
|
|
91
129
|
|
|
92
130
|
export function getFeedbackSummaryText(result: SubagentToolResult): string {
|
|
93
|
-
const rawFinalOutput = result.details
|
|
131
|
+
const rawFinalOutput = getLatestResult(result.details)?.finalOutput;
|
|
94
132
|
if (rawFinalOutput?.trim())
|
|
95
133
|
return summarizeFeedbackUiFinalOutput(rawFinalOutput);
|
|
96
134
|
return getSubagentText(result).trim() || "(no output)";
|
package/src/shared/types.ts
CHANGED
|
@@ -13,6 +13,13 @@ export interface UsageStats {
|
|
|
13
13
|
turns: number;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
export interface ToolActivity {
|
|
17
|
+
toolName: string;
|
|
18
|
+
inputSummary?: string;
|
|
19
|
+
instanceName?: string;
|
|
20
|
+
child?: ToolActivity;
|
|
21
|
+
}
|
|
22
|
+
|
|
16
23
|
export interface StreamingProgressToolCall {
|
|
17
24
|
id: string;
|
|
18
25
|
preview: string;
|
|
@@ -20,8 +27,10 @@ export interface StreamingProgressToolCall {
|
|
|
20
27
|
|
|
21
28
|
export interface StreamingProgress {
|
|
22
29
|
activityText?: string;
|
|
30
|
+
activeToolActivity?: ToolActivity;
|
|
23
31
|
toolCalls: StreamingProgressToolCall[];
|
|
24
32
|
lastToolPreview?: string;
|
|
33
|
+
toolResultCompleted?: boolean;
|
|
25
34
|
}
|
|
26
35
|
|
|
27
36
|
export interface SingleResult {
|
|
@@ -48,6 +57,7 @@ export interface SubagentDetails {
|
|
|
48
57
|
agentScope: AgentScope;
|
|
49
58
|
projectAgentsDir: string | null;
|
|
50
59
|
results: SingleResult[];
|
|
60
|
+
renderedByMessage?: true;
|
|
51
61
|
}
|
|
52
62
|
|
|
53
63
|
export interface SubagentToolResult {
|
package/src/shared/utils.ts
CHANGED
|
@@ -89,15 +89,54 @@ export function getPiInvocation(args: string[]): {
|
|
|
89
89
|
return { command: "pi", args };
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
const SKILL_DISCOVERY_CACHE_TTL_MS = 300_000;
|
|
93
|
+
|
|
94
|
+
type ResolvedSkillArgsCacheEntry = {
|
|
95
|
+
skillPaths: Map<string, string>;
|
|
96
|
+
ts: number;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const resolvedSkillArgsCache = new Map<string, ResolvedSkillArgsCacheEntry>();
|
|
100
|
+
|
|
101
|
+
async function canonicalPath(filePath: string): Promise<string> {
|
|
102
|
+
try {
|
|
103
|
+
return await fs.promises.realpath(filePath);
|
|
104
|
+
} catch {
|
|
105
|
+
return path.resolve(filePath);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function buildSkillArgs(
|
|
110
|
+
requested: string[],
|
|
111
|
+
skillPaths: Map<string, string>,
|
|
112
|
+
): string[] {
|
|
113
|
+
return requested.flatMap((name) => ["--skill", skillPaths.get(name) ?? name]);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function resetResolvedAgentSkillArgsCache(): void {
|
|
117
|
+
resolvedSkillArgsCache.clear();
|
|
118
|
+
}
|
|
119
|
+
|
|
92
120
|
export async function resolveAgentSkillArgs(
|
|
93
121
|
cwd: string,
|
|
94
122
|
skillNames: string[],
|
|
95
123
|
): Promise<{ args: string[] } | { error: string }> {
|
|
96
124
|
const requested = Array.from(new Set(skillNames));
|
|
97
125
|
if (requested.length === 0) return { args: [] };
|
|
126
|
+
const cacheIdentitySkills = [...requested].sort();
|
|
127
|
+
const agentDir = getAgentDir();
|
|
128
|
+
const cacheKey = JSON.stringify({
|
|
129
|
+
cwd: await canonicalPath(cwd),
|
|
130
|
+
agentDir: await canonicalPath(agentDir),
|
|
131
|
+
skills: cacheIdentitySkills,
|
|
132
|
+
});
|
|
133
|
+
const cached = resolvedSkillArgsCache.get(cacheKey);
|
|
134
|
+
if (cached && Date.now() - cached.ts <= SKILL_DISCOVERY_CACHE_TTL_MS) {
|
|
135
|
+
return { args: buildSkillArgs(requested, cached.skillPaths) };
|
|
136
|
+
}
|
|
98
137
|
const loader = new DefaultResourceLoader({
|
|
99
138
|
cwd,
|
|
100
|
-
agentDir
|
|
139
|
+
agentDir,
|
|
101
140
|
noContextFiles: true,
|
|
102
141
|
noPromptTemplates: true,
|
|
103
142
|
noThemes: true,
|
|
@@ -124,12 +163,12 @@ export async function resolveAgentSkillArgs(
|
|
|
124
163
|
.join(", ")}. Available skills: ${available}.`,
|
|
125
164
|
};
|
|
126
165
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
};
|
|
166
|
+
const skillPaths = new Map(
|
|
167
|
+
requested.map((name) => [name, skillMap.get(name)?.filePath ?? name]),
|
|
168
|
+
);
|
|
169
|
+
const args = buildSkillArgs(requested, skillPaths);
|
|
170
|
+
resolvedSkillArgsCache.set(cacheKey, { skillPaths, ts: Date.now() });
|
|
171
|
+
return { args };
|
|
133
172
|
}
|
|
134
173
|
|
|
135
174
|
export function getSubagentDepth(): number {
|