pi-workflow-engine 0.13.0 → 1.0.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/.pi/extensions/pi-workflow-engine/index.ts +106 -391
- package/.pi/extensions/pi-workflow-engine/src/advisory-challenge.ts +1 -1
- package/.pi/extensions/pi-workflow-engine/src/advisory-evidence.ts +8 -8
- package/.pi/extensions/pi-workflow-engine/src/advisory-schema.ts +7 -1
- package/.pi/extensions/pi-workflow-engine/src/agent-attempt.ts +48 -62
- package/.pi/extensions/pi-workflow-engine/src/agent-replay.ts +46 -67
- package/.pi/extensions/pi-workflow-engine/src/agent-retry.ts +14 -37
- package/.pi/extensions/pi-workflow-engine/src/agent-runner-types.ts +5 -11
- package/.pi/extensions/pi-workflow-engine/src/agent-runner.ts +15 -21
- package/.pi/extensions/pi-workflow-engine/src/agent-session-identity.ts +13 -43
- package/.pi/extensions/pi-workflow-engine/src/agent-session-providers.ts +17 -5
- package/.pi/extensions/pi-workflow-engine/src/agent-session.ts +93 -100
- package/.pi/extensions/pi-workflow-engine/src/agent-skills.ts +11 -43
- package/.pi/extensions/pi-workflow-engine/src/agent-workspace.ts +4 -18
- package/.pi/extensions/pi-workflow-engine/src/background-workflows.ts +33 -86
- package/.pi/extensions/pi-workflow-engine/src/cancellation.ts +1 -5
- package/.pi/extensions/pi-workflow-engine/src/concurrency.ts +25 -80
- package/.pi/extensions/pi-workflow-engine/src/discovery.ts +16 -23
- package/.pi/extensions/pi-workflow-engine/src/dynamax-shortcuts.ts +1 -4
- package/.pi/extensions/pi-workflow-engine/src/dynamax.ts +39 -98
- package/.pi/extensions/pi-workflow-engine/src/engine.ts +37 -80
- package/.pi/extensions/pi-workflow-engine/src/finalizers.ts +2 -10
- package/.pi/extensions/pi-workflow-engine/src/git.ts +55 -0
- package/.pi/extensions/pi-workflow-engine/src/guards.ts +8 -0
- package/.pi/extensions/pi-workflow-engine/src/identity-canonicalization.ts +7 -19
- package/.pi/extensions/pi-workflow-engine/src/inline-workflow.ts +171 -106
- package/.pi/extensions/pi-workflow-engine/src/journal.ts +0 -12
- package/.pi/extensions/pi-workflow-engine/src/model-profile-command.ts +37 -8
- package/.pi/extensions/pi-workflow-engine/src/model-profiles.ts +15 -66
- package/.pi/extensions/pi-workflow-engine/src/options.ts +97 -16
- package/.pi/extensions/pi-workflow-engine/src/perf.ts +43 -72
- package/.pi/extensions/pi-workflow-engine/src/process-runner.ts +14 -4
- package/.pi/extensions/pi-workflow-engine/src/progress-types.ts +2 -0
- package/.pi/extensions/pi-workflow-engine/src/progress.ts +79 -80
- package/.pi/extensions/pi-workflow-engine/src/provider-usage-limit.ts +28 -20
- package/.pi/extensions/pi-workflow-engine/src/replay-path-identity.ts +2 -7
- package/.pi/extensions/pi-workflow-engine/src/research-contract.ts +0 -1
- package/.pi/extensions/pi-workflow-engine/src/research-evidence.ts +27 -31
- package/.pi/extensions/pi-workflow-engine/src/resume-context.ts +75 -139
- package/.pi/extensions/pi-workflow-engine/src/review/github-pr-comments.ts +14 -31
- package/.pi/extensions/pi-workflow-engine/src/review/patch-validation.ts +3 -4
- package/.pi/extensions/pi-workflow-engine/src/review/review-actions.ts +1 -1
- package/.pi/extensions/pi-workflow-engine/src/review/review-diff-lines.ts +54 -0
- package/.pi/extensions/pi-workflow-engine/src/review/review-fix-workflow.ts +7 -9
- package/.pi/extensions/pi-workflow-engine/src/review/review-format.ts +51 -117
- package/.pi/extensions/pi-workflow-engine/src/review/review-handoff.ts +1 -1
- package/.pi/extensions/pi-workflow-engine/src/review/review-issues.ts +8 -13
- package/.pi/extensions/pi-workflow-engine/src/review/review-report.ts +3 -7
- package/.pi/extensions/pi-workflow-engine/src/review/review-results-viewer.ts +158 -241
- package/.pi/extensions/pi-workflow-engine/src/review/review-session-coordinator.ts +28 -38
- package/.pi/extensions/pi-workflow-engine/src/review/review-snapshot.ts +35 -44
- package/.pi/extensions/pi-workflow-engine/src/review-diff-target.ts +13 -14
- package/.pi/extensions/pi-workflow-engine/src/text.ts +29 -0
- package/.pi/extensions/pi-workflow-engine/src/tool-capabilities.ts +2 -1
- package/.pi/extensions/pi-workflow-engine/src/tool-source-identity.ts +16 -28
- package/.pi/extensions/pi-workflow-engine/src/tree-fingerprint.ts +34 -14
- package/.pi/extensions/pi-workflow-engine/src/types.ts +6 -0
- package/.pi/extensions/pi-workflow-engine/src/ui/kit.ts +193 -0
- package/.pi/extensions/pi-workflow-engine/src/ui/research-result-renderer.ts +177 -0
- package/.pi/extensions/pi-workflow-engine/src/ui/result-layout.ts +209 -0
- package/.pi/extensions/pi-workflow-engine/src/ui/workflow-format.ts +17 -44
- package/.pi/extensions/pi-workflow-engine/src/ui/workflow-inspector.ts +703 -235
- package/.pi/extensions/pi-workflow-engine/src/ui/workflow-picker.ts +48 -0
- package/.pi/extensions/pi-workflow-engine/src/ui/workflow-result-renderer.ts +239 -129
- package/.pi/extensions/pi-workflow-engine/src/ui/workflow-runs-browser.ts +461 -0
- package/.pi/extensions/pi-workflow-engine/src/ui/workflow-tool-renderer.ts +91 -0
- package/.pi/extensions/pi-workflow-engine/src/ui/workflow-widget.ts +316 -98
- package/.pi/extensions/pi-workflow-engine/src/usage.ts +21 -32
- package/.pi/extensions/pi-workflow-engine/src/workflow-advisory-utils.ts +56 -19
- package/.pi/extensions/pi-workflow-engine/src/workflow-execution.ts +19 -24
- package/.pi/extensions/pi-workflow-engine/src/workflow-module.ts +17 -37
- package/.pi/extensions/pi-workflow-engine/src/workflow-result-context.ts +79 -0
- package/.pi/extensions/pi-workflow-engine/src/workflow-run-background.ts +4 -14
- package/.pi/extensions/pi-workflow-engine/src/workflow-run-controller.ts +77 -63
- package/.pi/extensions/pi-workflow-engine/src/workflow-run-history.ts +42 -29
- package/.pi/extensions/pi-workflow-engine/src/workflow-run-record.ts +15 -14
- package/.pi/extensions/pi-workflow-engine/src/workflow-run-store.ts +27 -31
- package/.pi/extensions/pi-workflow-engine/src/workflow-usage-limit-scheduler.ts +1 -5
- package/.pi/extensions/pi-workflow-engine/src/workflows.ts +3 -6
- package/.pi/extensions/pi-workflow-engine/src/worktree.ts +48 -110
- package/.pi/extensions/pi-workflow-engine/workflows/code-review.ts +64 -93
- package/.pi/extensions/pi-workflow-engine/workflows/diagnose.ts +25 -67
- package/.pi/extensions/pi-workflow-engine/workflows/perf-review.ts +19 -53
- package/.pi/extensions/pi-workflow-engine/workflows/refactor-scout.ts +21 -49
- package/.pi/extensions/pi-workflow-engine/workflows/research.ts +74 -66
- package/USAGE.md +31 -17
- package/package.json +1 -2
- package/skills/workflow-code-review-actions/SKILL.md +12 -23
- package/.pi/extensions/pi-workflow-engine/src/diff-capture.ts +0 -81
- package/.pi/extensions/pi-workflow-engine/src/review/code-review-orchestration.ts +0 -24
- package/.pi/extensions/pi-workflow-engine/src/review/review-results-flow.ts +0 -52
- package/.pi/settings.json +0 -3
|
@@ -1,26 +1,21 @@
|
|
|
1
1
|
import { fileURLToPath } from "node:url";
|
|
2
2
|
import { Type } from "typebox";
|
|
3
|
-
import { VERSION, type ExtensionAPI, type ExtensionCommandContext, type ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
4
|
-
import {
|
|
5
|
-
import { isAdvisoryReport } from "./src/advisory-schema.ts";
|
|
3
|
+
import { getSelectListTheme, keyText, VERSION, type ExtensionAPI, type ExtensionCommandContext, type ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import type { AutocompleteItem, SelectItem } from "@earendil-works/pi-tui";
|
|
6
5
|
import type { WorkflowProgressSnapshot } from "./src/progress-types.ts";
|
|
7
|
-
import type {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
6
|
+
import type { WorkflowRunState } from "./src/workflow-run-record.ts";
|
|
7
|
+
import { WORKFLOW_TOOL_NAME, type LoadedWorkflow, type WorkflowModule, type WorkflowProgressSource, type WorkflowRef } from "./src/types.ts";
|
|
8
|
+
import { showWorkflowInspector } from "./src/ui/workflow-inspector.ts";
|
|
9
|
+
import { WorkflowPicker } from "./src/ui/workflow-picker.ts";
|
|
10
10
|
import type { PerfSink } from "./src/perf.ts";
|
|
11
|
-
import type { WorkflowUsageSnapshot } from "./src/usage.ts";
|
|
12
11
|
import { ADAPTIVE_WORKFLOW_GUIDANCE, registerDynamax } from "./src/dynamax.ts";
|
|
13
12
|
import { sessionKey } from "./src/session-identity.ts";
|
|
14
13
|
import { resolveDynamaxShortcuts, type DynamaxShortcuts } from "./src/dynamax-shortcuts.ts";
|
|
15
14
|
import { ReviewSessionCoordinator } from "./src/review/review-session-coordinator.ts";
|
|
15
|
+
import { isWorkflowResult, renderWorkflowResult, type WorkflowResultHints } from "./src/ui/workflow-result-renderer.ts";
|
|
16
|
+
import { renderWorkflowToolCall, renderWorkflowToolResult } from "./src/ui/workflow-tool-renderer.ts";
|
|
16
17
|
import {
|
|
17
|
-
|
|
18
|
-
isWorkflowResult,
|
|
19
|
-
renderWorkflowResult,
|
|
20
|
-
} from "./src/ui/workflow-result-renderer.ts";
|
|
21
|
-
import {
|
|
22
|
-
parseWorkflowBudgetString,
|
|
23
|
-
parseWorkflowIntegerString,
|
|
18
|
+
parseWorkflowInvocation,
|
|
24
19
|
resolveWorkflowRunOptions,
|
|
25
20
|
type ResolvedWorkflowRunOptions,
|
|
26
21
|
WORKFLOW_AGENT_TIMEOUT_MAX_MS,
|
|
@@ -36,7 +31,8 @@ import {
|
|
|
36
31
|
WORKFLOW_USAGE_LIMIT_DELAY_MAX_MS,
|
|
37
32
|
WORKFLOW_USAGE_LIMIT_DELAY_MIN_MS,
|
|
38
33
|
} from "./src/options.ts";
|
|
39
|
-
import { executeWorkflowInvocation, type
|
|
34
|
+
import { executeWorkflowInvocation, type WorkflowResultEnvelope } from "./src/workflow-execution.ts";
|
|
35
|
+
import { formatWorkflowResultForContext } from "./src/workflow-result-context.ts";
|
|
40
36
|
import { registerWorkflowModelProfileCommand } from "./src/model-profile-command.ts";
|
|
41
37
|
import { BackgroundWorkflowCoordinator } from "./src/background-workflows.ts";
|
|
42
38
|
import { backgroundUnavailableResult, startBackgroundWorkflowTool } from "./src/background-workflow-tool.ts";
|
|
@@ -48,54 +44,6 @@ import { formatWorkflowInspection, workflowInspectionSnapshot } from "./src/ui/w
|
|
|
48
44
|
/** Extension root (this file lives in <repo>/.pi/extensions/pi-workflow-engine/index.ts). */
|
|
49
45
|
const EXTENSION_DIR = fileURLToPath(new URL(".", import.meta.url));
|
|
50
46
|
|
|
51
|
-
function summarize(result: unknown): string {
|
|
52
|
-
if (typeof result === "object" && result !== null && "summary" in result && typeof result.summary === "string") return result.summary;
|
|
53
|
-
return typeof result === "string" ? result : "Workflow finished.";
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function formatMessageContent(
|
|
57
|
-
name: string,
|
|
58
|
-
result: unknown,
|
|
59
|
-
usage?: WorkflowUsageSnapshot,
|
|
60
|
-
perf?: WorkflowPerfDetails,
|
|
61
|
-
metadata?: WorkflowRunMetadata,
|
|
62
|
-
): string {
|
|
63
|
-
const details = formatWorkflowDetailLines({ usage, perf, metadata });
|
|
64
|
-
return `## Workflow: ${name}\n\n${formatResultForContext(result)}${details.length > 0 ? `\n\n${details.join("\n")}` : ""}`;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function formatResultForContext(result: unknown): string {
|
|
68
|
-
if (!isAdvisoryReport(result)) return summarize(result);
|
|
69
|
-
|
|
70
|
-
const lines = [result.summary];
|
|
71
|
-
if (result.findings.length > 0) {
|
|
72
|
-
lines.push("", "Findings:");
|
|
73
|
-
result.findings.forEach((finding, index) => {
|
|
74
|
-
const id = `R${String(index + 1).padStart(3, "0")}`;
|
|
75
|
-
lines.push(
|
|
76
|
-
`\n### ${id}: ${finding.summary}`,
|
|
77
|
-
`- Severity: ${finding.severity}`,
|
|
78
|
-
`- Confidence: ${finding.confidence}`,
|
|
79
|
-
`- Category: ${finding.category}`,
|
|
80
|
-
`- Location: ${finding.locations.map(formatFindingLocation).join(", ")}`,
|
|
81
|
-
`- Impact: ${finding.impact}`,
|
|
82
|
-
`- Evidence: ${finding.evidence.length > 0 ? finding.evidence.join("; ") : "(none cited)"}`,
|
|
83
|
-
`- Recommendation: ${finding.recommendation}`,
|
|
84
|
-
);
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
if (result.nextSteps.length > 0) {
|
|
88
|
-
lines.push("", "Next steps:", ...result.nextSteps.map((step) => `- ${step}`));
|
|
89
|
-
}
|
|
90
|
-
return lines.join("\n");
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function formatFindingLocation(location: { readonly file: string; readonly line?: number; readonly symbol?: string }): string {
|
|
94
|
-
const line = location.line === undefined ? "" : `:${location.line}`;
|
|
95
|
-
const symbol = location.symbol === undefined ? "" : ` (${location.symbol})`;
|
|
96
|
-
return `${location.file}${line}${symbol}`;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
47
|
type DiscoveryModule = typeof import("./src/discovery.ts");
|
|
100
48
|
type EngineModule = typeof import("./src/engine.ts");
|
|
101
49
|
type InlineWorkflowModule = typeof import("./src/inline-workflow.ts");
|
|
@@ -133,7 +81,7 @@ export async function resolveWorkflowRef(ref: WorkflowRef, perf?: PerfSink): Pro
|
|
|
133
81
|
}
|
|
134
82
|
|
|
135
83
|
const AUTHOR_TEMP_WORKFLOW_VALUE = "__author-temporary-workflow__";
|
|
136
|
-
const AUTHOR_TEMP_WORKFLOW_LABEL = "
|
|
84
|
+
const AUTHOR_TEMP_WORKFLOW_LABEL = "+ author a one-off workflow";
|
|
137
85
|
const AUTHOR_TEMP_WORKFLOW_DESCRIPTION = "Ask the host agent to author and run an inline workflow.";
|
|
138
86
|
|
|
139
87
|
const WORKFLOW_OPTION_COMPLETIONS = [
|
|
@@ -147,7 +95,7 @@ const WORKFLOW_OPTION_COMPLETIONS = [
|
|
|
147
95
|
{ value: "--parallel-limit=", description: "Set the parallel submission limit" },
|
|
148
96
|
{ value: "--max-agents=", description: "Set the maximum admitted live agents" },
|
|
149
97
|
{ value: "--agent-timeout-ms=", description: "Set the timeout for each agent attempt" },
|
|
150
|
-
{ value: "--agent-retries=", description: "
|
|
98
|
+
{ value: "--agent-retries=", description: "Restart an agent after pi's in-session turn retries are exhausted" },
|
|
151
99
|
{ value: "--budget=", description: "Set the workflow output-token budget" },
|
|
152
100
|
{ value: "--resume=", description: "Resume from a retained workflow run ID" },
|
|
153
101
|
] as const;
|
|
@@ -168,21 +116,22 @@ async function workflowArgumentCompletions(argumentPrefix: string): Promise<Auto
|
|
|
168
116
|
return completeCurrentArgument(argumentPrefix, WORKFLOW_OPTION_COMPLETIONS);
|
|
169
117
|
}
|
|
170
118
|
|
|
119
|
+
/** Registered workflows in discovery order (built-ins first), then the author option. */
|
|
171
120
|
async function selectWorkflowValue(workflows: ReadonlyMap<string, WorkflowModule>, ctx: ExtensionCommandContext): Promise<string | undefined> {
|
|
121
|
+
const workflowItems: SelectItem[] = [...workflows.values()].map((workflow) => ({
|
|
122
|
+
value: workflow.meta.name,
|
|
123
|
+
label: workflow.meta.name,
|
|
124
|
+
description: workflow.meta.description,
|
|
125
|
+
}));
|
|
126
|
+
if (ctx.mode === "tui") {
|
|
127
|
+
const items = [...workflowItems, { value: AUTHOR_TEMP_WORKFLOW_VALUE, label: AUTHOR_TEMP_WORKFLOW_LABEL }];
|
|
128
|
+
return await ctx.ui.custom<string | undefined>((...[, theme, , done]) => new WorkflowPicker(items, theme, getSelectListTheme(), done));
|
|
129
|
+
}
|
|
172
130
|
const choices = [
|
|
173
|
-
{
|
|
174
|
-
|
|
175
|
-
display: `${AUTHOR_TEMP_WORKFLOW_LABEL} — ${AUTHOR_TEMP_WORKFLOW_DESCRIPTION}`,
|
|
176
|
-
},
|
|
177
|
-
...[...workflows.values()].map((workflow) => ({
|
|
178
|
-
value: workflow.meta.name,
|
|
179
|
-
display: `${workflow.meta.name} — ${workflow.meta.description}`,
|
|
180
|
-
})),
|
|
131
|
+
...workflowItems.map((item) => ({ value: item.value, display: `${item.label} — ${item.description}` })),
|
|
132
|
+
{ value: AUTHOR_TEMP_WORKFLOW_VALUE, display: `${AUTHOR_TEMP_WORKFLOW_LABEL} — ${AUTHOR_TEMP_WORKFLOW_DESCRIPTION}` },
|
|
181
133
|
];
|
|
182
|
-
const selected = await ctx.ui.select(
|
|
183
|
-
"Run workflow",
|
|
184
|
-
choices.map((choice) => choice.display),
|
|
185
|
-
);
|
|
134
|
+
const selected = await ctx.ui.select("Run a workflow", choices.map((choice) => choice.display));
|
|
186
135
|
return choices.find((choice) => choice.display === selected)?.value;
|
|
187
136
|
}
|
|
188
137
|
|
|
@@ -191,6 +140,7 @@ export interface LastWorkflowInspection {
|
|
|
191
140
|
readonly args: string;
|
|
192
141
|
readonly completedAt: number;
|
|
193
142
|
readonly snapshot: WorkflowProgressSnapshot;
|
|
143
|
+
readonly state?: () => WorkflowRunState;
|
|
194
144
|
}
|
|
195
145
|
|
|
196
146
|
export interface ActiveWorkflowInspection {
|
|
@@ -198,6 +148,7 @@ export interface ActiveWorkflowInspection {
|
|
|
198
148
|
readonly args: string;
|
|
199
149
|
readonly startedAt: number;
|
|
200
150
|
readonly snapshot: () => WorkflowProgressSnapshot;
|
|
151
|
+
readonly state?: () => WorkflowRunState;
|
|
201
152
|
}
|
|
202
153
|
|
|
203
154
|
interface SessionWorkflowInspections {
|
|
@@ -206,22 +157,13 @@ interface SessionWorkflowInspections {
|
|
|
206
157
|
}
|
|
207
158
|
|
|
208
159
|
const workflowInspections = new WeakMap<ExtensionAPI, Map<string, SessionWorkflowInspections>>();
|
|
209
|
-
let latestWorkflowInspection: LastWorkflowInspection | undefined;
|
|
210
|
-
let latestActiveWorkflowInspection: ActiveWorkflowInspection | undefined;
|
|
211
|
-
|
|
212
|
-
export function getLastWorkflowInspection(): LastWorkflowInspection | undefined {
|
|
213
|
-
return latestWorkflowInspection;
|
|
214
|
-
}
|
|
215
160
|
|
|
216
161
|
export async function openWorkflowInspector(ctx: ExtensionContext, inspection: LastWorkflowInspection | ActiveWorkflowInspection): Promise<void> {
|
|
217
162
|
if (!ctx.hasUI || ctx.mode !== "tui") {
|
|
218
163
|
ctx.ui.notify(formatWorkflowInspection(inspection), "info");
|
|
219
164
|
return;
|
|
220
165
|
}
|
|
221
|
-
await ctx.ui.
|
|
222
|
-
(tui, theme, _keybindings, done) => new WorkflowInspector(() => workflowInspectionSnapshot(inspection), tui, theme, () => done(undefined)),
|
|
223
|
-
WORKFLOW_VIEWER_OVERLAY_OPTIONS,
|
|
224
|
-
);
|
|
166
|
+
await showWorkflowInspector(ctx.ui, () => workflowInspectionSnapshot(inspection), () => ({ state: inspection.state?.() }));
|
|
225
167
|
}
|
|
226
168
|
|
|
227
169
|
function workflowInspectionState(pi: ExtensionAPI, ctx: ExtensionContext): SessionWorkflowInspections {
|
|
@@ -244,190 +186,7 @@ async function openAvailableWorkflowInspector(pi: ExtensionAPI, ctx: ExtensionCo
|
|
|
244
186
|
}
|
|
245
187
|
|
|
246
188
|
function bindActiveWorkflowInspection(name: string, args: string, source: WorkflowProgressSource): ActiveWorkflowInspection {
|
|
247
|
-
return { name, args, startedAt: Date.now(), snapshot: () => source.snapshot() };
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
export interface WorkflowInvocation {
|
|
251
|
-
name: string;
|
|
252
|
-
args: string;
|
|
253
|
-
options: WorkflowRunOptions;
|
|
254
|
-
refreshDiscovery?: boolean;
|
|
255
|
-
optionErrors?: string[];
|
|
256
|
-
authorBrief?: string;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
export function parseWorkflowInvocation(input: string): WorkflowInvocation {
|
|
260
|
-
const trimmed = input.trim();
|
|
261
|
-
const space = trimmed.indexOf(" ");
|
|
262
|
-
const name = space === -1 ? trimmed : trimmed.slice(0, space);
|
|
263
|
-
const rest = space === -1 ? "" : trimmed.slice(space + 1).trim();
|
|
264
|
-
const { args, options, refreshDiscovery, optionErrors } = parseWorkflowOptions(rest);
|
|
265
|
-
const invocation: WorkflowInvocation = { name, args, options };
|
|
266
|
-
if (refreshDiscovery) invocation.refreshDiscovery = refreshDiscovery;
|
|
267
|
-
if (optionErrors) invocation.optionErrors = optionErrors;
|
|
268
|
-
return invocation;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
const INVALID_BUDGET_OPTION = "--budget requires a positive integer output-token count";
|
|
272
|
-
const INVALID_RESUME_OPTION = "--resume requires a workflow run id";
|
|
273
|
-
const INVALID_MAX_AGENTS_OPTION = "--max-agents requires an integer";
|
|
274
|
-
const INVALID_AGENT_TIMEOUT_OPTION = "--agent-timeout-ms requires an integer";
|
|
275
|
-
const INVALID_AGENT_RETRIES_OPTION = "--agent-retries requires an integer";
|
|
276
|
-
const INVALID_EDITED_RESUME_OPTION = "--resume-edited requires --resume <run-id>";
|
|
277
|
-
|
|
278
|
-
function parseWorkflowOptions(input: string): { args: string; options: WorkflowRunOptions; refreshDiscovery?: boolean; optionErrors?: string[] } {
|
|
279
|
-
const tokens = input.split(/\s+/).filter(Boolean);
|
|
280
|
-
const kept: string[] = [];
|
|
281
|
-
const options: WorkflowRunOptions = {};
|
|
282
|
-
const optionErrors: string[] = [];
|
|
283
|
-
let refreshDiscovery = false;
|
|
284
|
-
for (let i = 0; i < tokens.length; i++) {
|
|
285
|
-
const token = tokens[i];
|
|
286
|
-
if (token === "--inspect") {
|
|
287
|
-
options.inspect = true;
|
|
288
|
-
continue;
|
|
289
|
-
}
|
|
290
|
-
if (token === "--refresh") {
|
|
291
|
-
refreshDiscovery = true;
|
|
292
|
-
continue;
|
|
293
|
-
}
|
|
294
|
-
if (token === "--perf") {
|
|
295
|
-
options.perf = true;
|
|
296
|
-
continue;
|
|
297
|
-
}
|
|
298
|
-
if (token === "--resume-edited") {
|
|
299
|
-
options.resumeEditedWorkflow = true;
|
|
300
|
-
continue;
|
|
301
|
-
}
|
|
302
|
-
if (token === "--result-viewer" || token === "--review-viewer") {
|
|
303
|
-
options.resultViewer = "open";
|
|
304
|
-
continue;
|
|
305
|
-
}
|
|
306
|
-
if (token === "--no-result-viewer" || token === "--no-review-viewer") {
|
|
307
|
-
options.resultViewer = "skip";
|
|
308
|
-
continue;
|
|
309
|
-
}
|
|
310
|
-
if (token.startsWith("--concurrency=")) {
|
|
311
|
-
options.concurrency = parseNumericOption(token.slice("--concurrency=".length));
|
|
312
|
-
continue;
|
|
313
|
-
}
|
|
314
|
-
if (token === "--concurrency") {
|
|
315
|
-
const next = tokens[i + 1];
|
|
316
|
-
options.concurrency = parseNumericOption(next);
|
|
317
|
-
if (next !== undefined) i++;
|
|
318
|
-
continue;
|
|
319
|
-
}
|
|
320
|
-
if (token.startsWith("--parallel-limit=")) {
|
|
321
|
-
options.parallelSubmissionLimit = parseNumericOption(token.slice("--parallel-limit=".length));
|
|
322
|
-
continue;
|
|
323
|
-
}
|
|
324
|
-
if (token === "--parallel-limit") {
|
|
325
|
-
const next = tokens[i + 1];
|
|
326
|
-
options.parallelSubmissionLimit = parseNumericOption(next);
|
|
327
|
-
if (next !== undefined) i++;
|
|
328
|
-
continue;
|
|
329
|
-
}
|
|
330
|
-
if (token.startsWith("--max-agents=")) {
|
|
331
|
-
const parsed = parseWorkflowIntegerString(token.slice("--max-agents=".length));
|
|
332
|
-
if (parsed === undefined) optionErrors.push(INVALID_MAX_AGENTS_OPTION);
|
|
333
|
-
else options.maxAgents = parsed;
|
|
334
|
-
continue;
|
|
335
|
-
}
|
|
336
|
-
if (token === "--max-agents") {
|
|
337
|
-
const next = tokens[i + 1];
|
|
338
|
-
const parsed = parseWorkflowIntegerString(next);
|
|
339
|
-
if (parsed === undefined) optionErrors.push(INVALID_MAX_AGENTS_OPTION);
|
|
340
|
-
else {
|
|
341
|
-
options.maxAgents = parsed;
|
|
342
|
-
i++;
|
|
343
|
-
}
|
|
344
|
-
continue;
|
|
345
|
-
}
|
|
346
|
-
if (token.startsWith("--agent-timeout-ms=")) {
|
|
347
|
-
const parsed = parseWorkflowIntegerString(token.slice("--agent-timeout-ms=".length));
|
|
348
|
-
if (parsed === undefined) optionErrors.push(INVALID_AGENT_TIMEOUT_OPTION);
|
|
349
|
-
else options.agentTimeoutMs = parsed;
|
|
350
|
-
continue;
|
|
351
|
-
}
|
|
352
|
-
if (token === "--agent-timeout-ms") {
|
|
353
|
-
const next = tokens[i + 1];
|
|
354
|
-
const parsed = parseWorkflowIntegerString(next);
|
|
355
|
-
if (parsed === undefined) optionErrors.push(INVALID_AGENT_TIMEOUT_OPTION);
|
|
356
|
-
else {
|
|
357
|
-
options.agentTimeoutMs = parsed;
|
|
358
|
-
i++;
|
|
359
|
-
}
|
|
360
|
-
continue;
|
|
361
|
-
}
|
|
362
|
-
if (token.startsWith("--agent-retries=")) {
|
|
363
|
-
const parsed = parseWorkflowIntegerString(token.slice("--agent-retries=".length));
|
|
364
|
-
if (parsed === undefined) optionErrors.push(INVALID_AGENT_RETRIES_OPTION);
|
|
365
|
-
else options.agentRetries = parsed;
|
|
366
|
-
continue;
|
|
367
|
-
}
|
|
368
|
-
if (token === "--agent-retries") {
|
|
369
|
-
const next = tokens[i + 1];
|
|
370
|
-
const parsed = parseWorkflowIntegerString(next);
|
|
371
|
-
if (parsed === undefined) optionErrors.push(INVALID_AGENT_RETRIES_OPTION);
|
|
372
|
-
else {
|
|
373
|
-
options.agentRetries = parsed;
|
|
374
|
-
i++;
|
|
375
|
-
}
|
|
376
|
-
continue;
|
|
377
|
-
}
|
|
378
|
-
if (token.startsWith("--budget=")) {
|
|
379
|
-
const parsed = parseBudgetOption(token.slice("--budget=".length));
|
|
380
|
-
if (parsed === undefined) optionErrors.push(INVALID_BUDGET_OPTION);
|
|
381
|
-
else options.budget = parsed;
|
|
382
|
-
continue;
|
|
383
|
-
}
|
|
384
|
-
if (token === "--budget") {
|
|
385
|
-
const next = tokens[i + 1];
|
|
386
|
-
const parsed = next === undefined ? undefined : parseBudgetOption(next);
|
|
387
|
-
if (parsed === undefined) {
|
|
388
|
-
optionErrors.push(INVALID_BUDGET_OPTION);
|
|
389
|
-
} else {
|
|
390
|
-
options.budget = parsed;
|
|
391
|
-
i++;
|
|
392
|
-
}
|
|
393
|
-
continue;
|
|
394
|
-
}
|
|
395
|
-
if (token.startsWith("--resume=")) {
|
|
396
|
-
const value = token.slice("--resume=".length).trim();
|
|
397
|
-
if (value === "") optionErrors.push(INVALID_RESUME_OPTION);
|
|
398
|
-
else options.resumeFromRunId = value;
|
|
399
|
-
continue;
|
|
400
|
-
}
|
|
401
|
-
if (token === "--resume") {
|
|
402
|
-
const next = tokens[i + 1];
|
|
403
|
-
if (next === undefined || next.startsWith("--")) {
|
|
404
|
-
optionErrors.push(INVALID_RESUME_OPTION);
|
|
405
|
-
} else {
|
|
406
|
-
options.resumeFromRunId = next;
|
|
407
|
-
i++;
|
|
408
|
-
}
|
|
409
|
-
continue;
|
|
410
|
-
}
|
|
411
|
-
kept.push(token);
|
|
412
|
-
}
|
|
413
|
-
if (options.resumeEditedWorkflow && !options.resumeFromRunId) optionErrors.push(INVALID_EDITED_RESUME_OPTION);
|
|
414
|
-
return { args: kept.join(" ").trim(), options, refreshDiscovery: refreshDiscovery || undefined, optionErrors: optionErrors.length > 0 ? optionErrors : undefined };
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
function parseBudgetOption(value: string): number | undefined {
|
|
418
|
-
return parseWorkflowBudgetString(value);
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
function parseNumericOption(value: string | undefined): number | undefined {
|
|
422
|
-
if (value === undefined) return undefined;
|
|
423
|
-
const parsed = Number(value);
|
|
424
|
-
return Number.isFinite(parsed) ? parsed : undefined;
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
function compactInlinePreview(script: string | undefined): string {
|
|
428
|
-
if (!script) return "";
|
|
429
|
-
const compact = script.replace(/\s+/g, " ").trim();
|
|
430
|
-
return compact.length > 60 ? `${compact.slice(0, 57)}…` : compact;
|
|
189
|
+
return { name, args, startedAt: Date.now(), snapshot: () => source.snapshot(), state: source.state };
|
|
431
190
|
}
|
|
432
191
|
|
|
433
192
|
export function buildTemporaryWorkflowAuthorPrompt(brief: string): string {
|
|
@@ -484,27 +243,30 @@ export function inlineCompileErrorResult(message: string): WorkflowToolErrorResu
|
|
|
484
243
|
return { content: [{ type: "text", text: `Inline workflow did not compile: ${message}` }], details: { error: "inline_compile_error", message } };
|
|
485
244
|
}
|
|
486
245
|
|
|
246
|
+
export type WorkflowPickerSelection =
|
|
247
|
+
| { readonly kind: "run"; readonly name: string; readonly args: string }
|
|
248
|
+
| { readonly kind: "author"; readonly brief: string };
|
|
249
|
+
|
|
487
250
|
export async function pickWorkflow(
|
|
488
251
|
workflows: ReadonlyMap<string, WorkflowModule>,
|
|
489
252
|
ctx: ExtensionCommandContext,
|
|
490
|
-
): Promise<
|
|
491
|
-
const
|
|
492
|
-
if (!
|
|
253
|
+
): Promise<WorkflowPickerSelection | undefined> {
|
|
254
|
+
const name = await selectWorkflowValue(workflows, ctx);
|
|
255
|
+
if (!name) return undefined;
|
|
493
256
|
|
|
494
|
-
if (
|
|
257
|
+
if (name === AUTHOR_TEMP_WORKFLOW_VALUE) {
|
|
495
258
|
const brief = await ctx.ui.editor(
|
|
496
259
|
"Describe temporary workflow",
|
|
497
260
|
"Goal:\n\nAgents to run:\n- \n\nFinal output should include:\n- summary\n- findings\n- next steps\n",
|
|
498
261
|
);
|
|
499
262
|
const trimmed = brief?.trim();
|
|
500
|
-
|
|
501
|
-
return { name: "", args: "", options: {}, authorBrief: trimmed };
|
|
263
|
+
return trimmed ? { kind: "author", brief: trimmed } : undefined;
|
|
502
264
|
}
|
|
503
265
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
const
|
|
507
|
-
return { name, args
|
|
266
|
+
if (name !== "code-review") return { kind: "run", name, args: "" };
|
|
267
|
+
// Escape resolves undefined and cancels; only a submitted blank target means auto-detect.
|
|
268
|
+
const target = await ctx.ui.input("Code-review target/instructions", "Blank = auto-detect diff");
|
|
269
|
+
return target === undefined ? undefined : { kind: "run", name, args: target.trim() };
|
|
508
270
|
}
|
|
509
271
|
|
|
510
272
|
export async function sendWorkflowResult(
|
|
@@ -513,36 +275,14 @@ export async function sendWorkflowResult(
|
|
|
513
275
|
name: string,
|
|
514
276
|
mod: LoadedWorkflow,
|
|
515
277
|
args: string,
|
|
516
|
-
options:
|
|
278
|
+
options: ResolvedWorkflowRunOptions,
|
|
517
279
|
perfRecorder?: PerfSink,
|
|
518
280
|
reviewSessions: ReviewSessionCoordinator = createReviewSessionCoordinator(pi),
|
|
519
281
|
): Promise<void> {
|
|
520
|
-
await
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
mod,
|
|
525
|
-
args,
|
|
526
|
-
resolveWorkflowRunOptions(options),
|
|
527
|
-
perfRecorder,
|
|
528
|
-
reviewSessions,
|
|
529
|
-
);
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
async function sendResolvedWorkflowResult(
|
|
533
|
-
pi: ExtensionAPI,
|
|
534
|
-
ctx: ExtensionContext,
|
|
535
|
-
name: string,
|
|
536
|
-
mod: LoadedWorkflow,
|
|
537
|
-
args: string,
|
|
538
|
-
options: ResolvedWorkflowRunOptions,
|
|
539
|
-
perfRecorder: PerfSink | undefined,
|
|
540
|
-
reviewSessions: ReviewSessionCoordinator,
|
|
541
|
-
): Promise<void> {
|
|
542
|
-
const execution = await executeResolvedWorkflow(pi, ctx, name, mod, args, options, perfRecorder);
|
|
543
|
-
reviewSessions.remember(ctx, execution, options);
|
|
544
|
-
sendWorkflowExecution(pi, execution);
|
|
545
|
-
await reviewSessions.present(ctx, execution, options);
|
|
282
|
+
const envelope = await executeResolvedWorkflow(pi, ctx, name, mod, args, options, perfRecorder);
|
|
283
|
+
const retained = reviewSessions.remember(ctx, envelope, options);
|
|
284
|
+
publishWorkflowResult(pi, envelope);
|
|
285
|
+
await reviewSessions.present(ctx, retained, options);
|
|
546
286
|
}
|
|
547
287
|
|
|
548
288
|
async function executeResolvedWorkflow(
|
|
@@ -553,7 +293,7 @@ async function executeResolvedWorkflow(
|
|
|
553
293
|
args: string,
|
|
554
294
|
options: ResolvedWorkflowRunOptions,
|
|
555
295
|
perfRecorder?: PerfSink,
|
|
556
|
-
): Promise<
|
|
296
|
+
): Promise<WorkflowResultEnvelope> {
|
|
557
297
|
const { runResolvedWorkflow } = await loadEngine();
|
|
558
298
|
let liveInspection: ActiveWorkflowInspection | undefined;
|
|
559
299
|
const inspections = workflowInspectionState(pi, ctx);
|
|
@@ -570,35 +310,25 @@ async function executeResolvedWorkflow(
|
|
|
570
310
|
if (source) {
|
|
571
311
|
liveInspection = bindActiveWorkflowInspection(name, args, source);
|
|
572
312
|
inspections.active = liveInspection;
|
|
573
|
-
latestActiveWorkflowInspection = liveInspection;
|
|
574
313
|
} else if (inspections.active === liveInspection) {
|
|
575
314
|
inspections.active = undefined;
|
|
576
|
-
if (latestActiveWorkflowInspection === liveInspection) latestActiveWorkflowInspection = undefined;
|
|
577
315
|
liveInspection = undefined;
|
|
578
316
|
}
|
|
579
317
|
},
|
|
580
318
|
onProgressSnapshot(snapshot) {
|
|
581
|
-
|
|
582
|
-
inspections.last =
|
|
583
|
-
latestWorkflowInspection = completed;
|
|
319
|
+
// The durable state settles after this final snapshot; keep reading it from the live source.
|
|
320
|
+
inspections.last = { name, args, completedAt: snapshot.doneAt ?? Date.now(), snapshot, state: liveInspection?.state };
|
|
584
321
|
},
|
|
585
322
|
});
|
|
586
323
|
}
|
|
587
324
|
|
|
588
|
-
function
|
|
589
|
-
const { name } = execution.envelope;
|
|
325
|
+
function publishWorkflowResult(pi: ExtensionAPI, envelope: WorkflowResultEnvelope): void {
|
|
590
326
|
pi.sendMessage(
|
|
591
327
|
{
|
|
592
328
|
customType: "workflow-result",
|
|
593
|
-
content:
|
|
594
|
-
name,
|
|
595
|
-
execution.envelope.result,
|
|
596
|
-
execution.envelope.usage,
|
|
597
|
-
execution.envelope.perf,
|
|
598
|
-
execution.metadata,
|
|
599
|
-
),
|
|
329
|
+
content: formatWorkflowResultForContext(envelope),
|
|
600
330
|
display: true,
|
|
601
|
-
details:
|
|
331
|
+
details: envelope,
|
|
602
332
|
},
|
|
603
333
|
{ triggerTurn: false },
|
|
604
334
|
);
|
|
@@ -610,7 +340,7 @@ function createReviewSessionCoordinator(pi: ExtensionAPI): ReviewSessionCoordina
|
|
|
610
340
|
const perfRecorder = await createInvocationPerf(options);
|
|
611
341
|
return await executeResolvedWorkflow(pi, ctx, workflow.meta.name, workflow, "", options, perfRecorder);
|
|
612
342
|
},
|
|
613
|
-
publish: (
|
|
343
|
+
publish: (envelope) => publishWorkflowResult(pi, envelope),
|
|
614
344
|
});
|
|
615
345
|
}
|
|
616
346
|
|
|
@@ -625,12 +355,12 @@ export default function workflowEngine(pi: ExtensionAPI, shortcuts: DynamaxShort
|
|
|
625
355
|
},
|
|
626
356
|
async execute(ctx, name, workflow, options) {
|
|
627
357
|
const perfRecorder = await createInvocationPerf(options);
|
|
628
|
-
const
|
|
629
|
-
reviewSessions.remember(ctx,
|
|
358
|
+
const envelope = await executeResolvedWorkflow(pi, ctx, name, workflow, "", options, perfRecorder);
|
|
359
|
+
reviewSessions.remember(ctx, envelope, options);
|
|
630
360
|
},
|
|
631
361
|
});
|
|
632
362
|
backgroundWorkflows.onRunSettled((ctx, runId) => workflowRuns.runSettled(ctx, runId));
|
|
633
|
-
registerDynamax(pi, shortcuts, { openInspector: (ctx) => openAvailableWorkflowInspector(pi, ctx) });
|
|
363
|
+
const dynamax = registerDynamax(pi, shortcuts, { openInspector: (ctx) => openAvailableWorkflowInspector(pi, ctx) });
|
|
634
364
|
registerWorkflowModelProfileCommand(pi);
|
|
635
365
|
registerWorkflowRunCommand(pi, workflowRuns);
|
|
636
366
|
pi.on("session_start", async (_event, ctx) => {
|
|
@@ -658,13 +388,8 @@ export default function workflowEngine(pi: ExtensionAPI, shortcuts: DynamaxShort
|
|
|
658
388
|
|
|
659
389
|
pi.registerMessageRenderer("workflow-result", (message, { expanded }, theme) => {
|
|
660
390
|
const details = message.details;
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
runId: details.runId,
|
|
664
|
-
resumedFromRunId: details.resumedFromRunId,
|
|
665
|
-
}, details.perf);
|
|
666
|
-
}
|
|
667
|
-
return renderWorkflowResult("workflow", details ?? message.content, expanded, theme);
|
|
391
|
+
const view = isWorkflowResult(details) ? details : { name: "workflow", result: details ?? message.content };
|
|
392
|
+
return renderWorkflowResult(view, expanded, theme, workflowResultHints(shortcuts));
|
|
668
393
|
});
|
|
669
394
|
|
|
670
395
|
pi.registerCommand("workflow:inspector", {
|
|
@@ -697,40 +422,54 @@ export default function workflowEngine(pi: ExtensionAPI, shortcuts: DynamaxShort
|
|
|
697
422
|
description: "Run a multi-agent workflow: /workflow <name> [args]",
|
|
698
423
|
getArgumentCompletions: workflowArgumentCompletions,
|
|
699
424
|
handler: async (args: string, ctx: ExtensionCommandContext) => {
|
|
700
|
-
const
|
|
701
|
-
if (
|
|
702
|
-
ctx.ui.notify(`Invalid workflow option: ${
|
|
425
|
+
const invocation = parseWorkflowInvocation(args);
|
|
426
|
+
if (invocation.optionErrors.length > 0) {
|
|
427
|
+
ctx.ui.notify(`Invalid workflow option: ${invocation.optionErrors.join("; ")}`, "warning");
|
|
703
428
|
return;
|
|
704
429
|
}
|
|
705
|
-
const
|
|
706
|
-
const perfRecorder = await createInvocationPerf(
|
|
430
|
+
const options = resolveWorkflowRunOptions(invocation.options);
|
|
431
|
+
const perfRecorder = await createInvocationPerf(options);
|
|
707
432
|
const { discoverWorkflows } = await loadDiscovery();
|
|
708
|
-
const workflows = await discoverWorkflows(EXTENSION_DIR, { refresh:
|
|
433
|
+
const workflows = await discoverWorkflows(EXTENSION_DIR, { refresh: invocation.refreshDiscovery, perf: perfRecorder });
|
|
709
434
|
const available = [...workflows.keys()].join(", ") || "(none)";
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
if (!invocation) {
|
|
435
|
+
if (!invocation.name && !ctx.hasUI) {
|
|
713
436
|
ctx.ui.notify(`Usage: /workflow <name> [args]. Available: ${available}`, "warning");
|
|
714
437
|
return;
|
|
715
438
|
}
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
439
|
+
const selection: WorkflowPickerSelection | undefined = invocation.name
|
|
440
|
+
? { kind: "run", name: invocation.name, args: invocation.args }
|
|
441
|
+
: await pickWorkflow(workflows, ctx);
|
|
442
|
+
// Dismissing the picker or one of its follow-up prompts is a deliberate cancel, not a usage error.
|
|
443
|
+
if (!selection) return;
|
|
444
|
+
|
|
445
|
+
if (selection.kind === "author") {
|
|
446
|
+
// A send while the agent streams is rejected, which would leave the one-shot armed for an unrelated prompt.
|
|
447
|
+
await ctx.waitForIdle();
|
|
448
|
+
dynamax.markOneShot(ctx);
|
|
449
|
+
pi.sendUserMessage(buildTemporaryWorkflowAuthorPrompt(selection.brief));
|
|
719
450
|
return;
|
|
720
451
|
}
|
|
721
452
|
|
|
722
|
-
const mod = workflows.get(
|
|
453
|
+
const mod = workflows.get(selection.name);
|
|
723
454
|
if (!mod) {
|
|
724
|
-
ctx.ui.notify(`Unknown workflow "${
|
|
455
|
+
ctx.ui.notify(`Unknown workflow "${selection.name}". Available: ${available}`, "error");
|
|
725
456
|
return;
|
|
726
457
|
}
|
|
727
458
|
|
|
728
|
-
|
|
729
|
-
await sendResolvedWorkflowResult(pi, ctx, invocation.name, mod, invocation.args, effectiveOptions, perfRecorder, reviewSessions);
|
|
459
|
+
await sendWorkflowResult(pi, ctx, selection.name, mod, selection.args, options, perfRecorder, reviewSessions);
|
|
730
460
|
},
|
|
731
461
|
});
|
|
732
462
|
|
|
733
|
-
registerWorkflowTool(pi, reviewSessions, backgroundWorkflows);
|
|
463
|
+
registerWorkflowTool(pi, reviewSessions, backgroundWorkflows, shortcuts);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/** Result footers advertise the configured triage and inspector shortcuts and pi's current expand key, read at render time. */
|
|
467
|
+
function workflowResultHints(shortcuts: DynamaxShortcuts): WorkflowResultHints {
|
|
468
|
+
return {
|
|
469
|
+
triage: shortcuts.results ?? "/workflow:results",
|
|
470
|
+
inspect: shortcuts.inspector ?? "/workflow:inspector",
|
|
471
|
+
expand: keyText("app.tools.expand") || undefined,
|
|
472
|
+
};
|
|
734
473
|
}
|
|
735
474
|
|
|
736
475
|
/** Register the host-facing workflow tool independently from command and lifecycle surfaces. */
|
|
@@ -738,9 +477,10 @@ function registerWorkflowTool(
|
|
|
738
477
|
pi: ExtensionAPI,
|
|
739
478
|
reviewSessions: ReviewSessionCoordinator,
|
|
740
479
|
backgroundWorkflows: BackgroundWorkflowCoordinator,
|
|
480
|
+
shortcuts: DynamaxShortcuts,
|
|
741
481
|
): void {
|
|
742
482
|
pi.registerTool({
|
|
743
|
-
name:
|
|
483
|
+
name: WORKFLOW_TOOL_NAME,
|
|
744
484
|
label: "Workflow",
|
|
745
485
|
description:
|
|
746
486
|
"ONLY call workflow when the user opted into multi-agent orchestration via the literal token `dynamax`, sticky `/workflow:dynamax on`, an explicit request to run or author a workflow, or a command/skill instruction. Runs either a registered named workflow or an inline one-off workflow script (fan-out → verify → synthesize), synchronously by default or explicitly in the background.",
|
|
@@ -780,7 +520,7 @@ function registerWorkflowTool(
|
|
|
780
520
|
),
|
|
781
521
|
agentRetries: Type.Optional(
|
|
782
522
|
Type.Integer({
|
|
783
|
-
description: `
|
|
523
|
+
description: `Whole-agent restarts after a transient provider failure that pi's in-session turn retry (the user's pi retry settings) could not recover; clamped to ${WORKFLOW_AGENT_RETRIES_MIN}-${WORKFLOW_AGENT_RETRIES_MAX}`,
|
|
784
524
|
}),
|
|
785
525
|
),
|
|
786
526
|
autoResumeOnUsageLimit: Type.Optional(
|
|
@@ -815,29 +555,12 @@ function registerWorkflowTool(
|
|
|
815
555
|
background: Type.Optional(Type.Boolean({ description: "Return a durable run ID immediately and deliver completion to this conversation later" })),
|
|
816
556
|
}),
|
|
817
557
|
renderCall(args, theme) {
|
|
818
|
-
|
|
819
|
-
const background = args.background ? ` ${theme.fg("dim", "(background)")}` : "";
|
|
820
|
-
if (args.name?.trim()) {
|
|
821
|
-
return new Text(`▸ ${theme.fg("toolTitle", theme.bold("workflow"))} ${theme.fg("accent", args.name.trim())}${background}${suffix}`, 0, 0);
|
|
822
|
-
}
|
|
823
|
-
const preview = compactInlinePreview(args.script);
|
|
824
|
-
const previewSuffix = preview ? ` ${theme.fg("dim", preview)}` : "";
|
|
825
|
-
return new Text(`▸ ${theme.fg("toolTitle", theme.bold("workflow"))} ${theme.fg("accent", "inline")}${background}${suffix}${previewSuffix}`, 0, 0);
|
|
558
|
+
return renderWorkflowToolCall(args, theme);
|
|
826
559
|
},
|
|
827
|
-
renderResult(result,
|
|
828
|
-
|
|
829
|
-
const details = result.details;
|
|
830
|
-
if (isWorkflowResult(details)) {
|
|
831
|
-
return renderWorkflowResult(details.name, details.result, expanded, theme, details.usage, {
|
|
832
|
-
runId: details.runId,
|
|
833
|
-
resumedFromRunId: details.resumedFromRunId,
|
|
834
|
-
}, details.perf);
|
|
835
|
-
}
|
|
836
|
-
const first = result.content[0];
|
|
837
|
-
const text = first?.type === "text" ? first.text : "Workflow finished.";
|
|
838
|
-
return new Text(theme.fg("muted", text), 0, 0);
|
|
560
|
+
renderResult(result, options, theme, context) {
|
|
561
|
+
return renderWorkflowToolResult(result, options, theme, context.args, workflowResultHints(shortcuts));
|
|
839
562
|
},
|
|
840
|
-
async execute(_toolCallId, params, signal,
|
|
563
|
+
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
841
564
|
const request = normalizeWorkflowToolRequest(params);
|
|
842
565
|
if (request.kind === "error") return invalidWorkflowInvocationResult();
|
|
843
566
|
const resumeFromRunId = params.resumeFromRunId?.trim();
|
|
@@ -859,7 +582,6 @@ function registerWorkflowTool(
|
|
|
859
582
|
}
|
|
860
583
|
|
|
861
584
|
const runOptions = resolveWorkflowRunOptions({
|
|
862
|
-
inspect: ctx.hasUI && ctx.mode === "tui",
|
|
863
585
|
concurrency: params.concurrency,
|
|
864
586
|
parallelSubmissionLimit: params.parallelSubmissionLimit,
|
|
865
587
|
maxAgents: params.maxAgents,
|
|
@@ -910,7 +632,7 @@ function registerWorkflowTool(
|
|
|
910
632
|
name: resultName,
|
|
911
633
|
options: runOptions,
|
|
912
634
|
async execute(backgroundCtx, backgroundOptions) {
|
|
913
|
-
const
|
|
635
|
+
const envelope = await executeResolvedWorkflow(
|
|
914
636
|
pi,
|
|
915
637
|
backgroundCtx,
|
|
916
638
|
resultName,
|
|
@@ -919,24 +641,17 @@ function registerWorkflowTool(
|
|
|
919
641
|
backgroundOptions,
|
|
920
642
|
perfRecorder,
|
|
921
643
|
);
|
|
922
|
-
reviewSessions.remember(ctx,
|
|
644
|
+
reviewSessions.remember(ctx, envelope, backgroundOptions);
|
|
923
645
|
},
|
|
924
646
|
});
|
|
925
647
|
}
|
|
926
|
-
|
|
927
|
-
|
|
648
|
+
// Marks the tool row as running (rendered `● running <name>`) until the envelope replaces it.
|
|
649
|
+
onUpdate?.({ content: [{ type: "text", text: `Running workflow ${resultName}.` }], details: { state: "running", name: resultName } });
|
|
650
|
+
const envelope = await executeResolvedWorkflow(pi, ctx, resultName, mod, resultArgs, runOptions, perfRecorder);
|
|
651
|
+
reviewSessions.remember(ctx, envelope, runOptions);
|
|
928
652
|
return {
|
|
929
|
-
content: [{
|
|
930
|
-
|
|
931
|
-
text: formatMessageContent(
|
|
932
|
-
resultName,
|
|
933
|
-
execution.envelope.result,
|
|
934
|
-
execution.envelope.usage,
|
|
935
|
-
execution.envelope.perf,
|
|
936
|
-
execution.metadata,
|
|
937
|
-
),
|
|
938
|
-
}],
|
|
939
|
-
details: execution.envelope,
|
|
653
|
+
content: [{ type: "text", text: formatWorkflowResultForContext(envelope) }],
|
|
654
|
+
details: envelope,
|
|
940
655
|
};
|
|
941
656
|
},
|
|
942
657
|
});
|