pi-workflow-engine 0.9.0 → 0.10.1
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/.pi/extensions/pi-workflow-engine/index.ts +136 -26
- package/.pi/extensions/pi-workflow-engine/src/agent-runner.ts +257 -23
- package/.pi/extensions/pi-workflow-engine/src/budget.ts +46 -0
- package/.pi/extensions/pi-workflow-engine/src/cancellation.ts +7 -0
- package/.pi/extensions/pi-workflow-engine/src/concurrency.ts +83 -23
- package/.pi/extensions/pi-workflow-engine/src/diff-capture.ts +15 -69
- package/.pi/extensions/pi-workflow-engine/src/engine.ts +24 -2
- package/.pi/extensions/pi-workflow-engine/src/journal.ts +200 -0
- package/.pi/extensions/pi-workflow-engine/src/options.ts +27 -1
- package/.pi/extensions/pi-workflow-engine/src/process-runner.ts +96 -0
- package/.pi/extensions/pi-workflow-engine/src/types.ts +59 -4
- package/.pi/extensions/pi-workflow-engine/src/ui/workflow-result-renderer.ts +81 -13
- package/.pi/extensions/pi-workflow-engine/src/workflow-advisory-utils.ts +14 -5
- package/.pi/extensions/pi-workflow-engine/src/worktree.ts +238 -0
- package/.pi/extensions/pi-workflow-engine/workflows/code-review.ts +10 -5
- package/.pi/extensions/pi-workflow-engine/workflows/diagnose.ts +12 -8
- package/.pi/extensions/pi-workflow-engine/workflows/perf-review.ts +6 -2
- package/.pi/extensions/pi-workflow-engine/workflows/refactor-scout.ts +6 -2
- package/README.md +10 -1
- package/USAGE.md +50 -8
- package/package.json +8 -8
|
@@ -3,14 +3,21 @@ import { Type } from "typebox";
|
|
|
3
3
|
import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
|
|
4
4
|
import { SelectList, Text, truncateToWidth, type Component, type SelectItem, type SelectListTheme, type TUI } from "@earendil-works/pi-tui";
|
|
5
5
|
import type { WorkflowProgressSnapshot } from "./src/progress.ts";
|
|
6
|
-
import type { WorkflowModule, WorkflowProgressSource, WorkflowRef, WorkflowRunOptions } from "./src/types.ts";
|
|
6
|
+
import type { WorkflowModule, WorkflowProgressSource, WorkflowRef, WorkflowRunMetadata, WorkflowRunOptions } from "./src/types.ts";
|
|
7
7
|
import { WorkflowInspector } from "./src/ui/workflow-inspector.ts";
|
|
8
8
|
import type { PerfSink, PerfSnapshot } from "./src/perf.ts";
|
|
9
|
-
import {
|
|
9
|
+
import type { WorkflowUsageSnapshot } from "./src/usage.ts";
|
|
10
10
|
import { registerDynamax } from "./src/dynamax.ts";
|
|
11
11
|
import { handleReviewViewerAction } from "./src/review/review-actions.ts";
|
|
12
12
|
import { decideReviewResultsPresentation, extensionContextMode, maybeShowReviewResultsViewer } from "./src/review/review-results-flow.ts";
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
formatWorkflowDetailLines,
|
|
15
|
+
isWorkflowResult,
|
|
16
|
+
renderWorkflowResult,
|
|
17
|
+
type WorkflowPerfDetails,
|
|
18
|
+
type WorkflowResultEnvelope,
|
|
19
|
+
} from "./src/ui/workflow-result-renderer.ts";
|
|
20
|
+
import { parseWorkflowBudgetString, WORKFLOW_BUDGET_MAX, WORKFLOW_BUDGET_MIN } from "./src/options.ts";
|
|
14
21
|
|
|
15
22
|
/** Extension root (this file lives in <repo>/.pi/extensions/pi-workflow-engine/index.ts). */
|
|
16
23
|
const EXTENSION_DIR = fileURLToPath(new URL(".", import.meta.url));
|
|
@@ -22,15 +29,25 @@ function summarize(result: unknown): string {
|
|
|
22
29
|
return typeof result === "string" ? result : "Workflow finished.";
|
|
23
30
|
}
|
|
24
31
|
|
|
25
|
-
function formatMessageContent(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
32
|
+
function formatMessageContent(
|
|
33
|
+
name: string,
|
|
34
|
+
result: unknown,
|
|
35
|
+
usage?: WorkflowUsageSnapshot,
|
|
36
|
+
perf?: WorkflowPerfDetails,
|
|
37
|
+
metadata?: WorkflowRunMetadata,
|
|
38
|
+
): string {
|
|
39
|
+
const details = formatWorkflowDetailLines({ usage, perf, metadata });
|
|
29
40
|
return `## Workflow: ${name}\n\n${summarize(result)}${details.length > 0 ? `\n\n${details.join("\n")}` : ""}`;
|
|
30
41
|
}
|
|
31
42
|
|
|
32
|
-
function workflowEnvelope(
|
|
33
|
-
|
|
43
|
+
function workflowEnvelope(
|
|
44
|
+
name: string,
|
|
45
|
+
result: unknown,
|
|
46
|
+
usage?: WorkflowUsageSnapshot,
|
|
47
|
+
perf?: WorkflowPerfDetails,
|
|
48
|
+
metadata?: WorkflowRunMetadata,
|
|
49
|
+
): WorkflowResultEnvelope {
|
|
50
|
+
return { name, result, completedAt: Date.now(), usage, perf, runId: metadata?.runId, resumedFromRunId: metadata?.resumedFromRunId };
|
|
34
51
|
}
|
|
35
52
|
|
|
36
53
|
function compactPerfSnapshot(snapshot: PerfSnapshot | undefined): WorkflowPerfDetails | undefined {
|
|
@@ -38,12 +55,6 @@ function compactPerfSnapshot(snapshot: PerfSnapshot | undefined): WorkflowPerfDe
|
|
|
38
55
|
return { enabled: true, startedAt: snapshot.startedAt, aggregates: snapshot.aggregates };
|
|
39
56
|
}
|
|
40
57
|
|
|
41
|
-
function formatPerfLine(perf: WorkflowPerfDetails | undefined): string | undefined {
|
|
42
|
-
if (!perf) return undefined;
|
|
43
|
-
const parts = perf.aggregates.slice(0, 4).map((aggregate) => `${aggregate.name} ${Math.round(aggregate.total)}ms`);
|
|
44
|
-
return parts.length > 0 ? `Perf: ${parts.join(" · ")}` : "Perf: no samples";
|
|
45
|
-
}
|
|
46
|
-
|
|
47
58
|
type DiscoveryModule = typeof import("./src/discovery.ts");
|
|
48
59
|
type EngineModule = typeof import("./src/engine.ts");
|
|
49
60
|
type InlineWorkflowModule = typeof import("./src/inline-workflow.ts");
|
|
@@ -216,6 +227,7 @@ export interface WorkflowInvocation {
|
|
|
216
227
|
args: string;
|
|
217
228
|
options: WorkflowRunOptions;
|
|
218
229
|
refreshDiscovery?: boolean;
|
|
230
|
+
optionErrors?: string[];
|
|
219
231
|
authorBrief?: string;
|
|
220
232
|
}
|
|
221
233
|
|
|
@@ -224,14 +236,21 @@ export function parseWorkflowInvocation(input: string): WorkflowInvocation {
|
|
|
224
236
|
const space = trimmed.indexOf(" ");
|
|
225
237
|
const name = space === -1 ? trimmed : trimmed.slice(0, space);
|
|
226
238
|
const rest = space === -1 ? "" : trimmed.slice(space + 1).trim();
|
|
227
|
-
const { args, options, refreshDiscovery } = parseWorkflowOptions(rest);
|
|
228
|
-
|
|
239
|
+
const { args, options, refreshDiscovery, optionErrors } = parseWorkflowOptions(rest);
|
|
240
|
+
const invocation: WorkflowInvocation = { name, args, options };
|
|
241
|
+
if (refreshDiscovery) invocation.refreshDiscovery = refreshDiscovery;
|
|
242
|
+
if (optionErrors) invocation.optionErrors = optionErrors;
|
|
243
|
+
return invocation;
|
|
229
244
|
}
|
|
230
245
|
|
|
231
|
-
|
|
246
|
+
const INVALID_BUDGET_OPTION = "--budget requires a positive integer output-token count";
|
|
247
|
+
const INVALID_RESUME_OPTION = "--resume requires a workflow run id";
|
|
248
|
+
|
|
249
|
+
function parseWorkflowOptions(input: string): { args: string; options: WorkflowRunOptions; refreshDiscovery?: boolean; optionErrors?: string[] } {
|
|
232
250
|
const tokens = input.split(/\s+/).filter(Boolean);
|
|
233
251
|
const kept: string[] = [];
|
|
234
252
|
const options: WorkflowRunOptions = {};
|
|
253
|
+
const optionErrors: string[] = [];
|
|
235
254
|
let refreshDiscovery = false;
|
|
236
255
|
for (let i = 0; i < tokens.length; i++) {
|
|
237
256
|
const token = tokens[i];
|
|
@@ -275,9 +294,46 @@ function parseWorkflowOptions(input: string): { args: string; options: WorkflowR
|
|
|
275
294
|
if (next !== undefined) i++;
|
|
276
295
|
continue;
|
|
277
296
|
}
|
|
297
|
+
if (token.startsWith("--budget=")) {
|
|
298
|
+
const parsed = parseBudgetOption(token.slice("--budget=".length));
|
|
299
|
+
if (parsed === undefined) optionErrors.push(INVALID_BUDGET_OPTION);
|
|
300
|
+
else options.budget = parsed;
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
if (token === "--budget") {
|
|
304
|
+
const next = tokens[i + 1];
|
|
305
|
+
const parsed = next === undefined ? undefined : parseBudgetOption(next);
|
|
306
|
+
if (parsed === undefined) {
|
|
307
|
+
optionErrors.push(INVALID_BUDGET_OPTION);
|
|
308
|
+
} else {
|
|
309
|
+
options.budget = parsed;
|
|
310
|
+
i++;
|
|
311
|
+
}
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
if (token.startsWith("--resume=")) {
|
|
315
|
+
const value = token.slice("--resume=".length).trim();
|
|
316
|
+
if (value === "") optionErrors.push(INVALID_RESUME_OPTION);
|
|
317
|
+
else options.resumeFromRunId = value;
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
if (token === "--resume") {
|
|
321
|
+
const next = tokens[i + 1];
|
|
322
|
+
if (next === undefined || next.startsWith("--")) {
|
|
323
|
+
optionErrors.push(INVALID_RESUME_OPTION);
|
|
324
|
+
} else {
|
|
325
|
+
options.resumeFromRunId = next;
|
|
326
|
+
i++;
|
|
327
|
+
}
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
278
330
|
kept.push(token);
|
|
279
331
|
}
|
|
280
|
-
return { args: kept.join(" ").trim(), options, refreshDiscovery: refreshDiscovery || undefined };
|
|
332
|
+
return { args: kept.join(" ").trim(), options, refreshDiscovery: refreshDiscovery || undefined, optionErrors: optionErrors.length > 0 ? optionErrors : undefined };
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function parseBudgetOption(value: string): number | undefined {
|
|
336
|
+
return parseWorkflowBudgetString(value);
|
|
281
337
|
}
|
|
282
338
|
|
|
283
339
|
function parseNumericOption(value: string | undefined): number | undefined {
|
|
@@ -302,6 +358,9 @@ Use the workflow tool with a script argument, not a saved workflow name.
|
|
|
302
358
|
The script must start with export const meta = { ... } and default-export an async workflow function.
|
|
303
359
|
Use the injected Type object for schemas. Do not import anything or use dynamic import().
|
|
304
360
|
Set thinkingLevel explicitly on each agent() call.
|
|
361
|
+
Always pass a plain string as the first api.agent() argument; build prompts with template strings before calling agent().
|
|
362
|
+
If using \`isolation: "worktree"\`, remember api.agent() returns \`{ result, patch, changed }\`; read \`.result\` for the agent answer and \`.patch\` for the diff.
|
|
363
|
+
When the run is budgeted, guard expensive loops with \`while (api.budget.total && api.budget.remaining() > N) { ... }\`; api.agent() throws once the budget is spent.
|
|
305
364
|
Subagents receive no skills by default. When the brief asks for a skill or a stage clearly benefits from one, pass \`skills: ["skill-name"]\` on that agent call only.
|
|
306
365
|
Do not edit files unless the user explicitly requested edits.`;
|
|
307
366
|
}
|
|
@@ -309,6 +368,7 @@ Do not edit files unless the user explicitly requested edits.`;
|
|
|
309
368
|
export interface WorkflowToolRequestParams {
|
|
310
369
|
readonly name?: string;
|
|
311
370
|
readonly script?: string;
|
|
371
|
+
readonly resumeFromRunId?: string;
|
|
312
372
|
}
|
|
313
373
|
|
|
314
374
|
export type WorkflowToolRequest =
|
|
@@ -375,6 +435,7 @@ export async function sendWorkflowResult(
|
|
|
375
435
|
const { runWorkflow } = await loadEngine();
|
|
376
436
|
let perfSnapshot: PerfSnapshot | undefined;
|
|
377
437
|
let usageSnapshot: WorkflowUsageSnapshot | undefined;
|
|
438
|
+
let runMetadata: WorkflowRunMetadata | undefined;
|
|
378
439
|
let liveInspection: ActiveWorkflowInspection | undefined;
|
|
379
440
|
const result = await runWorkflow(ctx, mod, args, {
|
|
380
441
|
...options,
|
|
@@ -399,6 +460,10 @@ export async function sendWorkflowResult(
|
|
|
399
460
|
usageSnapshot = snapshot;
|
|
400
461
|
options.onUsageSnapshot?.(snapshot);
|
|
401
462
|
},
|
|
463
|
+
onRunMetadata(metadata) {
|
|
464
|
+
runMetadata = metadata;
|
|
465
|
+
options.onRunMetadata?.(metadata);
|
|
466
|
+
},
|
|
402
467
|
onProgressSnapshot(snapshot) {
|
|
403
468
|
lastWorkflowInspection = { name, args, completedAt: snapshot.doneAt ?? Date.now(), snapshot };
|
|
404
469
|
options.onProgressSnapshot?.(snapshot);
|
|
@@ -418,7 +483,12 @@ export async function sendWorkflowResult(
|
|
|
418
483
|
await handleReviewViewerAction(pi, ctx, reviewAction, reviewDecision.issues, reviewDecision.report.reviewContext);
|
|
419
484
|
}
|
|
420
485
|
pi.sendMessage(
|
|
421
|
-
{
|
|
486
|
+
{
|
|
487
|
+
customType: "workflow-result",
|
|
488
|
+
content: formatMessageContent(name, result, usageSnapshot, perf, runMetadata),
|
|
489
|
+
display: true,
|
|
490
|
+
details: workflowEnvelope(name, result, usageSnapshot, perf, runMetadata),
|
|
491
|
+
},
|
|
422
492
|
{ triggerTurn: false },
|
|
423
493
|
);
|
|
424
494
|
}
|
|
@@ -428,7 +498,12 @@ export default function workflowEngine(pi: ExtensionAPI): void {
|
|
|
428
498
|
|
|
429
499
|
pi.registerMessageRenderer("workflow-result", (message, { expanded }, theme) => {
|
|
430
500
|
const details = message.details;
|
|
431
|
-
if (isWorkflowResult(details))
|
|
501
|
+
if (isWorkflowResult(details)) {
|
|
502
|
+
return renderWorkflowResult(details.name, details.result, expanded, theme, details.usage, {
|
|
503
|
+
runId: details.runId,
|
|
504
|
+
resumedFromRunId: details.resumedFromRunId,
|
|
505
|
+
}, details.perf);
|
|
506
|
+
}
|
|
432
507
|
return renderWorkflowResult("workflow", details ?? message.content, expanded, theme);
|
|
433
508
|
});
|
|
434
509
|
|
|
@@ -449,6 +524,10 @@ export default function workflowEngine(pi: ExtensionAPI): void {
|
|
|
449
524
|
description: "Run a multi-agent workflow: /workflow <name> [args]",
|
|
450
525
|
handler: async (args: string, ctx: ExtensionCommandContext) => {
|
|
451
526
|
const direct = parseWorkflowInvocation(args);
|
|
527
|
+
if (direct.optionErrors?.length) {
|
|
528
|
+
ctx.ui.notify(`Invalid workflow option: ${direct.optionErrors.join("; ")}`, "warning");
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
452
531
|
const perfRecorder = await createInvocationPerf(direct.options);
|
|
453
532
|
const { discoverWorkflows } = await loadDiscovery();
|
|
454
533
|
const workflows = await discoverWorkflows(EXTENSION_DIR, { refresh: direct.refreshDiscovery, perf: perfRecorder });
|
|
@@ -489,6 +568,10 @@ export default function workflowEngine(pi: ExtensionAPI): void {
|
|
|
489
568
|
"Inline workflow scripts must use the injected `Type` object for schemas and must not contain imports or dynamic import().",
|
|
490
569
|
"Inline scripts may compose registered workflows in-process via `api.workflow(\"<name>\", args)` (e.g. `await api.workflow(\"code-review\", \"HEAD~3\")`); it returns the sub-workflow's result and nests one level only.",
|
|
491
570
|
"Subagents receive no skills by default. In inline workflows, pass `skills: [\"skill-name\"]` per `agent()` call when the user asks for a skill or a stage should use one; grant only the needed skills.",
|
|
571
|
+
"Always pass a plain string as the first `api.agent()` argument; build prompts with template strings before calling agent().",
|
|
572
|
+
"When using `isolation: \"worktree\"`, `api.agent()` returns `{ result, patch, changed }`; use `.result` for the answer and `.patch` for the isolated diff.",
|
|
573
|
+
"If an inline subagent needs grep/find/code-search helpers, use `tools: [\"read\", \"bash\", \"grep\", \"find\", \"ls\"]` plus `toolHints: [\"search\"]` so installed tools such as ast-grep, mgrep, ffgrep, or fffind are discovered dynamically.",
|
|
574
|
+
"`api.budget` exposes `{ total, spent(), remaining() }` (output tokens). When the run is budgeted, scale fleets from `budget.total` and guard loops with `while (budget.total && budget.remaining() > N) { await api.agent(...) }`; `api.agent()` throws once the ceiling is reached.",
|
|
492
575
|
"Every workflow tool call must provide exactly one of `name` or `script`, never both.",
|
|
493
576
|
],
|
|
494
577
|
parameters: Type.Object({
|
|
@@ -497,7 +580,15 @@ export default function workflowEngine(pi: ExtensionAPI): void {
|
|
|
497
580
|
args: Type.Optional(Type.String({ description: "Arguments for the workflow (e.g. target or focus)" })),
|
|
498
581
|
concurrency: Type.Optional(Type.Number({ description: "Optional per-run agent concurrency cap" })),
|
|
499
582
|
parallelSubmissionLimit: Type.Optional(Type.Number({ description: "Optional limit for eagerly submitted parallel thunks" })),
|
|
583
|
+
budget: Type.Optional(
|
|
584
|
+
Type.Integer({
|
|
585
|
+
minimum: WORKFLOW_BUDGET_MIN,
|
|
586
|
+
maximum: WORKFLOW_BUDGET_MAX,
|
|
587
|
+
description: "Optional output-token ceiling for the run; agent() throws once it is exceeded",
|
|
588
|
+
}),
|
|
589
|
+
),
|
|
500
590
|
perf: Type.Optional(Type.Boolean({ description: "Include workflow performance timing aggregates in the result details" })),
|
|
591
|
+
resumeFromRunId: Type.Optional(Type.String({ minLength: 1, description: "Workflow run id to resume from by replaying matching completed agent results" })),
|
|
501
592
|
}),
|
|
502
593
|
renderCall(args, theme) {
|
|
503
594
|
const suffix = args.args ? ` ${theme.fg("dim", args.args)}` : "";
|
|
@@ -511,7 +602,12 @@ export default function workflowEngine(pi: ExtensionAPI): void {
|
|
|
511
602
|
renderResult(result, { expanded, isPartial }, theme) {
|
|
512
603
|
if (isPartial) return new Text(theme.fg("accent", "Running workflow…"), 0, 0);
|
|
513
604
|
const details = result.details;
|
|
514
|
-
if (isWorkflowResult(details))
|
|
605
|
+
if (isWorkflowResult(details)) {
|
|
606
|
+
return renderWorkflowResult(details.name, details.result, expanded, theme, details.usage, {
|
|
607
|
+
runId: details.runId,
|
|
608
|
+
resumedFromRunId: details.resumedFromRunId,
|
|
609
|
+
}, details.perf);
|
|
610
|
+
}
|
|
515
611
|
const first = result.content[0];
|
|
516
612
|
const text = first?.type === "text" ? first.text : "Workflow finished.";
|
|
517
613
|
return new Text(theme.fg("muted", text), 0, 0);
|
|
@@ -519,12 +615,21 @@ export default function workflowEngine(pi: ExtensionAPI): void {
|
|
|
519
615
|
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
520
616
|
const request = normalizeWorkflowToolRequest(params);
|
|
521
617
|
if (request.kind === "error") return invalidWorkflowInvocationResult();
|
|
618
|
+
const resumeFromRunId = params.resumeFromRunId?.trim();
|
|
619
|
+
if (params.resumeFromRunId !== undefined && resumeFromRunId === "") {
|
|
620
|
+
return {
|
|
621
|
+
content: [{ type: "text", text: "resumeFromRunId must be non-empty." }],
|
|
622
|
+
details: { error: "invalid_resume_from_run_id" },
|
|
623
|
+
};
|
|
624
|
+
}
|
|
522
625
|
|
|
523
626
|
const runOptions: WorkflowRunOptions = {
|
|
524
627
|
inspect: ctx.hasUI,
|
|
525
628
|
concurrency: params.concurrency,
|
|
526
629
|
parallelSubmissionLimit: params.parallelSubmissionLimit,
|
|
630
|
+
budget: params.budget,
|
|
527
631
|
perf: params.perf,
|
|
632
|
+
resumeFromRunId,
|
|
528
633
|
signal,
|
|
529
634
|
};
|
|
530
635
|
const perfRecorder = await createInvocationPerf(runOptions);
|
|
@@ -558,6 +663,7 @@ export default function workflowEngine(pi: ExtensionAPI): void {
|
|
|
558
663
|
const { runWorkflow } = await loadEngine();
|
|
559
664
|
let perfSnapshot: PerfSnapshot | undefined;
|
|
560
665
|
let usageSnapshot: WorkflowUsageSnapshot | undefined;
|
|
666
|
+
let runMetadata: WorkflowRunMetadata | undefined;
|
|
561
667
|
let liveInspection: ActiveWorkflowInspection | undefined;
|
|
562
668
|
const resultArgs = params.args ?? "";
|
|
563
669
|
const result = await runWorkflow(ctx, mod, resultArgs, {
|
|
@@ -580,6 +686,9 @@ export default function workflowEngine(pi: ExtensionAPI): void {
|
|
|
580
686
|
onUsageSnapshot: (snapshot) => {
|
|
581
687
|
usageSnapshot = snapshot;
|
|
582
688
|
},
|
|
689
|
+
onRunMetadata: (metadata) => {
|
|
690
|
+
runMetadata = metadata;
|
|
691
|
+
},
|
|
583
692
|
onProgressSnapshot: (snapshot) => {
|
|
584
693
|
// Record the run so /workflow:inspector can reopen it — tool-invoked (dynamax) runs were
|
|
585
694
|
// previously uninspectable, unlike the /workflow command path.
|
|
@@ -587,10 +696,11 @@ export default function workflowEngine(pi: ExtensionAPI): void {
|
|
|
587
696
|
},
|
|
588
697
|
});
|
|
589
698
|
const perf = compactPerfSnapshot(perfSnapshot);
|
|
590
|
-
const
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
699
|
+
const detailLines = formatWorkflowDetailLines({ usage: usageSnapshot, perf, metadata: runMetadata });
|
|
700
|
+
return {
|
|
701
|
+
content: [{ type: "text", text: `${summarize(result)}${detailLines.length > 0 ? `\n\n${detailLines.join("\n")}` : ""}` }],
|
|
702
|
+
details: workflowEnvelope(resultName, result, usageSnapshot, perf, runMetadata),
|
|
703
|
+
};
|
|
594
704
|
},
|
|
595
705
|
});
|
|
596
706
|
}
|