@mystilleef/pi-subagent 0.6.0 → 0.7.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 +1 -1
- package/src/agent/agent-cache.ts +225 -8
- package/src/agent/agents.ts +83 -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
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AgentToolUpdateCallback } from "@earendil-works/pi-agent-core";
|
|
1
2
|
import { type Message, StringEnum } from "@earendil-works/pi-ai";
|
|
2
3
|
import type {
|
|
3
4
|
ExtensionAPI,
|
|
@@ -10,7 +11,7 @@ import type {
|
|
|
10
11
|
AgentScope,
|
|
11
12
|
ThinkingLevel,
|
|
12
13
|
} from "../agent/agents.js";
|
|
13
|
-
import { runSingleAgent } from "../child/process.js";
|
|
14
|
+
import { runSingleAgent, SubagentAbortError } from "../child/process.js";
|
|
14
15
|
import { formatSubagentResultForParent } from "../output/summary.js";
|
|
15
16
|
import {
|
|
16
17
|
cancelProgressState,
|
|
@@ -22,6 +23,7 @@ import {
|
|
|
22
23
|
import {
|
|
23
24
|
createSubagentError,
|
|
24
25
|
getFeedbackSummaryText,
|
|
26
|
+
getLatestResult,
|
|
25
27
|
getResultDisplayText,
|
|
26
28
|
hasSubagentFailed,
|
|
27
29
|
patchProgressFromDetails,
|
|
@@ -34,6 +36,7 @@ import type {
|
|
|
34
36
|
SubagentDetails,
|
|
35
37
|
SubagentToolResult,
|
|
36
38
|
} from "../shared/types.js";
|
|
39
|
+
import { getSubagentDepth } from "../shared/utils.js";
|
|
37
40
|
import {
|
|
38
41
|
listRunJobs,
|
|
39
42
|
type RunJob,
|
|
@@ -74,6 +77,22 @@ type DetailsBuilder = (
|
|
|
74
77
|
options?: DetailsOptions,
|
|
75
78
|
) => SubagentDetails;
|
|
76
79
|
|
|
80
|
+
interface LifecycleContext {
|
|
81
|
+
pi: ExtensionAPI;
|
|
82
|
+
ctx: ExtensionContext;
|
|
83
|
+
requestId: string;
|
|
84
|
+
job: RunJob;
|
|
85
|
+
debug: boolean;
|
|
86
|
+
makeDetails: DetailsBuilder;
|
|
87
|
+
mergedSignal: AbortSignal;
|
|
88
|
+
agents: AgentConfig[];
|
|
89
|
+
agentName: string;
|
|
90
|
+
task: string;
|
|
91
|
+
parentModel: { provider: string; id: string } | undefined;
|
|
92
|
+
parentThinking: ThinkingLevel;
|
|
93
|
+
hostOnUpdate?: AgentToolUpdateCallback<SubagentDetails>;
|
|
94
|
+
}
|
|
95
|
+
|
|
77
96
|
function createDetailsBuilder(
|
|
78
97
|
agentScope: AgentScope,
|
|
79
98
|
projectAgentsDir: string | null,
|
|
@@ -108,14 +127,22 @@ function sanitizeResultDetails(
|
|
|
108
127
|
contextWindowTokens;
|
|
109
128
|
}
|
|
110
129
|
if (progress !== undefined) {
|
|
111
|
-
const {
|
|
130
|
+
const {
|
|
131
|
+
activityText,
|
|
132
|
+
activeToolActivity,
|
|
133
|
+
lastToolPreview,
|
|
134
|
+
toolResultCompleted,
|
|
135
|
+
...progBase
|
|
136
|
+
} = progress;
|
|
112
137
|
sanitized.progress = {
|
|
113
138
|
toolCalls: progBase.toolCalls.map((tc) => ({
|
|
114
139
|
id: tc.id,
|
|
115
140
|
preview: tc.preview,
|
|
116
141
|
})),
|
|
117
142
|
...(activityText !== undefined && { activityText }),
|
|
143
|
+
...(activeToolActivity !== undefined && { activeToolActivity }),
|
|
118
144
|
...(lastToolPreview !== undefined && { lastToolPreview }),
|
|
145
|
+
...(toolResultCompleted !== undefined && { toolResultCompleted }),
|
|
119
146
|
};
|
|
120
147
|
}
|
|
121
148
|
if (includeMessages) {
|
|
@@ -177,93 +204,136 @@ export function emitCompletionAlert(
|
|
|
177
204
|
process.stdout.write("\x07");
|
|
178
205
|
}
|
|
179
206
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
207
|
+
function createCompletedToolResult(
|
|
208
|
+
content: string,
|
|
209
|
+
details: SubagentDetails,
|
|
210
|
+
): SubagentToolResult {
|
|
211
|
+
return {
|
|
212
|
+
content: [{ type: "text", text: content }],
|
|
213
|
+
details: { ...details, renderedByMessage: true },
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function finishLifecycleFailure(
|
|
218
|
+
lc: LifecycleContext,
|
|
219
|
+
errorMessage: string,
|
|
220
|
+
details: SubagentDetails,
|
|
221
|
+
): SubagentToolResult {
|
|
222
|
+
const displayDetails = sanitizeDetailsForDisplay(details, lc.debug);
|
|
223
|
+
if (lc.mergedSignal.aborted) {
|
|
224
|
+
cancelProgressState(lc.requestId, lc.job.cancelReason ?? errorMessage);
|
|
225
|
+
if (getSubagentDepth() > 0) {
|
|
226
|
+
sendSubagentResultMessage(lc.pi, "Canceled", displayDetails);
|
|
227
|
+
}
|
|
228
|
+
return createCompletedToolResult("Canceled", displayDetails);
|
|
229
|
+
}
|
|
230
|
+
failProgressState(lc.requestId, errorMessage);
|
|
231
|
+
lc.ctx.ui?.notify(errorMessage, "error");
|
|
232
|
+
const latestResult = getLatestResult(details);
|
|
233
|
+
const content = latestResult
|
|
234
|
+
? formatSubagentResultForParent(latestResult) || "(failed)"
|
|
235
|
+
: errorMessage;
|
|
236
|
+
sendSubagentResultMessage(lc.pi, content, displayDetails);
|
|
237
|
+
return createCompletedToolResult(content, displayDetails);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function finishLifecycleResult(
|
|
241
|
+
lc: LifecycleContext,
|
|
242
|
+
result: SingleResult,
|
|
243
|
+
): SubagentToolResult {
|
|
244
|
+
const details = lc.makeDetails([result]);
|
|
245
|
+
if (hasSubagentFailed(result)) {
|
|
246
|
+
return finishLifecycleFailure(
|
|
247
|
+
lc,
|
|
248
|
+
lc.mergedSignal.aborted ? "Aborted" : createSubagentError(result).message,
|
|
249
|
+
details,
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
const displayDetails = sanitizeDetailsForDisplay(details, lc.debug);
|
|
253
|
+
const content = formatSubagentResultForParent(result) || "(no output)";
|
|
254
|
+
const toolResult = createCompletedToolResult(content, displayDetails);
|
|
255
|
+
finalizeProgressState(lc.requestId, getFeedbackSummaryText(toolResult));
|
|
256
|
+
sendSubagentResultMessage(
|
|
257
|
+
lc.pi,
|
|
258
|
+
getResultDisplayText(toolResult),
|
|
259
|
+
displayDetails,
|
|
260
|
+
);
|
|
261
|
+
return toolResult;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function createPayloadFingerprint(payload: {
|
|
265
|
+
content: { type: string; text?: string }[];
|
|
266
|
+
details: SubagentDetails;
|
|
267
|
+
}): string {
|
|
268
|
+
const contentText = payload.content[0]?.text ?? "";
|
|
269
|
+
const latestResult = payload.details.results[0];
|
|
270
|
+
const activityText = latestResult?.progress?.activityText ?? "";
|
|
271
|
+
const toolCallIds =
|
|
272
|
+
[...new Set(latestResult?.progress?.toolCalls?.map((tc) => tc.id))]
|
|
273
|
+
.sort()
|
|
274
|
+
.join(",") ?? "";
|
|
275
|
+
const exitCode = latestResult?.exitCode ?? 0;
|
|
276
|
+
const stopReason = latestResult?.stopReason ?? "";
|
|
277
|
+
return `${contentText}|${activityText}|${toolCallIds}|${exitCode}|${stopReason}`;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
async function runSubagentLifecycle(
|
|
281
|
+
lc: LifecycleContext,
|
|
282
|
+
): Promise<SubagentToolResult> {
|
|
194
283
|
const seenToolCallIds = new Set<string>();
|
|
195
|
-
const requestProgressRender = createProgressRenderRequester(
|
|
284
|
+
const requestProgressRender = createProgressRenderRequester(
|
|
285
|
+
lc.ctx,
|
|
286
|
+
lc.requestId,
|
|
287
|
+
);
|
|
288
|
+
let lastDeliveredFingerprint: string | undefined;
|
|
196
289
|
const onUpdate: OnUpdateCallback = (result) => {
|
|
197
|
-
patchProgressFromDetails(requestId, result.details, seenToolCallIds);
|
|
290
|
+
patchProgressFromDetails(lc.requestId, result.details, seenToolCallIds);
|
|
198
291
|
requestProgressRender();
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
details: SubagentDetails,
|
|
204
|
-
) {
|
|
205
|
-
if (isAborted) {
|
|
206
|
-
cancelProgressState(requestId, job.cancelReason ?? errorMessage);
|
|
207
|
-
} else {
|
|
208
|
-
failProgressState(requestId, errorMessage);
|
|
209
|
-
ctx.ui?.notify(errorMessage, "error");
|
|
210
|
-
const content = details.results[0]
|
|
211
|
-
? formatSubagentResultForParent(details.results[0] as SingleResult) ||
|
|
212
|
-
"(failed)"
|
|
213
|
-
: errorMessage;
|
|
214
|
-
sendSubagentResultMessage(
|
|
215
|
-
pi,
|
|
216
|
-
content,
|
|
217
|
-
sanitizeDetailsForDisplay(details, debug),
|
|
292
|
+
if (lc.hostOnUpdate) {
|
|
293
|
+
const sanitizedDetails = sanitizeDetailsForDisplay(
|
|
294
|
+
result.details,
|
|
295
|
+
lc.debug,
|
|
218
296
|
);
|
|
297
|
+
const { renderedByMessage, ...partialDetails } = sanitizedDetails;
|
|
298
|
+
const payload = {
|
|
299
|
+
content: result.content,
|
|
300
|
+
details: partialDetails,
|
|
301
|
+
};
|
|
302
|
+
const fingerprint = createPayloadFingerprint(payload);
|
|
303
|
+
if (fingerprint !== lastDeliveredFingerprint) {
|
|
304
|
+
lastDeliveredFingerprint = fingerprint;
|
|
305
|
+
lc.hostOnUpdate(payload);
|
|
306
|
+
}
|
|
219
307
|
}
|
|
220
|
-
}
|
|
308
|
+
};
|
|
309
|
+
const timerTick = setInterval(requestProgressRender, 500);
|
|
221
310
|
try {
|
|
222
311
|
const result = await runSingleAgent(
|
|
223
|
-
ctx.cwd,
|
|
224
|
-
agents,
|
|
225
|
-
agentName,
|
|
226
|
-
task,
|
|
227
|
-
mergedSignal,
|
|
312
|
+
lc.ctx.cwd,
|
|
313
|
+
lc.agents,
|
|
314
|
+
lc.agentName,
|
|
315
|
+
lc.task,
|
|
316
|
+
lc.mergedSignal,
|
|
228
317
|
onUpdate,
|
|
229
|
-
makeDetails,
|
|
230
|
-
parentModel,
|
|
231
|
-
parentThinking,
|
|
318
|
+
lc.makeDetails,
|
|
319
|
+
lc.parentModel,
|
|
320
|
+
lc.parentThinking,
|
|
232
321
|
);
|
|
233
|
-
|
|
234
|
-
handleWorkerFailure(
|
|
235
|
-
mergedSignal.aborted ? "Aborted" : createSubagentError(result).message,
|
|
236
|
-
mergedSignal.aborted,
|
|
237
|
-
makeDetails([result]),
|
|
238
|
-
);
|
|
239
|
-
} else {
|
|
240
|
-
const toolResult: SubagentToolResult = {
|
|
241
|
-
content: [
|
|
242
|
-
{
|
|
243
|
-
type: "text" as const,
|
|
244
|
-
text: formatSubagentResultForParent(result) || "(no output)",
|
|
245
|
-
},
|
|
246
|
-
],
|
|
247
|
-
details: makeDetails([result]),
|
|
248
|
-
};
|
|
249
|
-
finalizeProgressState(requestId, getFeedbackSummaryText(toolResult));
|
|
250
|
-
sendSubagentResultMessage(
|
|
251
|
-
pi,
|
|
252
|
-
getResultDisplayText(toolResult),
|
|
253
|
-
sanitizeDetailsForDisplay(toolResult.details, debug),
|
|
254
|
-
);
|
|
255
|
-
}
|
|
322
|
+
return finishLifecycleResult(lc, result);
|
|
256
323
|
} catch (error) {
|
|
257
|
-
|
|
324
|
+
const abortResult =
|
|
325
|
+
error instanceof SubagentAbortError ? error.result : undefined;
|
|
326
|
+
return finishLifecycleFailure(
|
|
327
|
+
lc,
|
|
258
328
|
error instanceof Error ? error.message : String(error),
|
|
259
|
-
|
|
260
|
-
makeDetails([]),
|
|
329
|
+
abortResult ? lc.makeDetails([abortResult]) : lc.makeDetails([]),
|
|
261
330
|
);
|
|
262
331
|
} finally {
|
|
332
|
+
clearInterval(timerTick);
|
|
263
333
|
requestProgressRender();
|
|
264
|
-
removeRunJob(requestId);
|
|
334
|
+
removeRunJob(lc.requestId);
|
|
265
335
|
if (listRunJobs().length === 0) {
|
|
266
|
-
const state = getProgressState(requestId);
|
|
336
|
+
const state = getProgressState(lc.requestId);
|
|
267
337
|
if (state) {
|
|
268
338
|
emitCompletionAlert(state);
|
|
269
339
|
}
|
|
@@ -271,23 +341,43 @@ async function runSubagentWorker(
|
|
|
271
341
|
}
|
|
272
342
|
}
|
|
273
343
|
|
|
274
|
-
|
|
344
|
+
type StartJobResult =
|
|
275
345
|
| {
|
|
276
346
|
kind: "started";
|
|
277
347
|
requestId: string;
|
|
278
348
|
instanceName: string;
|
|
279
349
|
makeDetails: DetailsBuilder;
|
|
280
350
|
}
|
|
351
|
+
| { kind: "completed"; result: SubagentToolResult }
|
|
281
352
|
| { kind: "cancelled"; makeDetails: DetailsBuilder }
|
|
282
353
|
| { kind: "not_found"; makeDetails: DetailsBuilder };
|
|
283
354
|
|
|
284
|
-
|
|
355
|
+
type PrepareSubagentJobResult =
|
|
356
|
+
| {
|
|
357
|
+
kind: "ready";
|
|
358
|
+
lc: LifecycleContext;
|
|
359
|
+
instanceName: string;
|
|
360
|
+
requestProgressRender: () => void;
|
|
361
|
+
hostOnUpdate?: AgentToolUpdateCallback<SubagentDetails>;
|
|
362
|
+
}
|
|
363
|
+
| { kind: "not_found"; makeDetails: DetailsBuilder }
|
|
364
|
+
| { kind: "cancelled"; makeDetails: DetailsBuilder }
|
|
365
|
+
| { kind: "aborted"; makeDetails: DetailsBuilder };
|
|
366
|
+
|
|
367
|
+
export function formatSubagentToolResult(
|
|
285
368
|
agentName: string,
|
|
286
369
|
result: StartJobResult,
|
|
287
|
-
):
|
|
288
|
-
if (result.kind === "
|
|
289
|
-
|
|
290
|
-
|
|
370
|
+
): SubagentToolResult {
|
|
371
|
+
if (result.kind === "completed") return result.result;
|
|
372
|
+
let text: string;
|
|
373
|
+
if (result.kind === "not_found") text = `Unknown agent: "${agentName}"`;
|
|
374
|
+
else if (result.kind === "cancelled") text = "Canceled";
|
|
375
|
+
else
|
|
376
|
+
text = `Subagent ${agentName} ${result.instanceName} started (job: ${result.requestId})`;
|
|
377
|
+
return {
|
|
378
|
+
content: [{ type: "text", text }],
|
|
379
|
+
details: result.makeDetails([]),
|
|
380
|
+
};
|
|
291
381
|
}
|
|
292
382
|
|
|
293
383
|
function needsProjectAgentConfirmation(
|
|
@@ -309,12 +399,13 @@ function confirmProjectAgentRun(
|
|
|
309
399
|
);
|
|
310
400
|
}
|
|
311
401
|
|
|
312
|
-
|
|
402
|
+
async function prepareSubagentJob(
|
|
313
403
|
pi: ExtensionAPI,
|
|
314
404
|
ctx: ExtensionContext,
|
|
315
405
|
params: Static<typeof SubagentParams>,
|
|
316
406
|
hostSignal: AbortSignal | undefined,
|
|
317
|
-
|
|
407
|
+
hostOnUpdate?: AgentToolUpdateCallback<SubagentDetails>,
|
|
408
|
+
): Promise<PrepareSubagentJobResult> {
|
|
318
409
|
const agentScope: AgentScope = params.agentScope ?? "both";
|
|
319
410
|
const discovery = await getCachedAgentDiscovery(ctx.cwd, agentScope);
|
|
320
411
|
const agents = discovery.agents;
|
|
@@ -380,32 +471,70 @@ export async function startSubagentJob(
|
|
|
380
471
|
details: { agent: params.agent, instanceName, requestId },
|
|
381
472
|
});
|
|
382
473
|
const requestProgressRender = createProgressRenderRequester(ctx, requestId);
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
474
|
+
if (mergedSignal.aborted) {
|
|
475
|
+
cancelStartedJob(job, job.cancelReason ?? "Aborted");
|
|
476
|
+
requestProgressRender();
|
|
477
|
+
return { kind: "aborted", makeDetails: makeStartedDetails };
|
|
478
|
+
}
|
|
479
|
+
return {
|
|
480
|
+
kind: "ready",
|
|
481
|
+
lc: {
|
|
390
482
|
pi,
|
|
391
483
|
ctx,
|
|
484
|
+
requestId,
|
|
485
|
+
job,
|
|
486
|
+
debug,
|
|
487
|
+
makeDetails: makeStartedDetails,
|
|
488
|
+
mergedSignal,
|
|
392
489
|
agents,
|
|
393
|
-
params.agent,
|
|
490
|
+
agentName: params.agent,
|
|
394
491
|
task,
|
|
395
|
-
debug,
|
|
396
492
|
parentModel,
|
|
397
493
|
parentThinking,
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
494
|
+
hostOnUpdate,
|
|
495
|
+
},
|
|
496
|
+
instanceName,
|
|
497
|
+
requestProgressRender,
|
|
498
|
+
hostOnUpdate,
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
export async function startSubagentJob(
|
|
503
|
+
pi: ExtensionAPI,
|
|
504
|
+
ctx: ExtensionContext,
|
|
505
|
+
params: Static<typeof SubagentParams>,
|
|
506
|
+
hostSignal: AbortSignal | undefined,
|
|
507
|
+
hostOnUpdate?: AgentToolUpdateCallback<SubagentDetails>,
|
|
508
|
+
): Promise<StartJobResult> {
|
|
509
|
+
const prepared = await prepareSubagentJob(
|
|
510
|
+
pi,
|
|
511
|
+
ctx,
|
|
512
|
+
params,
|
|
513
|
+
hostSignal,
|
|
514
|
+
hostOnUpdate,
|
|
515
|
+
);
|
|
516
|
+
if (prepared.kind !== "ready") {
|
|
517
|
+
if (prepared.kind === "aborted")
|
|
518
|
+
return { kind: "cancelled", makeDetails: prepared.makeDetails };
|
|
519
|
+
return prepared;
|
|
520
|
+
}
|
|
521
|
+
const { lc, instanceName, requestProgressRender } = prepared;
|
|
522
|
+
if (getSubagentDepth() > 0) {
|
|
523
|
+
const result = await runSubagentLifecycle(lc);
|
|
524
|
+
return { kind: "completed", result };
|
|
525
|
+
}
|
|
526
|
+
setImmediate(() => {
|
|
527
|
+
if (lc.mergedSignal.aborted) {
|
|
528
|
+
cancelStartedJob(lc.job, lc.job.cancelReason ?? "Aborted");
|
|
529
|
+
requestProgressRender();
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
runSubagentLifecycle(lc);
|
|
403
533
|
});
|
|
404
|
-
if (mergedSignal.aborted) return { kind: "cancelled", makeDetails };
|
|
405
534
|
return {
|
|
406
535
|
kind: "started",
|
|
407
|
-
requestId,
|
|
536
|
+
requestId: lc.requestId,
|
|
408
537
|
instanceName,
|
|
409
|
-
makeDetails:
|
|
538
|
+
makeDetails: lc.makeDetails,
|
|
410
539
|
};
|
|
411
540
|
}
|
package/src/output/normalize.ts
CHANGED
|
@@ -7,35 +7,37 @@ export function normalizeSummaryValue(value: string): string {
|
|
|
7
7
|
return normalized;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
const SECRET_KEY_RE = /secret|token|password|passwd|credential|auth/i;
|
|
11
|
+
const JWT_RE = /^eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/;
|
|
12
|
+
|
|
10
13
|
export function extractSemanticToolTarget(
|
|
11
|
-
toolName: string,
|
|
12
14
|
args: Record<string, unknown>,
|
|
13
15
|
forceJson = false,
|
|
14
16
|
): string {
|
|
15
17
|
if (forceJson) return JSON.stringify(args);
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
18
|
+
const semanticKeys = [
|
|
19
|
+
"command",
|
|
20
|
+
"path",
|
|
21
|
+
"agent",
|
|
22
|
+
"query",
|
|
23
|
+
"url",
|
|
24
|
+
"action",
|
|
25
|
+
"name",
|
|
26
|
+
];
|
|
27
|
+
for (const key of semanticKeys) {
|
|
28
|
+
const value = args[key];
|
|
29
|
+
if (typeof value === "string" && value.trim()) return value;
|
|
30
|
+
}
|
|
31
|
+
for (const key of Object.keys(args)) {
|
|
32
|
+
const value = args[key];
|
|
33
|
+
if (typeof value !== "string" || !value.trim()) continue;
|
|
34
|
+
if (SECRET_KEY_RE.test(key)) continue;
|
|
35
|
+
if (value.length > 60 || JWT_RE.test(value)) continue;
|
|
36
|
+
return value;
|
|
31
37
|
}
|
|
32
38
|
return "";
|
|
33
39
|
}
|
|
34
40
|
|
|
35
|
-
export function stripTerminalStatusPrefixes(value: string): string {
|
|
36
|
-
return value.replace(/^(?:(?:success|failure):\s*)+/i, "");
|
|
37
|
-
}
|
|
38
|
-
|
|
39
41
|
export function truncateText(text: string, limit: number): string {
|
|
40
42
|
if (text.length <= limit) return text;
|
|
41
43
|
return `${text.slice(0, limit - 1)}…`;
|
|
@@ -51,7 +53,10 @@ export function normalizeTerminalSentence(
|
|
|
51
53
|
.replace(/^\s*`{1,3}([^`]+)`{1,3}\s*$/, "$1")
|
|
52
54
|
.replace(/^\s*\*\*([^*]+)\*\*\s*$/, "$1")
|
|
53
55
|
.replace(/^\s*__([^_]+)__\s*$/, "$1");
|
|
54
|
-
const withoutStatusPrefix =
|
|
56
|
+
const withoutStatusPrefix = unwrapped.replace(
|
|
57
|
+
/^(?:(?:success|failure):\s*)+/i,
|
|
58
|
+
"",
|
|
59
|
+
);
|
|
55
60
|
const withoutLabel = withoutStatusPrefix.replace(
|
|
56
61
|
/^\s*(?:status|summary|result|output|message|error|check|outcome|project summary):\s+/i,
|
|
57
62
|
"",
|
|
@@ -64,19 +69,21 @@ export function normalizeTerminalSentence(
|
|
|
64
69
|
|
|
65
70
|
export const TOOL_PREVIEW_MAX_CHARS = 120;
|
|
66
71
|
|
|
72
|
+
export function normalizeAndTruncate(
|
|
73
|
+
text: string,
|
|
74
|
+
limit = TOOL_PREVIEW_MAX_CHARS,
|
|
75
|
+
): string {
|
|
76
|
+
return truncateText(normalizeSummaryValue(text), limit);
|
|
77
|
+
}
|
|
78
|
+
|
|
67
79
|
export function makeToolPreview(
|
|
68
80
|
toolName: string,
|
|
69
81
|
args: Record<string, unknown> | undefined,
|
|
70
82
|
): string {
|
|
71
83
|
if (!args || Object.keys(args).length === 0) return toolName;
|
|
72
|
-
const target = normalizeSummaryValue(
|
|
73
|
-
extractSemanticToolTarget(toolName, args),
|
|
74
|
-
);
|
|
84
|
+
const target = normalizeSummaryValue(extractSemanticToolTarget(args));
|
|
75
85
|
if (!target) return toolName;
|
|
76
|
-
return
|
|
77
|
-
normalizeSummaryValue(`${toolName}: ${target}`),
|
|
78
|
-
TOOL_PREVIEW_MAX_CHARS,
|
|
79
|
-
);
|
|
86
|
+
return normalizeAndTruncate(`${toolName}: ${target}`);
|
|
80
87
|
}
|
|
81
88
|
|
|
82
89
|
export function isStatusOnlySuccess(value: string): boolean {
|
package/src/output/ui.ts
CHANGED
|
@@ -25,8 +25,6 @@ import {
|
|
|
25
25
|
normalizeSummaryValue,
|
|
26
26
|
} from "./normalize.js";
|
|
27
27
|
|
|
28
|
-
export type { ThemeBg };
|
|
29
|
-
|
|
30
28
|
/**
|
|
31
29
|
* Abstraction for theme-aware text formatting.
|
|
32
30
|
*/
|
|
@@ -127,7 +125,7 @@ export function formatToolCall(
|
|
|
127
125
|
forceJson = false,
|
|
128
126
|
): string {
|
|
129
127
|
const target = normalizeSummaryValue(
|
|
130
|
-
extractSemanticToolTarget(
|
|
128
|
+
extractSemanticToolTarget(args, forceJson),
|
|
131
129
|
);
|
|
132
130
|
if (!target) return themeFg("accent", toolName);
|
|
133
131
|
return themeFg("accent", toolName) + themeFg("dim", ` ${target}`);
|
|
@@ -183,7 +181,8 @@ export function renderSubagentCall(
|
|
|
183
181
|
): Text {
|
|
184
182
|
const scope: AgentScope = args.agentScope ?? "both";
|
|
185
183
|
const agentName = args.agent || "...";
|
|
186
|
-
|
|
184
|
+
// Parser-owned preview suppresses task text: show agent + scope only
|
|
185
|
+
const target = args.agent ? `[${scope}]` : JSON.stringify(args);
|
|
187
186
|
let text =
|
|
188
187
|
theme.fg("toolTitle", theme.bold("subagent ")) +
|
|
189
188
|
theme.fg("accent", agentName) +
|
|
@@ -192,15 +191,17 @@ export function renderSubagentCall(
|
|
|
192
191
|
return new Text(text, 0, 0, (line) => theme.bg("toolPendingBg", line));
|
|
193
192
|
}
|
|
194
193
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
194
|
+
export function renderSubagentToolResult(
|
|
195
|
+
result: { content: { type: string; text?: string }[]; details?: unknown },
|
|
196
|
+
theme: SubagentTheme,
|
|
197
|
+
display?: { isPartial?: boolean },
|
|
198
|
+
): Component {
|
|
199
|
+
const details = result.details as SubagentDetails | undefined;
|
|
200
|
+
if (details?.renderedByMessage) return new Text("", 0, 0);
|
|
201
|
+
return renderSubagentResult(result, theme, display);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Invariants: Red background = failure, green = success. Trims redundant "Outcome:" lines. Shows usage stats + duration in footer.
|
|
204
205
|
export function renderSubagentResult(
|
|
205
206
|
result: { content: { type: string; text?: string }[]; details?: unknown },
|
|
206
207
|
theme: SubagentTheme,
|
|
@@ -226,7 +227,15 @@ export function renderSubagentResult(
|
|
|
226
227
|
: "success";
|
|
227
228
|
const finalOutput = r.finalOutput ?? getFinalOutput(r.messages ?? []);
|
|
228
229
|
const title = formatSubagentTitle(r.agent, r.instanceName, theme);
|
|
229
|
-
|
|
230
|
+
let effectiveBody = bodyOverride ?? finalOutput;
|
|
231
|
+
if (_display?.isPartial && !finalOutput?.trim() && !bodyOverride) {
|
|
232
|
+
effectiveBody =
|
|
233
|
+
result.content[0]?.text ||
|
|
234
|
+
r.progress?.activityText ||
|
|
235
|
+
r.progress?.lastToolPreview ||
|
|
236
|
+
"(running...)";
|
|
237
|
+
}
|
|
238
|
+
const bodyText = stripOutcomeLineForResultUi(effectiveBody);
|
|
230
239
|
const toolCount = r.progress?.toolCalls?.length ?? 0;
|
|
231
240
|
const toolLabel = `${toolCount} ${toolCount === 1 ? "tool" : "tools"}`;
|
|
232
241
|
const ctxPercent = formatContextPercent({
|
|
@@ -339,7 +348,6 @@ function renderJobCard(
|
|
|
339
348
|
);
|
|
340
349
|
}
|
|
341
350
|
|
|
342
|
-
/** Sort states by startTime descending (newest first). */
|
|
343
351
|
function sortByStartTimeDesc(
|
|
344
352
|
a: SubagentProgressState,
|
|
345
353
|
b: SubagentProgressState,
|
|
@@ -355,11 +363,8 @@ const BOARD_SECTIONS: [string, ProgressStatus][] = [
|
|
|
355
363
|
["SUCCEEDED", "success"],
|
|
356
364
|
];
|
|
357
365
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
* Jobs render in status-specific sections, each sorted by `startTime` descending.
|
|
361
|
-
* Status icons preserve the existing /jobs contract for running and cancelled jobs.
|
|
362
|
-
*/
|
|
366
|
+
// Jobs render in status-specific sections, each sorted by `startTime` descending.
|
|
367
|
+
// Status icons preserve the existing /jobs contract for running and cancelled jobs.
|
|
363
368
|
export function renderRunsBoard(
|
|
364
369
|
states: SubagentProgressState[],
|
|
365
370
|
theme: SubagentTheme,
|