@jameslovespancakes/pi-plus 1.0.11 → 1.0.13

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.
Files changed (133) hide show
  1. package/README.md +67 -22
  2. package/config/skills/workflow-code-review-actions/SKILL.md +44 -0
  3. package/package.json +9 -4
  4. package/src/core/accounts/oauth-pool.ts +137 -0
  5. package/src/core/accounts/registry.ts +12 -25
  6. package/src/core/accounts/routing.ts +144 -0
  7. package/src/core/anthropic/client-identity.ts +9 -64
  8. package/src/core/anthropic/identity.ts +54 -0
  9. package/src/core/anthropic/oauth.ts +3 -0
  10. package/src/core/anthropic/quota.ts +232 -78
  11. package/src/core/anthropic/routing.ts +40 -115
  12. package/src/core/anthropic/store.ts +11 -10
  13. package/src/core/catalog/quality.ts +33 -15
  14. package/src/core/codex/quota.ts +46 -8
  15. package/src/core/codex/store.ts +29 -16
  16. package/src/core/config.ts +16 -17
  17. package/src/core/store.ts +13 -12
  18. package/src/domains/agents/format.ts +97 -0
  19. package/src/domains/agents/index.ts +58 -47
  20. package/src/domains/compact/archive.ts +140 -0
  21. package/src/domains/compact/chunking.ts +166 -0
  22. package/src/domains/compact/index.ts +452 -0
  23. package/src/domains/compact/jev.ts +242 -0
  24. package/src/domains/compact/policy.ts +255 -0
  25. package/src/domains/compact/types.ts +79 -0
  26. package/src/domains/models/provider-picker.ts +1 -1
  27. package/src/domains/setup/index.ts +23 -23
  28. package/src/domains/subscriptions/accounts-picker.ts +61 -40
  29. package/src/domains/subscriptions/accounts.ts +53 -36
  30. package/src/domains/subscriptions/footer.ts +48 -29
  31. package/src/domains/subscriptions/index.ts +11 -27
  32. package/src/domains/subscriptions/provider.ts +79 -103
  33. package/src/domains/subscriptions/providers/anthropic.ts +42 -22
  34. package/src/domains/subscriptions/providers/codex.ts +146 -113
  35. package/src/domains/subscriptions/providers/hosted.ts +18 -0
  36. package/src/domains/subscriptions/providers/oauth-pool.ts +325 -0
  37. package/src/domains/subscriptions/routing.ts +19 -11
  38. package/src/domains/workflows/LICENSE.md +21 -0
  39. package/src/domains/workflows/index.ts +836 -0
  40. package/src/domains/workflows/runtime/advisory-challenge.ts +75 -0
  41. package/src/domains/workflows/runtime/advisory-evidence.ts +90 -0
  42. package/src/domains/workflows/runtime/advisory-schema.ts +83 -0
  43. package/src/domains/workflows/runtime/agent-attempt.ts +177 -0
  44. package/src/domains/workflows/runtime/agent-failure.ts +14 -0
  45. package/src/domains/workflows/runtime/agent-limits.ts +66 -0
  46. package/src/domains/workflows/runtime/agent-replay.ts +399 -0
  47. package/src/domains/workflows/runtime/agent-retry.ts +116 -0
  48. package/src/domains/workflows/runtime/agent-runner-types.ts +96 -0
  49. package/src/domains/workflows/runtime/agent-runner.ts +225 -0
  50. package/src/domains/workflows/runtime/agent-session-identity.ts +256 -0
  51. package/src/domains/workflows/runtime/agent-session-providers.ts +50 -0
  52. package/src/domains/workflows/runtime/agent-session.ts +382 -0
  53. package/src/domains/workflows/runtime/agent-skills.ts +270 -0
  54. package/src/domains/workflows/runtime/agent-workspace.ts +79 -0
  55. package/src/domains/workflows/runtime/background-workflow-tool.ts +75 -0
  56. package/src/domains/workflows/runtime/background-workflows.ts +492 -0
  57. package/src/domains/workflows/runtime/budget.ts +53 -0
  58. package/src/domains/workflows/runtime/cancellation.ts +87 -0
  59. package/src/domains/workflows/runtime/command-completions.ts +36 -0
  60. package/src/domains/workflows/runtime/concurrency.ts +403 -0
  61. package/src/domains/workflows/runtime/debug.ts +3 -0
  62. package/src/domains/workflows/runtime/diff-capture.ts +81 -0
  63. package/src/domains/workflows/runtime/discovery.ts +137 -0
  64. package/src/domains/workflows/runtime/dynamax-shortcuts.ts +122 -0
  65. package/src/domains/workflows/runtime/dynamax.ts +330 -0
  66. package/src/domains/workflows/runtime/engine.ts +579 -0
  67. package/src/domains/workflows/runtime/filesystem-error.ts +4 -0
  68. package/src/domains/workflows/runtime/finalizers.ts +66 -0
  69. package/src/domains/workflows/runtime/identity-canonicalization.ts +138 -0
  70. package/src/domains/workflows/runtime/identity-fingerprint.ts +15 -0
  71. package/src/domains/workflows/runtime/inline-workflow.ts +403 -0
  72. package/src/domains/workflows/runtime/journal.ts +313 -0
  73. package/src/domains/workflows/runtime/model-profiles.ts +310 -0
  74. package/src/domains/workflows/runtime/options.ts +157 -0
  75. package/src/domains/workflows/runtime/perf.ts +146 -0
  76. package/src/domains/workflows/runtime/pi-compat.ts +32 -0
  77. package/src/domains/workflows/runtime/process-runner.ts +260 -0
  78. package/src/domains/workflows/runtime/progress-types.ts +59 -0
  79. package/src/domains/workflows/runtime/progress.ts +400 -0
  80. package/src/domains/workflows/runtime/provider-usage-limit.ts +191 -0
  81. package/src/domains/workflows/runtime/replay-path-identity.ts +48 -0
  82. package/src/domains/workflows/runtime/research-contract.ts +89 -0
  83. package/src/domains/workflows/runtime/research-evidence.ts +289 -0
  84. package/src/domains/workflows/runtime/resume-context.ts +751 -0
  85. package/src/domains/workflows/runtime/review/code-review-orchestration.ts +24 -0
  86. package/src/domains/workflows/runtime/review/github-pr-comments.ts +249 -0
  87. package/src/domains/workflows/runtime/review/patch-validation.ts +120 -0
  88. package/src/domains/workflows/runtime/review/review-actions.ts +180 -0
  89. package/src/domains/workflows/runtime/review/review-budget.ts +45 -0
  90. package/src/domains/workflows/runtime/review/review-fix-workflow.ts +153 -0
  91. package/src/domains/workflows/runtime/review/review-format.ts +133 -0
  92. package/src/domains/workflows/runtime/review/review-handoff.ts +38 -0
  93. package/src/domains/workflows/runtime/review/review-issues.ts +87 -0
  94. package/src/domains/workflows/runtime/review/review-report.ts +42 -0
  95. package/src/domains/workflows/runtime/review/review-results-flow.ts +52 -0
  96. package/src/domains/workflows/runtime/review/review-results-viewer.ts +312 -0
  97. package/src/domains/workflows/runtime/review/review-session-coordinator.ts +153 -0
  98. package/src/domains/workflows/runtime/review/review-snapshot.ts +294 -0
  99. package/src/domains/workflows/runtime/review-diff-target.ts +130 -0
  100. package/src/domains/workflows/runtime/session-identity.ts +6 -0
  101. package/src/domains/workflows/runtime/structured-output.ts +32 -0
  102. package/src/domains/workflows/runtime/tool-capabilities.ts +44 -0
  103. package/src/domains/workflows/runtime/tool-source-identity.ts +150 -0
  104. package/src/domains/workflows/runtime/tree-fingerprint.ts +404 -0
  105. package/src/domains/workflows/runtime/types.ts +255 -0
  106. package/src/domains/workflows/runtime/ui/display-text.ts +28 -0
  107. package/src/domains/workflows/runtime/ui/dynamax-editor-decoration.ts +220 -0
  108. package/src/domains/workflows/runtime/ui/workflow-format.ts +165 -0
  109. package/src/domains/workflows/runtime/ui/workflow-inspector.ts +435 -0
  110. package/src/domains/workflows/runtime/ui/workflow-result-renderer.ts +171 -0
  111. package/src/domains/workflows/runtime/ui/workflow-viewer-layout.ts +51 -0
  112. package/src/domains/workflows/runtime/ui/workflow-widget.ts +78 -0
  113. package/src/domains/workflows/runtime/unknown-error.ts +8 -0
  114. package/src/domains/workflows/runtime/usage.ts +341 -0
  115. package/src/domains/workflows/runtime/workflow-advisory-utils.ts +245 -0
  116. package/src/domains/workflows/runtime/workflow-execution.ts +121 -0
  117. package/src/domains/workflows/runtime/workflow-module.ts +75 -0
  118. package/src/domains/workflows/runtime/workflow-run-background.ts +72 -0
  119. package/src/domains/workflows/runtime/workflow-run-controller.ts +342 -0
  120. package/src/domains/workflows/runtime/workflow-run-history.ts +164 -0
  121. package/src/domains/workflows/runtime/workflow-run-record.ts +636 -0
  122. package/src/domains/workflows/runtime/workflow-run-store.ts +176 -0
  123. package/src/domains/workflows/runtime/workflow-usage-limit-scheduler.ts +80 -0
  124. package/src/domains/workflows/runtime/workflows.ts +40 -0
  125. package/src/domains/workflows/runtime/worktree.ts +615 -0
  126. package/src/domains/workflows/workflows/code-review.ts +232 -0
  127. package/src/domains/workflows/workflows/diagnose.ts +154 -0
  128. package/src/domains/workflows/workflows/perf-review.ts +149 -0
  129. package/src/domains/workflows/workflows/refactor-scout.ts +143 -0
  130. package/src/domains/workflows/workflows/research.ts +169 -0
  131. package/src/services/usage-service.ts +6 -30
  132. package/src/ui/usage-bars.ts +1 -2
  133. package/src/core/codex/oauth.ts +0 -129
@@ -0,0 +1,836 @@
1
+ import { fileURLToPath } from "node:url";
2
+ import { Type } from "typebox";
3
+ import { VERSION, type ExtensionAPI, type ExtensionCommandContext, type ExtensionContext } from "@earendil-works/pi-coding-agent";
4
+ import { Text, type AutocompleteItem } from "@earendil-works/pi-tui";
5
+ import { isAdvisoryReport } from "./runtime/advisory-schema.ts";
6
+ import type { WorkflowProgressSnapshot } from "./runtime/progress-types.ts";
7
+ import type { LoadedWorkflow, WorkflowProgressSource, WorkflowRef, WorkflowRunMetadata, WorkflowRunOptions } from "./runtime/types.ts";
8
+ import { WorkflowInspector } from "./runtime/ui/workflow-inspector.ts";
9
+ import { WORKFLOW_VIEWER_OVERLAY_OPTIONS } from "./runtime/ui/workflow-viewer-layout.ts";
10
+ import type { PerfSink } from "./runtime/perf.ts";
11
+ import type { WorkflowUsageSnapshot } from "./runtime/usage.ts";
12
+ import { ADAPTIVE_WORKFLOW_GUIDANCE, registerDynamax } from "./runtime/dynamax.ts";
13
+ import { sessionKey } from "./runtime/session-identity.ts";
14
+ import { resolveDynamaxShortcuts, type DynamaxShortcuts } from "./runtime/dynamax-shortcuts.ts";
15
+ import { ReviewSessionCoordinator } from "./runtime/review/review-session-coordinator.ts";
16
+ import {
17
+ formatWorkflowDetailLines,
18
+ isWorkflowResult,
19
+ renderWorkflowResult,
20
+ } from "./runtime/ui/workflow-result-renderer.ts";
21
+ import {
22
+ parseWorkflowBudgetString,
23
+ parseWorkflowIntegerString,
24
+ resolveWorkflowRunOptions,
25
+ type ResolvedWorkflowRunOptions,
26
+ WORKFLOW_AGENT_TIMEOUT_MAX_MS,
27
+ WORKFLOW_AGENT_TIMEOUT_MIN_MS,
28
+ WORKFLOW_AGENT_RETRIES_MAX,
29
+ WORKFLOW_AGENT_RETRIES_MIN,
30
+ WORKFLOW_BUDGET_MAX,
31
+ WORKFLOW_BUDGET_MIN,
32
+ WORKFLOW_MAX_AGENTS_MAX,
33
+ WORKFLOW_MAX_AGENTS_MIN,
34
+ WORKFLOW_USAGE_LIMIT_ATTEMPTS_MAX,
35
+ WORKFLOW_USAGE_LIMIT_ATTEMPTS_MIN,
36
+ WORKFLOW_USAGE_LIMIT_DELAY_MAX_MS,
37
+ WORKFLOW_USAGE_LIMIT_DELAY_MIN_MS,
38
+ } from "./runtime/options.ts";
39
+ import { executeWorkflowInvocation, type WorkflowExecution, type WorkflowPerfDetails } from "./runtime/workflow-execution.ts";
40
+ import { BackgroundWorkflowCoordinator } from "./runtime/background-workflows.ts";
41
+ import { backgroundUnavailableResult, startBackgroundWorkflowTool } from "./runtime/background-workflow-tool.ts";
42
+ import { WorkflowRunController } from "./runtime/workflow-run-controller.ts";
43
+ import { completeCurrentArgument, splitArgumentPrefix } from "./runtime/command-completions.ts";
44
+ import { assertSupportedPiVersion } from "./runtime/pi-compat.ts";
45
+ import { formatWorkflowInspection, workflowInspectionSnapshot } from "./runtime/ui/workflow-format.ts";
46
+
47
+ /** Root used for bundled and project workflow discovery. */
48
+ const EXTENSION_DIR = fileURLToPath(new URL(".", import.meta.url));
49
+
50
+ function summarize(result: unknown): string {
51
+ if (typeof result === "object" && result !== null && "summary" in result && typeof result.summary === "string") return result.summary;
52
+ return typeof result === "string" ? result : "Workflow finished.";
53
+ }
54
+
55
+ function formatMessageContent(
56
+ name: string,
57
+ result: unknown,
58
+ usage?: WorkflowUsageSnapshot,
59
+ perf?: WorkflowPerfDetails,
60
+ metadata?: WorkflowRunMetadata,
61
+ ): string {
62
+ const details = formatWorkflowDetailLines({ usage, perf, metadata });
63
+ return `## Workflow: ${name}\n\n${formatResultForContext(result)}${details.length > 0 ? `\n\n${details.join("\n")}` : ""}`;
64
+ }
65
+
66
+ function formatResultForContext(result: unknown): string {
67
+ if (!isAdvisoryReport(result)) return summarize(result);
68
+
69
+ const lines = [result.summary];
70
+ if (result.findings.length > 0) {
71
+ lines.push("", "Findings:");
72
+ result.findings.forEach((finding, index) => {
73
+ const id = `R${String(index + 1).padStart(3, "0")}`;
74
+ lines.push(
75
+ `\n### ${id}: ${finding.summary}`,
76
+ `- Severity: ${finding.severity}`,
77
+ `- Confidence: ${finding.confidence}`,
78
+ `- Category: ${finding.category}`,
79
+ `- Location: ${finding.locations.map(formatFindingLocation).join(", ")}`,
80
+ `- Impact: ${finding.impact}`,
81
+ `- Evidence: ${finding.evidence.length > 0 ? finding.evidence.join("; ") : "(none cited)"}`,
82
+ `- Recommendation: ${finding.recommendation}`,
83
+ );
84
+ });
85
+ }
86
+ if (result.nextSteps.length > 0) {
87
+ lines.push("", "Next steps:", ...result.nextSteps.map((step) => `- ${step}`));
88
+ }
89
+ return lines.join("\n");
90
+ }
91
+
92
+ function formatFindingLocation(location: { readonly file: string; readonly line?: number; readonly symbol?: string }): string {
93
+ const line = location.line === undefined ? "" : `:${location.line}`;
94
+ const symbol = location.symbol === undefined ? "" : ` (${location.symbol})`;
95
+ return `${location.file}${line}${symbol}`;
96
+ }
97
+
98
+ type DiscoveryModule = typeof import("./runtime/discovery.ts");
99
+ type EngineModule = typeof import("./runtime/engine.ts");
100
+ type InlineWorkflowModule = typeof import("./runtime/inline-workflow.ts");
101
+
102
+ async function loadDiscovery(): Promise<DiscoveryModule> {
103
+ return await import("./runtime/discovery.ts");
104
+ }
105
+
106
+ async function loadEngine(): Promise<EngineModule> {
107
+ return await import("./runtime/engine.ts");
108
+ }
109
+
110
+ async function loadInlineWorkflow(): Promise<InlineWorkflowModule> {
111
+ return await import("./runtime/inline-workflow.ts");
112
+ }
113
+
114
+ async function createInvocationPerf(options: ResolvedWorkflowRunOptions): Promise<PerfSink | undefined> {
115
+ if (!options.perf) return undefined;
116
+ const { createPerfRecorder } = await import("./runtime/perf.ts");
117
+ return createPerfRecorder(true);
118
+ }
119
+
120
+ /**
121
+ * Resolve an `api.workflow()` reference to a registered workflow module. Throws on an unknown name.
122
+ */
123
+ export async function resolveWorkflowRef(ref: WorkflowRef, perf?: PerfSink): Promise<LoadedWorkflow> {
124
+ const { discoverWorkflows } = await loadDiscovery();
125
+ const workflows = await discoverWorkflows(EXTENSION_DIR, { perf });
126
+ const mod = workflows.get(ref);
127
+ if (!mod) {
128
+ const available = [...workflows.keys()].join(", ") || "(none)";
129
+ throw new Error(`Unknown workflow "${ref}". Available: ${available}`);
130
+ }
131
+ return mod;
132
+ }
133
+
134
+ const WORKFLOW_OPTION_COMPLETIONS = [
135
+ { value: "--inspect", description: "Open the live workflow inspector" },
136
+ { value: "--refresh", description: "Refresh dynamic workflow discovery" },
137
+ { value: "--perf", description: "Collect workflow performance metrics" },
138
+ { value: "--result-viewer", description: "Open supported result viewers" },
139
+ { value: "--no-result-viewer", description: "Skip supported result viewers" },
140
+ { value: "--resume-edited", description: "Allow resume after workflow source edits" },
141
+ { value: "--concurrency=", description: "Optionally cap concurrent subagents" },
142
+ { value: "--parallel-limit=", description: "Set the parallel submission limit" },
143
+ { value: "--max-agents=", description: "Set the maximum admitted live agents" },
144
+ { value: "--agent-timeout-ms=", description: "Set the timeout for each agent attempt" },
145
+ { value: "--agent-retries=", description: "Set retries for each agent call" },
146
+ { value: "--budget=", description: "Set the workflow output-token budget" },
147
+ { value: "--resume=", description: "Resume from a retained workflow run ID" },
148
+ ] as const;
149
+
150
+ async function workflowArgumentCompletions(argumentPrefix: string): Promise<AutocompleteItem[] | null> {
151
+ const parts = splitArgumentPrefix(argumentPrefix);
152
+ if (parts.completed.length === 0) {
153
+ const { discoverWorkflows } = await loadDiscovery();
154
+ const workflows = await discoverWorkflows(EXTENSION_DIR);
155
+ return completeCurrentArgument(
156
+ argumentPrefix,
157
+ [...workflows.values()].map((workflow) => ({
158
+ value: workflow.meta.name,
159
+ description: workflow.meta.description,
160
+ })),
161
+ );
162
+ }
163
+ return completeCurrentArgument(argumentPrefix, WORKFLOW_OPTION_COMPLETIONS);
164
+ }
165
+
166
+ export interface ActiveWorkflowInspection {
167
+ readonly name: string;
168
+ readonly args: string;
169
+ readonly startedAt: number;
170
+ readonly source: WorkflowProgressSource;
171
+ readonly snapshot: () => WorkflowProgressSnapshot;
172
+ }
173
+
174
+ interface SessionWorkflowInspections {
175
+ readonly active: Map<string, ActiveWorkflowInspection>;
176
+ }
177
+
178
+ const workflowInspections = new WeakMap<ExtensionAPI, Map<string, SessionWorkflowInspections>>();
179
+
180
+ export async function openWorkflowInspector(ctx: ExtensionContext, inspection: ActiveWorkflowInspection): Promise<void> {
181
+ if (!ctx.hasUI || ctx.mode !== "tui") {
182
+ ctx.ui.notify(formatWorkflowInspection(inspection), "info");
183
+ return;
184
+ }
185
+ let unsubscribe: (() => void) | undefined;
186
+ try {
187
+ await ctx.ui.custom<void>(
188
+ (tui, theme, _keybindings, done) => {
189
+ const { source } = inspection;
190
+ unsubscribe = source.subscribe(() => tui.requestRender());
191
+ return new WorkflowInspector(
192
+ () => workflowInspectionSnapshot(inspection),
193
+ tui,
194
+ theme,
195
+ () => done(undefined),
196
+ undefined,
197
+ source,
198
+ );
199
+ },
200
+ WORKFLOW_VIEWER_OVERLAY_OPTIONS,
201
+ );
202
+ } finally {
203
+ unsubscribe?.();
204
+ }
205
+ }
206
+
207
+ function workflowInspectionState(pi: ExtensionAPI, ctx: ExtensionContext): SessionWorkflowInspections {
208
+ const sessions = workflowInspections.get(pi) ?? new Map<string, SessionWorkflowInspections>();
209
+ const key = sessionKey(ctx);
210
+ const state = sessions.get(key) ?? { active: new Map<string, ActiveWorkflowInspection>() };
211
+ sessions.set(key, state);
212
+ workflowInspections.set(pi, sessions);
213
+ return state;
214
+ }
215
+
216
+ async function openAvailableWorkflowInspector(pi: ExtensionAPI, ctx: ExtensionContext): Promise<void> {
217
+ const state = workflowInspectionState(pi, ctx);
218
+ const inspection = [...state.active.values()].sort((left, right) => right.startedAt - left.startedAt)[0];
219
+ if (!inspection) {
220
+ ctx.ui.notify("No workflow is currently running", "warning");
221
+ return;
222
+ }
223
+ await openWorkflowInspector(ctx, inspection);
224
+ }
225
+
226
+ function bindActiveWorkflowInspection(name: string, args: string, source: WorkflowProgressSource): ActiveWorkflowInspection {
227
+ return { name, args, startedAt: Date.now(), source, snapshot: () => source.snapshot() };
228
+ }
229
+
230
+ export interface WorkflowInvocation {
231
+ name: string;
232
+ args: string;
233
+ options: WorkflowRunOptions;
234
+ refreshDiscovery?: boolean;
235
+ optionErrors?: string[];
236
+ }
237
+
238
+ export function parseWorkflowInvocation(input: string): WorkflowInvocation {
239
+ const trimmed = input.trim();
240
+ const space = trimmed.indexOf(" ");
241
+ const name = space === -1 ? trimmed : trimmed.slice(0, space);
242
+ const rest = space === -1 ? "" : trimmed.slice(space + 1).trim();
243
+ const { args, options, refreshDiscovery, optionErrors } = parseWorkflowOptions(rest);
244
+ const invocation: WorkflowInvocation = { name, args, options };
245
+ if (refreshDiscovery) invocation.refreshDiscovery = refreshDiscovery;
246
+ if (optionErrors) invocation.optionErrors = optionErrors;
247
+ return invocation;
248
+ }
249
+
250
+ const INVALID_BUDGET_OPTION = "--budget requires a positive integer output-token count";
251
+ const INVALID_RESUME_OPTION = "--resume requires a workflow run id";
252
+ const INVALID_MAX_AGENTS_OPTION = "--max-agents requires an integer";
253
+ const INVALID_AGENT_TIMEOUT_OPTION = "--agent-timeout-ms requires an integer";
254
+ const INVALID_AGENT_RETRIES_OPTION = "--agent-retries requires an integer";
255
+ const INVALID_EDITED_RESUME_OPTION = "--resume-edited requires --resume <run-id>";
256
+
257
+ function parseWorkflowOptions(input: string): { args: string; options: WorkflowRunOptions; refreshDiscovery?: boolean; optionErrors?: string[] } {
258
+ const tokens = input.split(/\s+/).filter(Boolean);
259
+ const kept: string[] = [];
260
+ const options: WorkflowRunOptions = {};
261
+ const optionErrors: string[] = [];
262
+ let refreshDiscovery = false;
263
+ for (let i = 0; i < tokens.length; i++) {
264
+ const token = tokens[i];
265
+ if (token === "--inspect") {
266
+ options.inspect = true;
267
+ continue;
268
+ }
269
+ if (token === "--refresh") {
270
+ refreshDiscovery = true;
271
+ continue;
272
+ }
273
+ if (token === "--perf") {
274
+ options.perf = true;
275
+ continue;
276
+ }
277
+ if (token === "--resume-edited") {
278
+ options.resumeEditedWorkflow = true;
279
+ continue;
280
+ }
281
+ if (token === "--result-viewer" || token === "--review-viewer") {
282
+ options.resultViewer = "open";
283
+ continue;
284
+ }
285
+ if (token === "--no-result-viewer" || token === "--no-review-viewer") {
286
+ options.resultViewer = "skip";
287
+ continue;
288
+ }
289
+ if (token.startsWith("--concurrency=")) {
290
+ options.concurrency = parseNumericOption(token.slice("--concurrency=".length));
291
+ continue;
292
+ }
293
+ if (token === "--concurrency") {
294
+ const next = tokens[i + 1];
295
+ options.concurrency = parseNumericOption(next);
296
+ if (next !== undefined) i++;
297
+ continue;
298
+ }
299
+ if (token.startsWith("--parallel-limit=")) {
300
+ options.parallelSubmissionLimit = parseNumericOption(token.slice("--parallel-limit=".length));
301
+ continue;
302
+ }
303
+ if (token === "--parallel-limit") {
304
+ const next = tokens[i + 1];
305
+ options.parallelSubmissionLimit = parseNumericOption(next);
306
+ if (next !== undefined) i++;
307
+ continue;
308
+ }
309
+ if (token.startsWith("--max-agents=")) {
310
+ const parsed = parseWorkflowIntegerString(token.slice("--max-agents=".length));
311
+ if (parsed === undefined) optionErrors.push(INVALID_MAX_AGENTS_OPTION);
312
+ else options.maxAgents = parsed;
313
+ continue;
314
+ }
315
+ if (token === "--max-agents") {
316
+ const next = tokens[i + 1];
317
+ const parsed = parseWorkflowIntegerString(next);
318
+ if (parsed === undefined) optionErrors.push(INVALID_MAX_AGENTS_OPTION);
319
+ else {
320
+ options.maxAgents = parsed;
321
+ i++;
322
+ }
323
+ continue;
324
+ }
325
+ if (token.startsWith("--agent-timeout-ms=")) {
326
+ const parsed = parseWorkflowIntegerString(token.slice("--agent-timeout-ms=".length));
327
+ if (parsed === undefined) optionErrors.push(INVALID_AGENT_TIMEOUT_OPTION);
328
+ else options.agentTimeoutMs = parsed;
329
+ continue;
330
+ }
331
+ if (token === "--agent-timeout-ms") {
332
+ const next = tokens[i + 1];
333
+ const parsed = parseWorkflowIntegerString(next);
334
+ if (parsed === undefined) optionErrors.push(INVALID_AGENT_TIMEOUT_OPTION);
335
+ else {
336
+ options.agentTimeoutMs = parsed;
337
+ i++;
338
+ }
339
+ continue;
340
+ }
341
+ if (token.startsWith("--agent-retries=")) {
342
+ const parsed = parseWorkflowIntegerString(token.slice("--agent-retries=".length));
343
+ if (parsed === undefined) optionErrors.push(INVALID_AGENT_RETRIES_OPTION);
344
+ else options.agentRetries = parsed;
345
+ continue;
346
+ }
347
+ if (token === "--agent-retries") {
348
+ const next = tokens[i + 1];
349
+ const parsed = parseWorkflowIntegerString(next);
350
+ if (parsed === undefined) optionErrors.push(INVALID_AGENT_RETRIES_OPTION);
351
+ else {
352
+ options.agentRetries = parsed;
353
+ i++;
354
+ }
355
+ continue;
356
+ }
357
+ if (token.startsWith("--budget=")) {
358
+ const parsed = parseBudgetOption(token.slice("--budget=".length));
359
+ if (parsed === undefined) optionErrors.push(INVALID_BUDGET_OPTION);
360
+ else options.budget = parsed;
361
+ continue;
362
+ }
363
+ if (token === "--budget") {
364
+ const next = tokens[i + 1];
365
+ const parsed = next === undefined ? undefined : parseBudgetOption(next);
366
+ if (parsed === undefined) {
367
+ optionErrors.push(INVALID_BUDGET_OPTION);
368
+ } else {
369
+ options.budget = parsed;
370
+ i++;
371
+ }
372
+ continue;
373
+ }
374
+ if (token.startsWith("--resume=")) {
375
+ const value = token.slice("--resume=".length).trim();
376
+ if (value === "") optionErrors.push(INVALID_RESUME_OPTION);
377
+ else options.resumeFromRunId = value;
378
+ continue;
379
+ }
380
+ if (token === "--resume") {
381
+ const next = tokens[i + 1];
382
+ if (next === undefined || next.startsWith("--")) {
383
+ optionErrors.push(INVALID_RESUME_OPTION);
384
+ } else {
385
+ options.resumeFromRunId = next;
386
+ i++;
387
+ }
388
+ continue;
389
+ }
390
+ kept.push(token);
391
+ }
392
+ if (options.resumeEditedWorkflow && !options.resumeFromRunId) optionErrors.push(INVALID_EDITED_RESUME_OPTION);
393
+ return { args: kept.join(" ").trim(), options, refreshDiscovery: refreshDiscovery || undefined, optionErrors: optionErrors.length > 0 ? optionErrors : undefined };
394
+ }
395
+
396
+ function parseBudgetOption(value: string): number | undefined {
397
+ return parseWorkflowBudgetString(value);
398
+ }
399
+
400
+ function parseNumericOption(value: string | undefined): number | undefined {
401
+ if (value === undefined) return undefined;
402
+ const parsed = Number(value);
403
+ return Number.isFinite(parsed) ? parsed : undefined;
404
+ }
405
+
406
+ function compactInlinePreview(script: string | undefined): string {
407
+ if (!script) return "";
408
+ const compact = script.replace(/\s+/g, " ").trim();
409
+ return compact.length > 60 ? `${compact.slice(0, 57)}…` : compact;
410
+ }
411
+
412
+ export interface WorkflowToolRequestParams {
413
+ readonly name?: string;
414
+ readonly script?: string;
415
+ readonly resumeFromRunId?: string;
416
+ readonly background?: boolean;
417
+ }
418
+
419
+ export type WorkflowToolRequest =
420
+ | { readonly kind: "named"; readonly name: string }
421
+ | { readonly kind: "inline"; readonly script: string }
422
+ | { readonly kind: "error"; readonly error: "invalid_workflow_invocation"; readonly message: string };
423
+
424
+ export interface WorkflowToolErrorResult {
425
+ readonly content: Array<{ readonly type: "text"; readonly text: string }>;
426
+ readonly details: { readonly error: "invalid_workflow_invocation" } | { readonly error: "inline_compile_error"; readonly message: string };
427
+ }
428
+
429
+ const INVALID_WORKFLOW_INVOCATION_MESSAGE = "Provide exactly one workflow name or inline workflow script.";
430
+
431
+ export function normalizeWorkflowToolRequest(params: WorkflowToolRequestParams): WorkflowToolRequest {
432
+ const name = params.name?.trim() ?? "";
433
+ const script = params.script?.trim() ?? "";
434
+ const hasName = name.length > 0;
435
+ const hasScript = script.length > 0;
436
+ if (hasName === hasScript) return { kind: "error", error: "invalid_workflow_invocation", message: INVALID_WORKFLOW_INVOCATION_MESSAGE };
437
+ return hasName ? { kind: "named", name } : { kind: "inline", script };
438
+ }
439
+
440
+ export function invalidWorkflowInvocationResult(): WorkflowToolErrorResult {
441
+ return { content: [{ type: "text", text: INVALID_WORKFLOW_INVOCATION_MESSAGE }], details: { error: "invalid_workflow_invocation" } };
442
+ }
443
+
444
+ export function inlineCompileErrorResult(message: string): WorkflowToolErrorResult {
445
+ return { content: [{ type: "text", text: `Inline workflow did not compile: ${message}` }], details: { error: "inline_compile_error", message } };
446
+ }
447
+
448
+ export async function sendWorkflowResult(
449
+ pi: ExtensionAPI,
450
+ ctx: ExtensionContext,
451
+ name: string,
452
+ mod: LoadedWorkflow,
453
+ args: string,
454
+ options: WorkflowRunOptions,
455
+ perfRecorder?: PerfSink,
456
+ reviewSessions: ReviewSessionCoordinator = createReviewSessionCoordinator(pi),
457
+ ): Promise<void> {
458
+ await sendResolvedWorkflowResult(
459
+ pi,
460
+ ctx,
461
+ name,
462
+ mod,
463
+ args,
464
+ resolveWorkflowRunOptions(options),
465
+ perfRecorder,
466
+ reviewSessions,
467
+ );
468
+ }
469
+
470
+ async function sendResolvedWorkflowResult(
471
+ pi: ExtensionAPI,
472
+ ctx: ExtensionContext,
473
+ name: string,
474
+ mod: LoadedWorkflow,
475
+ args: string,
476
+ options: ResolvedWorkflowRunOptions,
477
+ perfRecorder: PerfSink | undefined,
478
+ reviewSessions: ReviewSessionCoordinator,
479
+ ): Promise<void> {
480
+ const execution = await executeResolvedWorkflow(pi, ctx, name, mod, args, options, perfRecorder);
481
+ reviewSessions.remember(ctx, execution, options);
482
+ sendWorkflowExecution(pi, execution);
483
+ await reviewSessions.present(ctx, execution, options);
484
+ }
485
+
486
+ async function executeResolvedWorkflow(
487
+ pi: ExtensionAPI,
488
+ ctx: ExtensionContext,
489
+ name: string,
490
+ mod: LoadedWorkflow,
491
+ args: string,
492
+ options: ResolvedWorkflowRunOptions,
493
+ perfRecorder?: PerfSink,
494
+ ): Promise<WorkflowExecution> {
495
+ const { runResolvedWorkflow } = await loadEngine();
496
+ let liveInspection: ActiveWorkflowInspection | undefined;
497
+ const inspections = workflowInspectionState(pi, ctx);
498
+ return await executeWorkflowInvocation({
499
+ ctx,
500
+ name,
501
+ mod,
502
+ args,
503
+ options,
504
+ perfRecorder,
505
+ runResolvedWorkflow,
506
+ resolveWorkflow: (ref) => resolveWorkflowRef(ref, perfRecorder),
507
+ onProgressSource(source) {
508
+ if (source) {
509
+ liveInspection = bindActiveWorkflowInspection(name, args, source);
510
+ inspections.active.set(source.snapshot().runId, liveInspection);
511
+ } else if (liveInspection) {
512
+ inspections.active.delete(liveInspection.source.snapshot().runId);
513
+ liveInspection = undefined;
514
+ }
515
+ },
516
+ });
517
+ }
518
+
519
+ function sendWorkflowExecution(pi: ExtensionAPI, execution: WorkflowExecution): void {
520
+ const { name } = execution.envelope;
521
+ pi.sendMessage(
522
+ {
523
+ customType: "workflow-result",
524
+ content: formatMessageContent(
525
+ name,
526
+ execution.envelope.result,
527
+ execution.envelope.usage,
528
+ execution.envelope.perf,
529
+ execution.metadata,
530
+ ),
531
+ display: true,
532
+ details: execution.envelope,
533
+ },
534
+ { triggerTurn: false },
535
+ );
536
+ }
537
+
538
+ function createReviewSessionCoordinator(pi: ExtensionAPI): ReviewSessionCoordinator {
539
+ return new ReviewSessionCoordinator(pi, {
540
+ async runFollowUp(ctx, workflow, options) {
541
+ const perfRecorder = await createInvocationPerf(options);
542
+ return await executeResolvedWorkflow(pi, ctx, workflow.meta.name, workflow, "", options, perfRecorder);
543
+ },
544
+ publish: (execution) => sendWorkflowExecution(pi, execution),
545
+ });
546
+ }
547
+
548
+ export default function workflowEngine(pi: ExtensionAPI, shortcuts: DynamaxShortcuts = resolveDynamaxShortcuts()): void {
549
+ assertSupportedPiVersion(VERSION);
550
+ const reviewSessions = createReviewSessionCoordinator(pi);
551
+ const backgroundWorkflows = new BackgroundWorkflowCoordinator(pi);
552
+ const workflowRuns = new WorkflowRunController(backgroundWorkflows, {
553
+ async resolveWorkflow(name) {
554
+ const { discoverWorkflows } = await loadDiscovery();
555
+ return (await discoverWorkflows(EXTENSION_DIR)).get(name);
556
+ },
557
+ async execute(ctx, name, workflow, options) {
558
+ const perfRecorder = await createInvocationPerf(options);
559
+ const execution = await executeResolvedWorkflow(pi, ctx, name, workflow, "", options, perfRecorder);
560
+ reviewSessions.remember(ctx, execution, options);
561
+ },
562
+ });
563
+ backgroundWorkflows.onRunSettled((ctx, runId) => workflowRuns.runSettled(ctx, runId));
564
+ registerDynamax(pi, shortcuts, { openInspector: (ctx) => openAvailableWorkflowInspector(pi, ctx) });
565
+ pi.on("session_start", async (_event, ctx) => {
566
+ await backgroundWorkflows.sessionStarted(ctx);
567
+ await workflowRuns.sessionStarted(ctx);
568
+ });
569
+ pi.on("agent_settled", async (_event, ctx) => {
570
+ await backgroundWorkflows.agentSettled(ctx);
571
+ });
572
+ pi.on("session_shutdown", async (_event, ctx) => {
573
+ workflowRuns.sessionShutdown(ctx);
574
+ await backgroundWorkflows.sessionShutdown(ctx);
575
+ const key = sessionKey(ctx);
576
+ workflowInspections.get(pi)?.delete(key);
577
+ reviewSessions.dispose(ctx);
578
+ });
579
+ if (shortcuts.results) {
580
+ pi.registerShortcut(shortcuts.results, {
581
+ description: "Open last code-review results",
582
+ handler: async (ctx) => {
583
+ await reviewSessions.reopen(ctx);
584
+ },
585
+ });
586
+ }
587
+
588
+ pi.registerMessageRenderer("workflow-result", (message, { expanded }, theme) => {
589
+ const details = message.details;
590
+ if (isWorkflowResult(details)) {
591
+ return renderWorkflowResult(details.name, details.result, expanded, theme, details.usage, {
592
+ runId: details.runId,
593
+ resumedFromRunId: details.resumedFromRunId,
594
+ }, details.perf);
595
+ }
596
+ return renderWorkflowResult("workflow", details ?? message.content, expanded, theme);
597
+ });
598
+
599
+ pi.registerCommand("workflow", {
600
+ description: "Open the running workflow or run one by name",
601
+ getArgumentCompletions: workflowArgumentCompletions,
602
+ handler: async (args: string, ctx: ExtensionCommandContext) => {
603
+ const direct = parseWorkflowInvocation(args);
604
+ if (!direct.name) {
605
+ await openAvailableWorkflowInspector(pi, ctx);
606
+ return;
607
+ }
608
+ if (direct.optionErrors?.length) {
609
+ ctx.ui.notify(`Invalid workflow option: ${direct.optionErrors.join("; ")}`, "warning");
610
+ return;
611
+ }
612
+ const directOptions = resolveWorkflowRunOptions(direct.options);
613
+ const perfRecorder = await createInvocationPerf(directOptions);
614
+ const { discoverWorkflows } = await loadDiscovery();
615
+ const workflows = await discoverWorkflows(EXTENSION_DIR, { refresh: direct.refreshDiscovery, perf: perfRecorder });
616
+ const available = [...workflows.keys()].join(", ") || "(none)";
617
+ const mod = workflows.get(direct.name);
618
+ if (!mod) {
619
+ ctx.ui.notify(`Unknown workflow "${direct.name}". Available: ${available}`, "error");
620
+ return;
621
+ }
622
+ await sendResolvedWorkflowResult(pi, ctx, direct.name, mod, direct.args, directOptions, perfRecorder, reviewSessions);
623
+ },
624
+ });
625
+
626
+ registerWorkflowTool(pi, reviewSessions, backgroundWorkflows);
627
+ }
628
+
629
+ /** Register the host-facing workflow tool independently from command and lifecycle surfaces. */
630
+ function registerWorkflowTool(
631
+ pi: ExtensionAPI,
632
+ reviewSessions: ReviewSessionCoordinator,
633
+ backgroundWorkflows: BackgroundWorkflowCoordinator,
634
+ ): void {
635
+ pi.registerTool({
636
+ name: "workflow",
637
+ label: "Workflow",
638
+ description:
639
+ "ONLY call workflow when the user opted in with the literal token `dynamax`, explicitly requested a workflow, or invoked a command or skill that requires one. Runs named or inline multi-agent workflows synchronously or in the background.",
640
+ promptSnippet: "Run an existing named workflow or an inline one-off workflow script",
641
+ promptGuidelines: [
642
+ "Use workflow only after a `dynamax` opt-in, an explicit workflow request, or a command or skill instruction.",
643
+ "Use workflow with `name` for existing registered workflows such as code-review, diagnose, refactor-scout, or perf-review.",
644
+ "Use workflow with `script` for a new one-off inline workflow; the script must start with `export const meta = { ... }` and default-export an async workflow function.",
645
+ "Inline workflow scripts must use the injected `Type` object for schemas and must not contain imports or dynamic import().",
646
+ "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.",
647
+ "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.",
648
+ "Always pass a plain string as the first `api.agent()` argument; build prompts with template strings before calling agent().",
649
+ "When using `isolation: \"worktree\"`, `api.agent()` returns `{ result, patch, changed }`; use `.result` for the answer and `.patch` for the isolated diff. Successful runs clean up the worktree; failed runs retain recoverable work and report its path.",
650
+ "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.",
651
+ "`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.",
652
+ ADAPTIVE_WORKFLOW_GUIDANCE,
653
+ "Set background: true only when the user explicitly wants the workflow to continue after this tool call; the tool returns a durable run ID and completion is delivered later.",
654
+ "Set autoResumeOnUsageLimit: true only for an explicitly backgrounded workflow when the user wants bounded automatic continuation after a recognized provider usage window.",
655
+ "Set resumeEditedWorkflow: true only with resumeFromRunId when the user explicitly accepts reusing behaviorally identical calls after workflow source edits.",
656
+ "Every workflow tool call must provide exactly one of `name` or `script`, never both.",
657
+ ],
658
+ parameters: Type.Object({
659
+ name: Type.Optional(Type.String({ description: "Workflow name, e.g. code-review. Provide exactly one of name or script." })),
660
+ script: Type.Optional(Type.String({ description: "Inline workflow script. Provide exactly one of script or name." })),
661
+ args: Type.Optional(Type.String({ description: "Arguments for the workflow (e.g. target or focus)" })),
662
+ concurrency: Type.Optional(Type.Number({ description: "Optional per-run agent concurrency cap" })),
663
+ parallelSubmissionLimit: Type.Optional(Type.Number({ description: "Optional limit for eagerly submitted parallel thunks" })),
664
+ maxAgents: Type.Optional(
665
+ Type.Integer({
666
+ description: `Optional live model-call limit; unlimited by default; clamped to ${WORKFLOW_MAX_AGENTS_MIN}-${WORKFLOW_MAX_AGENTS_MAX}`,
667
+ }),
668
+ ),
669
+ agentTimeoutMs: Type.Optional(
670
+ Type.Integer({
671
+ description: `Optional per-agent timeout in milliseconds; disabled by default; clamped to ${WORKFLOW_AGENT_TIMEOUT_MIN_MS}-${WORKFLOW_AGENT_TIMEOUT_MAX_MS}`,
672
+ }),
673
+ ),
674
+ agentRetries: Type.Optional(
675
+ Type.Integer({
676
+ description: `Retries per agent for classified transient provider failures; clamped to ${WORKFLOW_AGENT_RETRIES_MIN}-${WORKFLOW_AGENT_RETRIES_MAX}`,
677
+ }),
678
+ ),
679
+ autoResumeOnUsageLimit: Type.Optional(
680
+ Type.Boolean({ description: "For a background run, opt into bounded automatic resume after a recognized provider usage limit" }),
681
+ ),
682
+ usageLimitMaxAttempts: Type.Optional(
683
+ Type.Integer({
684
+ minimum: WORKFLOW_USAGE_LIMIT_ATTEMPTS_MIN,
685
+ maximum: WORKFLOW_USAGE_LIMIT_ATTEMPTS_MAX,
686
+ description: "Maximum total run attempts in one automatic provider-limit resume chain",
687
+ }),
688
+ ),
689
+ usageLimitMaxDelayMs: Type.Optional(
690
+ Type.Integer({
691
+ minimum: WORKFLOW_USAGE_LIMIT_DELAY_MIN_MS,
692
+ maximum: WORKFLOW_USAGE_LIMIT_DELAY_MAX_MS,
693
+ description: "Maximum delay accepted from a provider reset hint before automatic resume",
694
+ }),
695
+ ),
696
+ budget: Type.Optional(
697
+ Type.Integer({
698
+ minimum: WORKFLOW_BUDGET_MIN,
699
+ maximum: WORKFLOW_BUDGET_MAX,
700
+ description: "Optional output-token ceiling for the run; agent() throws once it is exceeded",
701
+ }),
702
+ ),
703
+ perf: Type.Optional(Type.Boolean({ description: "Include workflow performance timing aggregates in the result details" })),
704
+ resumeFromRunId: Type.Optional(Type.String({ minLength: 1, description: "Workflow run id to resume from by replaying matching completed agent results" })),
705
+ resumeEditedWorkflow: Type.Optional(
706
+ Type.Boolean({ description: "With resumeFromRunId, ignore only workflow-source fingerprint changes while retaining all other replay checks" }),
707
+ ),
708
+ background: Type.Optional(Type.Boolean({ description: "Return a durable run ID immediately and deliver completion to this conversation later" })),
709
+ }),
710
+ renderCall(args, theme) {
711
+ const suffix = args.args ? ` ${theme.fg("dim", args.args)}` : "";
712
+ const background = args.background ? ` ${theme.fg("dim", "(background)")}` : "";
713
+ if (args.name?.trim()) {
714
+ return new Text(`▸ ${theme.fg("toolTitle", theme.bold("workflow"))} ${theme.fg("accent", args.name.trim())}${background}${suffix}`, 0, 0);
715
+ }
716
+ const preview = compactInlinePreview(args.script);
717
+ const previewSuffix = preview ? ` ${theme.fg("dim", preview)}` : "";
718
+ return new Text(`▸ ${theme.fg("toolTitle", theme.bold("workflow"))} ${theme.fg("accent", "inline")}${background}${suffix}${previewSuffix}`, 0, 0);
719
+ },
720
+ renderResult(result, { expanded, isPartial }, theme) {
721
+ if (isPartial) return new Text(theme.fg("accent", "Running workflow…"), 0, 0);
722
+ const details = result.details;
723
+ if (isWorkflowResult(details)) {
724
+ return renderWorkflowResult(details.name, details.result, expanded, theme, details.usage, {
725
+ runId: details.runId,
726
+ resumedFromRunId: details.resumedFromRunId,
727
+ }, details.perf);
728
+ }
729
+ const first = result.content[0];
730
+ const text = first?.type === "text" ? first.text : "Workflow finished.";
731
+ return new Text(theme.fg("muted", text), 0, 0);
732
+ },
733
+ async execute(_toolCallId, params, signal, _onUpdate, ctx) {
734
+ const request = normalizeWorkflowToolRequest(params);
735
+ if (request.kind === "error") return invalidWorkflowInvocationResult();
736
+ const resumeFromRunId = params.resumeFromRunId?.trim();
737
+ if (params.resumeFromRunId !== undefined && resumeFromRunId === "") {
738
+ return {
739
+ content: [{ type: "text", text: "resumeFromRunId must be non-empty." }],
740
+ details: { error: "invalid_resume_from_run_id" },
741
+ };
742
+ }
743
+ if (params.resumeEditedWorkflow && !resumeFromRunId) {
744
+ return {
745
+ content: [{ type: "text", text: "resumeEditedWorkflow requires resumeFromRunId." }],
746
+ details: { error: "invalid_edited_workflow_resume" },
747
+ };
748
+ }
749
+ if (params.background) {
750
+ const unavailable = backgroundUnavailableResult(ctx.mode);
751
+ if (unavailable) return unavailable;
752
+ }
753
+
754
+ const runOptions = resolveWorkflowRunOptions({
755
+ inspect: ctx.hasUI && ctx.mode === "tui",
756
+ concurrency: params.concurrency,
757
+ parallelSubmissionLimit: params.parallelSubmissionLimit,
758
+ maxAgents: params.maxAgents,
759
+ agentTimeoutMs: params.agentTimeoutMs,
760
+ agentRetries: params.agentRetries,
761
+ autoResumeOnUsageLimit: params.autoResumeOnUsageLimit,
762
+ usageLimitMaxAttempts: params.usageLimitMaxAttempts,
763
+ usageLimitMaxDelayMs: params.usageLimitMaxDelayMs,
764
+ budget: params.budget,
765
+ perf: params.perf,
766
+ resumeFromRunId,
767
+ resumeEditedWorkflow: params.resumeEditedWorkflow,
768
+ signal,
769
+ });
770
+ const perfRecorder = await createInvocationPerf(runOptions);
771
+ let mod: LoadedWorkflow;
772
+ let resultName: string;
773
+
774
+ if (request.kind === "named") {
775
+ const { discoverWorkflows } = await loadDiscovery();
776
+ const workflows = await discoverWorkflows(EXTENSION_DIR, { perf: perfRecorder });
777
+ const named = workflows.get(request.name);
778
+ if (!named) {
779
+ const available = [...workflows.keys()].join(", ") || "(none)";
780
+ return {
781
+ content: [{ type: "text", text: `Unknown workflow "${request.name}". Available: ${available}` }],
782
+ details: { error: "unknown_workflow", available },
783
+ };
784
+ }
785
+ mod = named;
786
+ resultName = request.name;
787
+ } else {
788
+ const inline = await loadInlineWorkflow();
789
+ try {
790
+ mod = inline.compileInlineWorkflow(request.script);
791
+ } catch (error) {
792
+ if (error instanceof inline.InlineWorkflowCompileError) return inlineCompileErrorResult(error.message);
793
+ throw error;
794
+ }
795
+ resultName = mod.meta.name;
796
+ }
797
+
798
+ const resultArgs = params.args ?? "";
799
+ if (params.background) {
800
+ return await startBackgroundWorkflowTool({
801
+ coordinator: backgroundWorkflows,
802
+ ctx,
803
+ name: resultName,
804
+ options: runOptions,
805
+ async execute(backgroundCtx, backgroundOptions) {
806
+ const execution = await executeResolvedWorkflow(
807
+ pi,
808
+ backgroundCtx,
809
+ resultName,
810
+ mod,
811
+ resultArgs,
812
+ backgroundOptions,
813
+ perfRecorder,
814
+ );
815
+ reviewSessions.remember(ctx, execution, backgroundOptions);
816
+ },
817
+ });
818
+ }
819
+ const execution = await executeResolvedWorkflow(pi, ctx, resultName, mod, resultArgs, runOptions, perfRecorder);
820
+ reviewSessions.remember(ctx, execution, runOptions);
821
+ return {
822
+ content: [{
823
+ type: "text",
824
+ text: formatMessageContent(
825
+ resultName,
826
+ execution.envelope.result,
827
+ execution.envelope.usage,
828
+ execution.envelope.perf,
829
+ execution.metadata,
830
+ ),
831
+ }],
832
+ details: execution.envelope,
833
+ };
834
+ },
835
+ });
836
+ }