pi-workflow-engine 0.13.0 → 1.1.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 +152 -416
- package/.pi/extensions/pi-workflow-engine/src/advisory-challenge.ts +1 -1
- package/.pi/extensions/pi-workflow-engine/src/advisory-evidence.ts +17 -14
- 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 +12 -13
- 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 +131 -117
- 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-workflow-tool.ts +3 -0
- 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/classify.ts +107 -0
- 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 +58 -117
- package/.pi/extensions/pi-workflow-engine/src/engine.ts +41 -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/host-tools.ts +76 -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/model-ref.ts +19 -0
- 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/pi-compat.ts +2 -2
- 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 +29 -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 +17 -4
- 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 +30 -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 +41 -34
- package/.pi/extensions/pi-workflow-engine/src/workflow-advisory-utils.ts +162 -22
- 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/README.md +1 -1
- package/USAGE.md +79 -20
- 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
|
@@ -18,19 +18,21 @@ import {
|
|
|
18
18
|
isWorkflowRunLifecycleAction,
|
|
19
19
|
parseWorkflowRunsCommand,
|
|
20
20
|
retainedWorkflowRunOutcome,
|
|
21
|
+
supersedingRuns,
|
|
21
22
|
WORKFLOW_RUN_ACTIONS,
|
|
22
23
|
WORKFLOW_RUN_HISTORY_LIMIT,
|
|
24
|
+
type WorkflowRunActionContext,
|
|
23
25
|
type WorkflowRunLifecycleAction,
|
|
24
26
|
} from "./workflow-run-history.ts";
|
|
25
|
-
import {
|
|
27
|
+
import { stopWorkflowRunRecord, type WorkflowRunRecord } from "./workflow-run-record.ts";
|
|
26
28
|
import { ProjectWorkflowRunStore, type WorkflowRunStore } from "./workflow-run-store.ts";
|
|
27
29
|
import { unknownErrorMessage } from "./unknown-error.ts";
|
|
28
|
-
import { emptyWorkflowUsageTotals } from "./usage.ts";
|
|
29
30
|
import {
|
|
30
31
|
WorkflowUsageLimitScheduler,
|
|
31
32
|
type WorkflowUsageLimitSchedulerClock,
|
|
32
33
|
} from "./workflow-usage-limit-scheduler.ts";
|
|
33
|
-
import {
|
|
34
|
+
import { showWorkflowInspector } from "./ui/workflow-inspector.ts";
|
|
35
|
+
import { WorkflowRunsBrowser, type WorkflowRunsBrowserChoice } from "./ui/workflow-runs-browser.ts";
|
|
34
36
|
import { WORKFLOW_VIEWER_OVERLAY_OPTIONS } from "./ui/workflow-viewer-layout.ts";
|
|
35
37
|
import { completeCurrentArgument, splitArgumentPrefix } from "./command-completions.ts";
|
|
36
38
|
|
|
@@ -77,8 +79,10 @@ export class WorkflowRunController {
|
|
|
77
79
|
this.completionContext = { cwd: ctx.cwd, sessionManager: ctx.sessionManager };
|
|
78
80
|
this.usageLimitScheduler.activateSession(ctx);
|
|
79
81
|
try {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
+
const records = await this.storeForCwd(ctx.cwd).list();
|
|
83
|
+
const resumed = resumedRunIds(records);
|
|
84
|
+
for (const record of records) {
|
|
85
|
+
if (!resumed.has(record.runId) && canRelaunchWorkflowRun(record)) this.usageLimitScheduler.arm(ctx, record);
|
|
82
86
|
}
|
|
83
87
|
} catch (error) {
|
|
84
88
|
this.log(`[workflow] provider-limit recovery could not load run history: ${unknownErrorMessage(error)}`);
|
|
@@ -101,11 +105,12 @@ export class WorkflowRunController {
|
|
|
101
105
|
return;
|
|
102
106
|
}
|
|
103
107
|
if (!ctx.hasUI) {
|
|
104
|
-
const records = await this.
|
|
105
|
-
ctx.ui.notify(formatWorkflowRunHistory(records,
|
|
108
|
+
const { records, runs } = await this.history(ctx);
|
|
109
|
+
ctx.ui.notify(formatWorkflowRunHistory(records, runs), "info");
|
|
106
110
|
return;
|
|
107
111
|
}
|
|
108
|
-
await this.
|
|
112
|
+
if (ctx.mode === "tui") await this.openRunsBrowser(ctx);
|
|
113
|
+
else await this.openRunSelector(ctx);
|
|
109
114
|
}
|
|
110
115
|
|
|
111
116
|
async inspectStoredRun(ctx: ExtensionContext, runId: string): Promise<boolean> {
|
|
@@ -115,17 +120,35 @@ export class WorkflowRunController {
|
|
|
115
120
|
return true;
|
|
116
121
|
}
|
|
117
122
|
|
|
118
|
-
|
|
123
|
+
/** The TUI runs overlay; it reopens on the same run after each action until closed. */
|
|
124
|
+
private async openRunsBrowser(ctx: ExtensionCommandContext): Promise<void> {
|
|
125
|
+
let initialRunId: string | undefined;
|
|
119
126
|
while (true) {
|
|
120
|
-
const records = await this.
|
|
121
|
-
const active = this.background.activeRunIds(ctx);
|
|
127
|
+
const { records, runs } = await this.history(ctx);
|
|
122
128
|
if (records.length === 0) {
|
|
123
|
-
ctx.ui.notify(formatWorkflowRunHistory(records,
|
|
129
|
+
ctx.ui.notify(formatWorkflowRunHistory(records, runs), "info");
|
|
124
130
|
return;
|
|
125
131
|
}
|
|
126
|
-
const
|
|
127
|
-
|
|
132
|
+
const choice = await ctx.ui.custom<WorkflowRunsBrowserChoice | undefined>(
|
|
133
|
+
(...[tui, theme, , done]) =>
|
|
134
|
+
new WorkflowRunsBrowser({ records, runs }, tui, theme, done, { initialRunId, refresh: () => this.history(ctx) }),
|
|
135
|
+
WORKFLOW_VIEWER_OVERLAY_OPTIONS,
|
|
128
136
|
);
|
|
137
|
+
if (!choice) return;
|
|
138
|
+
initialRunId = choice.runId;
|
|
139
|
+
await this.perform(choice.action, choice.runId, ctx);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Two native selects (run, then action) for UI hosts that cannot show custom components, such as RPC. */
|
|
144
|
+
private async openRunSelector(ctx: ExtensionCommandContext): Promise<void> {
|
|
145
|
+
while (true) {
|
|
146
|
+
const { records, runs } = await this.history(ctx);
|
|
147
|
+
if (records.length === 0) {
|
|
148
|
+
ctx.ui.notify(formatWorkflowRunHistory(records, runs), "info");
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const options = records.map((record) => formatWorkflowRunSummary(record, runs));
|
|
129
152
|
const selected = await ctx.ui.select("Workflow Runs", options);
|
|
130
153
|
if (!selected) return;
|
|
131
154
|
const selectedIndex = options.indexOf(selected);
|
|
@@ -135,7 +158,7 @@ export class WorkflowRunController {
|
|
|
135
158
|
ctx.ui.notify("The selected workflow run is no longer available.", "warning");
|
|
136
159
|
continue;
|
|
137
160
|
}
|
|
138
|
-
const actions = availableWorkflowRunActions(record,
|
|
161
|
+
const actions = availableWorkflowRunActions(record, runs);
|
|
139
162
|
const selectedAction = await ctx.ui.select(
|
|
140
163
|
`${record.workflow.name} · ${record.runId}`,
|
|
141
164
|
[...actions],
|
|
@@ -149,22 +172,10 @@ export class WorkflowRunController {
|
|
|
149
172
|
|
|
150
173
|
private async inspect(record: WorkflowRunRecord, ctx: ExtensionContext): Promise<void> {
|
|
151
174
|
if (!ctx.hasUI || ctx.mode !== "tui") {
|
|
152
|
-
ctx.ui.notify(
|
|
153
|
-
formatWorkflowRunDetails(record, this.background.activeRunIds(ctx).has(record.runId)),
|
|
154
|
-
"info",
|
|
155
|
-
);
|
|
175
|
+
ctx.ui.notify(formatWorkflowRunDetails(record, (await this.history(ctx)).runs), "info");
|
|
156
176
|
return;
|
|
157
177
|
}
|
|
158
|
-
await ctx.ui.
|
|
159
|
-
(tui, theme, _keybindings, done) => new WorkflowInspector(
|
|
160
|
-
() => record.progress,
|
|
161
|
-
tui,
|
|
162
|
-
theme,
|
|
163
|
-
() => done(undefined),
|
|
164
|
-
{ label: `${record.state.toUpperCase()} outcome`, text: retainedWorkflowRunOutcome(record) },
|
|
165
|
-
),
|
|
166
|
-
WORKFLOW_VIEWER_OVERLAY_OPTIONS,
|
|
167
|
-
);
|
|
178
|
+
await showWorkflowInspector(ctx.ui, () => record.progress, { text: retainedWorkflowRunOutcome(record), state: record.state });
|
|
168
179
|
}
|
|
169
180
|
|
|
170
181
|
private async perform(
|
|
@@ -181,12 +192,15 @@ export class WorkflowRunController {
|
|
|
181
192
|
await this.inspect(record, ctx);
|
|
182
193
|
return;
|
|
183
194
|
}
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
195
|
+
const { runs } = await this.history(ctx);
|
|
196
|
+
if (!availableWorkflowRunActions(record, runs).includes(action)) {
|
|
197
|
+
const resumedAs = record.state === "paused" && action === "resume" ? runs.resumedAs.get(runId) : undefined;
|
|
198
|
+
ctx.ui.notify(
|
|
199
|
+
resumedAs
|
|
200
|
+
? `Workflow run ${runId} was already resumed as ${resumedAs}.`
|
|
201
|
+
: `Action ${action} is not available for ${record.state} run ${runId}.`,
|
|
202
|
+
"warning",
|
|
203
|
+
);
|
|
190
204
|
return;
|
|
191
205
|
}
|
|
192
206
|
|
|
@@ -194,17 +208,7 @@ export class WorkflowRunController {
|
|
|
194
208
|
if (action === "stop") {
|
|
195
209
|
if (record.state === "paused") {
|
|
196
210
|
this.usageLimitScheduler.cancel(runId);
|
|
197
|
-
|
|
198
|
-
state: "stopped",
|
|
199
|
-
progress: record.progress,
|
|
200
|
-
usage: record.usage ?? record.progress.usage ?? {
|
|
201
|
-
agents: [],
|
|
202
|
-
totals: emptyWorkflowUsageTotals(),
|
|
203
|
-
assistantMessages: 0,
|
|
204
|
-
},
|
|
205
|
-
error: new Error("Workflow stopped by user."),
|
|
206
|
-
});
|
|
207
|
-
await this.storeForCwd(ctx.cwd).save(stopped);
|
|
211
|
+
await this.storeForCwd(ctx.cwd).save(stopWorkflowRunRecord(record));
|
|
208
212
|
await this.background.durableRunSettled(ctx, runId);
|
|
209
213
|
ctx.ui.notify(`Workflow run ${runId} is now stopped.`, "info");
|
|
210
214
|
return;
|
|
@@ -227,10 +231,7 @@ export class WorkflowRunController {
|
|
|
227
231
|
action: "resume" | "restart",
|
|
228
232
|
): Promise<string> {
|
|
229
233
|
const unavailable = backgroundUnavailableResult(ctx.mode);
|
|
230
|
-
if (unavailable)
|
|
231
|
-
const first = unavailable.content[0];
|
|
232
|
-
throw new Error(first?.type === "text" ? first.text : "background workflows are unavailable");
|
|
233
|
-
}
|
|
234
|
+
if (unavailable) throw new Error(unavailable.content[0].text);
|
|
234
235
|
const workflow = await this.dependencies.resolveWorkflow(record.workflow.name);
|
|
235
236
|
if (!workflow) throw new Error(`registered workflow ${record.workflow.name} is unavailable`);
|
|
236
237
|
if (workflow.source.kind !== "file") {
|
|
@@ -243,7 +244,6 @@ export class WorkflowRunController {
|
|
|
243
244
|
throw new Error("workflow source changed, so journal replay cannot resume safely");
|
|
244
245
|
}
|
|
245
246
|
const options = resolveWorkflowRunOptions({
|
|
246
|
-
inspect: false,
|
|
247
247
|
perf: record.options.perf,
|
|
248
248
|
concurrency: record.options.concurrency,
|
|
249
249
|
parallelSubmissionLimit: record.options.parallelSubmissionLimit ?? undefined,
|
|
@@ -257,7 +257,6 @@ export class WorkflowRunController {
|
|
|
257
257
|
? (record.state === "paused" ? record.pause?.attempt : undefined) ?? record.options.usageLimitAttempt
|
|
258
258
|
: 0,
|
|
259
259
|
budget: record.options.budget ?? undefined,
|
|
260
|
-
resultViewer: "skip",
|
|
261
260
|
resumeFromRunId: action === "resume" ? record.runId : undefined,
|
|
262
261
|
});
|
|
263
262
|
const result = await startBackgroundWorkflowTool({
|
|
@@ -268,8 +267,7 @@ export class WorkflowRunController {
|
|
|
268
267
|
execute: (backgroundCtx, backgroundOptions) =>
|
|
269
268
|
this.dependencies.execute(backgroundCtx, workflow.meta.name, workflow, backgroundOptions),
|
|
270
269
|
});
|
|
271
|
-
const
|
|
272
|
-
const message = first?.type === "text" ? first.text : `Workflow ${action} started.`;
|
|
270
|
+
const message = result.content[0].text;
|
|
273
271
|
if (typeof result.details.error === "string") throw new Error(message);
|
|
274
272
|
return message;
|
|
275
273
|
}
|
|
@@ -282,6 +280,7 @@ export class WorkflowRunController {
|
|
|
282
280
|
|| !record.pause.autoResume
|
|
283
281
|
|| record.pause.attempt !== attempt
|
|
284
282
|
|| !canRelaunchWorkflowRun(record)
|
|
283
|
+
|| resumedRunIds(await this.storeForCwd(ctx.cwd).list()).has(runId)
|
|
285
284
|
) {
|
|
286
285
|
return;
|
|
287
286
|
}
|
|
@@ -298,11 +297,17 @@ export class WorkflowRunController {
|
|
|
298
297
|
return await this.storeForCwd(cwd).load(runId);
|
|
299
298
|
}
|
|
300
299
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
300
|
+
/** The most recent runs and what their actions depend on, from one store listing. */
|
|
301
|
+
private async history(
|
|
302
|
+
ctx: WorkflowRunCompletionContext,
|
|
303
|
+
): Promise<{ readonly records: WorkflowRunRecord[]; readonly runs: WorkflowRunActionContext }> {
|
|
304
|
+
const records = await this.storeForCwd(ctx.cwd).list();
|
|
305
|
+
return {
|
|
306
|
+
records: [...records]
|
|
307
|
+
.sort((left, right) => right.createdAt - left.createdAt)
|
|
308
|
+
.slice(0, WORKFLOW_RUN_HISTORY_LIMIT),
|
|
309
|
+
runs: { activeRunIds: this.background.activeRunIds(ctx), resumedAs: supersedingRuns(records) },
|
|
310
|
+
};
|
|
306
311
|
}
|
|
307
312
|
|
|
308
313
|
async argumentCompletions(argumentPrefix: string): Promise<AutocompleteItem[] | null> {
|
|
@@ -314,12 +319,11 @@ export class WorkflowRunController {
|
|
|
314
319
|
if (parts.completed.length !== 1 || !ctx) return null;
|
|
315
320
|
const action = parts.completed[0];
|
|
316
321
|
if (!action || !isWorkflowRunLifecycleAction(action)) return null;
|
|
317
|
-
const records = await this.
|
|
318
|
-
const active = this.background.activeRunIds(ctx);
|
|
322
|
+
const { records, runs } = await this.history(ctx);
|
|
319
323
|
return completeCurrentArgument(
|
|
320
324
|
argumentPrefix,
|
|
321
325
|
records
|
|
322
|
-
.filter((record) => availableWorkflowRunActions(record,
|
|
326
|
+
.filter((record) => availableWorkflowRunActions(record, runs).includes(action))
|
|
323
327
|
.map((record) => ({
|
|
324
328
|
value: record.runId,
|
|
325
329
|
description: `${record.state} · ${record.workflow.name}`,
|
|
@@ -331,7 +335,7 @@ export class WorkflowRunController {
|
|
|
331
335
|
const ctx = this.completionContext;
|
|
332
336
|
const parts = splitArgumentPrefix(argumentPrefix);
|
|
333
337
|
if (parts.completed.length > 0) return null;
|
|
334
|
-
const records = ctx ? await this.
|
|
338
|
+
const records = ctx ? (await this.history(ctx)).records : [];
|
|
335
339
|
return completeCurrentArgument(argumentPrefix, [
|
|
336
340
|
{ value: "last", description: "Inspect the current or most recent in-session workflow" },
|
|
337
341
|
...records.map((record) => ({
|
|
@@ -342,6 +346,16 @@ export class WorkflowRunController {
|
|
|
342
346
|
}
|
|
343
347
|
}
|
|
344
348
|
|
|
349
|
+
/**
|
|
350
|
+
* Runs that any stored run resumed, whatever that resume's outcome. A resumed run keeps its paused
|
|
351
|
+
* record and pause attempt, so automatic resume treats the pause as spent: re-arming it after a
|
|
352
|
+
* failed or stopped resume would relaunch it on every session start. Manual resume instead uses
|
|
353
|
+
* `supersedingRuns`, which frees a run whose every resume failed or was stopped.
|
|
354
|
+
*/
|
|
355
|
+
function resumedRunIds(records: readonly WorkflowRunRecord[]): ReadonlySet<string> {
|
|
356
|
+
return new Set(records.flatMap((record) => record.options.resumeFromRunId ?? []));
|
|
357
|
+
}
|
|
358
|
+
|
|
345
359
|
export function registerWorkflowRunCommand(
|
|
346
360
|
pi: Pick<ExtensionAPI, "registerCommand">,
|
|
347
361
|
controller: WorkflowRunController,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { truncateText } from "./text.ts";
|
|
1
2
|
import { formatWorkflowUsageLine } from "./usage.ts";
|
|
2
|
-
import type { WorkflowRunRecord
|
|
3
|
+
import type { WorkflowRunRecord } from "./workflow-run-record.ts";
|
|
3
4
|
import { formatDuration } from "./ui/workflow-format.ts";
|
|
4
5
|
|
|
5
6
|
export const WORKFLOW_RUN_HISTORY_LIMIT = 50;
|
|
@@ -43,15 +44,37 @@ export function workflowRunsUsage(): string {
|
|
|
43
44
|
return `Usage: /workflow:runs [${WORKFLOW_RUN_ACTIONS.map((action) => action.value).join("|")} <run-id>]`;
|
|
44
45
|
}
|
|
45
46
|
|
|
47
|
+
/** What a run's lifecycle actions depend on beyond its own record. */
|
|
48
|
+
export interface WorkflowRunActionContext {
|
|
49
|
+
/** Runs this session is executing in the background. */
|
|
50
|
+
readonly activeRunIds: ReadonlySet<string>;
|
|
51
|
+
/** Resumed runs, keyed to the run that resumed them; see {@link supersedingRuns}. */
|
|
52
|
+
readonly resumedAs: ReadonlyMap<string, string>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Maps each run to a stored run that resumed it and is queued, running, paused or completed, so
|
|
57
|
+
* resuming it again would replay its journal a second time. A run whose every resume failed or
|
|
58
|
+
* was stopped is absent, which leaves manual resume as the way back to its journal.
|
|
59
|
+
*/
|
|
60
|
+
export function supersedingRuns(records: readonly WorkflowRunRecord[]): ReadonlyMap<string, string> {
|
|
61
|
+
const resumedAs = new Map<string, string>();
|
|
62
|
+
for (const record of records) {
|
|
63
|
+
const source = record.options.resumeFromRunId;
|
|
64
|
+
if (source !== undefined && record.state !== "failed" && record.state !== "stopped") resumedAs.set(source, record.runId);
|
|
65
|
+
}
|
|
66
|
+
return resumedAs;
|
|
67
|
+
}
|
|
68
|
+
|
|
46
69
|
export function availableWorkflowRunActions(
|
|
47
70
|
record: WorkflowRunRecord,
|
|
48
|
-
|
|
71
|
+
runs: WorkflowRunActionContext,
|
|
49
72
|
): readonly WorkflowRunLifecycleAction[] {
|
|
50
73
|
const actions: WorkflowRunLifecycleAction[] = ["inspect"];
|
|
51
|
-
if ((record.state === "queued" || record.state === "running") &&
|
|
74
|
+
if ((record.state === "queued" || record.state === "running") && runs.activeRunIds.has(record.runId)) actions.push("stop");
|
|
52
75
|
if (record.state === "paused") {
|
|
53
76
|
actions.push("stop");
|
|
54
|
-
if (canRelaunchWorkflowRun(record)) actions.push("resume");
|
|
77
|
+
if (!runs.resumedAs.has(record.runId) && canRelaunchWorkflowRun(record)) actions.push("resume");
|
|
55
78
|
}
|
|
56
79
|
if (
|
|
57
80
|
(record.state === "completed" || record.state === "failed" || record.state === "stopped")
|
|
@@ -70,33 +93,31 @@ export function canRelaunchWorkflowRun(record: WorkflowRunRecord): boolean {
|
|
|
70
93
|
|
|
71
94
|
export function formatWorkflowRunHistory(
|
|
72
95
|
records: readonly WorkflowRunRecord[],
|
|
73
|
-
|
|
96
|
+
runs: WorkflowRunActionContext,
|
|
74
97
|
now = Date.now(),
|
|
75
98
|
): string {
|
|
76
99
|
if (records.length === 0) return "No durable workflow runs are available for this project.";
|
|
77
100
|
const lines = ["Recent workflow runs:"];
|
|
78
|
-
for (const record of records
|
|
79
|
-
lines.push(`- ${formatWorkflowRunSummary(record,
|
|
80
|
-
}
|
|
81
|
-
if (records.length > WORKFLOW_RUN_HISTORY_LIMIT) {
|
|
82
|
-
lines.push(`… ${records.length - WORKFLOW_RUN_HISTORY_LIMIT} older runs hidden`);
|
|
101
|
+
for (const record of records) {
|
|
102
|
+
lines.push(`- ${formatWorkflowRunSummary(record, runs, now)}`);
|
|
83
103
|
}
|
|
84
104
|
return lines.join("\n");
|
|
85
105
|
}
|
|
86
106
|
|
|
87
107
|
export function formatWorkflowRunSummary(
|
|
88
108
|
record: WorkflowRunRecord,
|
|
89
|
-
|
|
109
|
+
runs: WorkflowRunActionContext,
|
|
90
110
|
now = Date.now(),
|
|
91
111
|
): string {
|
|
92
112
|
const usage = formatWorkflowUsageLine(record.usage);
|
|
93
|
-
const
|
|
94
|
-
|
|
113
|
+
const resumedAs = runs.resumedAs.get(record.runId);
|
|
114
|
+
const actions = availableWorkflowRunActions(record, runs).filter((action) => action !== "inspect");
|
|
115
|
+
return `${record.state.toUpperCase()} ${record.workflow.name} · age ${formatDuration(Math.max(0, now - record.createdAt))} · duration ${formatWorkflowRunDuration(record, now)}${usage ? ` · ${usage}` : ""} · ${record.runId}${resumedAs ? ` · resumed as ${resumedAs}` : ""}${actions.length > 0 ? ` · actions ${actions.join(", ")}` : ""}`;
|
|
95
116
|
}
|
|
96
117
|
|
|
97
118
|
export function formatWorkflowRunDetails(
|
|
98
119
|
record: WorkflowRunRecord,
|
|
99
|
-
|
|
120
|
+
runs: WorkflowRunActionContext,
|
|
100
121
|
now = Date.now(),
|
|
101
122
|
): string {
|
|
102
123
|
const agents = record.progress.phases.flatMap((phase) =>
|
|
@@ -107,12 +128,14 @@ export function formatWorkflowRunDetails(
|
|
|
107
128
|
const lines = [
|
|
108
129
|
`Workflow run ${record.runId}`,
|
|
109
130
|
`Workflow: ${record.workflow.name}`,
|
|
110
|
-
`State: ${
|
|
131
|
+
`State: ${record.state.toUpperCase()}`,
|
|
111
132
|
`Age: ${formatDuration(Math.max(0, now - record.createdAt))}`,
|
|
112
133
|
`Duration: ${formatWorkflowRunDuration(record, now)}`,
|
|
113
134
|
`Phase: ${record.progress.currentPhase}`,
|
|
114
|
-
`Actions: ${availableWorkflowRunActions(record,
|
|
135
|
+
`Actions: ${availableWorkflowRunActions(record, runs).join(", ")}`,
|
|
115
136
|
];
|
|
137
|
+
const resumedAs = runs.resumedAs.get(record.runId);
|
|
138
|
+
if (resumedAs) lines.push(`Resumed as: ${resumedAs}`);
|
|
116
139
|
if (usage) lines.push(usage);
|
|
117
140
|
if (record.options.resumeEditedWorkflow) lines.push("Edited-workflow resume: enabled");
|
|
118
141
|
const cachedAgents = record.progress.counters.find((counter) => counter.key === "resume.cached")?.value ?? 0;
|
|
@@ -136,11 +159,11 @@ export function formatWorkflowRunDetails(
|
|
|
136
159
|
|
|
137
160
|
export function retainedWorkflowRunOutcome(record: WorkflowRunRecord): string {
|
|
138
161
|
if (record.state === "completed") {
|
|
139
|
-
if (record.result.kind === "unavailable") return
|
|
140
|
-
return
|
|
162
|
+
if (record.result.kind === "unavailable") return truncateText(`Result unavailable: ${record.result.reason}`, WORKFLOW_RUN_OUTCOME_TEXT_LIMIT);
|
|
163
|
+
return truncateText(JSON.stringify(record.result.value, null, 2), WORKFLOW_RUN_OUTCOME_TEXT_LIMIT);
|
|
141
164
|
}
|
|
142
165
|
if (record.state === "failed" || record.state === "stopped" || record.state === "paused") {
|
|
143
|
-
return
|
|
166
|
+
return truncateText(record.message, WORKFLOW_RUN_OUTCOME_TEXT_LIMIT);
|
|
144
167
|
}
|
|
145
168
|
return "Run is still in progress.";
|
|
146
169
|
}
|
|
@@ -150,13 +173,3 @@ export function formatWorkflowRunDuration(record: WorkflowRunRecord, now = Date.
|
|
|
150
173
|
const end = record.endedAt ?? (record.state === "paused" ? record.updatedAt : now);
|
|
151
174
|
return formatDuration(Math.max(0, end - start));
|
|
152
175
|
}
|
|
153
|
-
|
|
154
|
-
export function workflowRunStateLabel(state: WorkflowRunState): string {
|
|
155
|
-
return state.toUpperCase();
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function boundedOutcome(value: string): string {
|
|
159
|
-
return value.length <= WORKFLOW_RUN_OUTCOME_TEXT_LIMIT
|
|
160
|
-
? value
|
|
161
|
-
: `${value.slice(0, WORKFLOW_RUN_OUTCOME_TEXT_LIMIT - 1)}…`;
|
|
162
|
-
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { validateWorkflowRunId } from "./journal.ts";
|
|
2
|
+
import { isFiniteNumber, isRecord } from "./guards.ts";
|
|
2
3
|
import {
|
|
3
4
|
type ResolvedWorkflowRunOptions,
|
|
4
5
|
WORKFLOW_USAGE_LIMIT_ATTEMPTS_MAX,
|
|
@@ -8,7 +9,8 @@ import {
|
|
|
8
9
|
} from "./options.ts";
|
|
9
10
|
import type { WorkflowProgressSnapshot } from "./progress-types.ts";
|
|
10
11
|
import type { LoadedWorkflow, WorkflowSourceIdentity } from "./types.ts";
|
|
11
|
-
import {
|
|
12
|
+
import { truncateText } from "./text.ts";
|
|
13
|
+
import { emptyWorkflowUsageTotals, isWorkflowUsageSnapshot, type WorkflowUsageSnapshot } from "./usage.ts";
|
|
12
14
|
import { unknownErrorMessage } from "./unknown-error.ts";
|
|
13
15
|
import {
|
|
14
16
|
createPersistedWorkflowBackground,
|
|
@@ -112,7 +114,6 @@ export type WorkflowRunRecord = WorkflowRunRecordBase & (
|
|
|
112
114
|
);
|
|
113
115
|
|
|
114
116
|
export type WorkflowRunTransition =
|
|
115
|
-
| { readonly state: "queued"; readonly progress: WorkflowProgressSnapshot; readonly at?: number }
|
|
116
117
|
| { readonly state: "running"; readonly progress: WorkflowProgressSnapshot; readonly at?: number }
|
|
117
118
|
| { readonly state: "paused"; readonly progress: WorkflowProgressSnapshot; readonly message: string; readonly pause?: WorkflowRunPause; readonly at?: number }
|
|
118
119
|
| { readonly state: "completed"; readonly progress: WorkflowProgressSnapshot; readonly usage: WorkflowUsageSnapshot; readonly result: unknown; readonly at?: number }
|
|
@@ -177,8 +178,6 @@ export function transitionWorkflowRun(record: WorkflowRunRecord, transition: Wor
|
|
|
177
178
|
};
|
|
178
179
|
|
|
179
180
|
switch (transition.state) {
|
|
180
|
-
case "queued":
|
|
181
|
-
return { ...base, state: "queued", startedAt: record.startedAt };
|
|
182
181
|
case "running":
|
|
183
182
|
return { ...base, state: "running", startedAt: record.startedAt ?? at };
|
|
184
183
|
case "paused":
|
|
@@ -211,6 +210,16 @@ export function transitionWorkflowRun(record: WorkflowRunRecord, transition: Wor
|
|
|
211
210
|
}
|
|
212
211
|
}
|
|
213
212
|
|
|
213
|
+
/** Records a user stop from the retained state, for runs whose engine cannot record it itself. */
|
|
214
|
+
export function stopWorkflowRunRecord(record: WorkflowRunRecord): WorkflowRunRecord {
|
|
215
|
+
return transitionWorkflowRun(record, {
|
|
216
|
+
state: "stopped",
|
|
217
|
+
progress: record.progress,
|
|
218
|
+
usage: record.usage ?? record.progress.usage ?? { agents: [], totals: emptyWorkflowUsageTotals(), assistantMessages: 0 },
|
|
219
|
+
error: "Workflow stopped by user.",
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
214
223
|
export function captureWorkflowRunResult(value: unknown): WorkflowRunStoredResult {
|
|
215
224
|
try {
|
|
216
225
|
const state = { nodes: 0, bytes: 0, active: new WeakSet<object>() };
|
|
@@ -334,7 +343,7 @@ function isAllowedTransition(from: WorkflowRunState, to: WorkflowRunState): bool
|
|
|
334
343
|
case "running":
|
|
335
344
|
return to === "completed" || to === "failed" || to === "stopped" || to === "paused";
|
|
336
345
|
case "paused":
|
|
337
|
-
return to === "
|
|
346
|
+
return to === "running" || to === "failed" || to === "stopped";
|
|
338
347
|
case "completed":
|
|
339
348
|
case "failed":
|
|
340
349
|
case "stopped":
|
|
@@ -458,7 +467,7 @@ function compactWorkflowRunPause(pause: WorkflowRunPause): WorkflowRunPause {
|
|
|
458
467
|
}
|
|
459
468
|
|
|
460
469
|
function boundedText(value: string): string {
|
|
461
|
-
return value
|
|
470
|
+
return truncateText(value, MAX_PERSISTED_TEXT);
|
|
462
471
|
}
|
|
463
472
|
|
|
464
473
|
export function isWorkflowRunRecord(value: unknown): value is WorkflowRunRecord {
|
|
@@ -617,16 +626,8 @@ function isValidRunId(value: unknown): value is string {
|
|
|
617
626
|
}
|
|
618
627
|
}
|
|
619
628
|
|
|
620
|
-
function isFiniteNumber(value: unknown): value is number {
|
|
621
|
-
return typeof value === "number" && Number.isFinite(value);
|
|
622
|
-
}
|
|
623
|
-
|
|
624
629
|
function isIntegerInRange(value: unknown, min: number, max: number): value is number {
|
|
625
630
|
return typeof value === "number" && Number.isSafeInteger(value) && value >= min && value <= max;
|
|
626
631
|
}
|
|
627
632
|
|
|
628
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
629
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
630
|
-
}
|
|
631
|
-
|
|
632
633
|
class WorkflowRunValueError extends Error {}
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
transitionWorkflowRun,
|
|
9
9
|
updateWorkflowRunProgress,
|
|
10
10
|
type WorkflowRunRecord,
|
|
11
|
+
type WorkflowRunState,
|
|
11
12
|
type WorkflowRunTransition,
|
|
12
13
|
} from "./workflow-run-record.ts";
|
|
13
14
|
|
|
@@ -37,8 +38,9 @@ export class ProjectWorkflowRunStore implements WorkflowRunStore {
|
|
|
37
38
|
try {
|
|
38
39
|
await writeFile(temporaryPath, content, { encoding: "utf8", mode: 0o600 });
|
|
39
40
|
await rename(temporaryPath, path);
|
|
40
|
-
}
|
|
41
|
+
} catch (error) {
|
|
41
42
|
await rm(temporaryPath, { force: true }).catch(() => undefined);
|
|
43
|
+
throw error;
|
|
42
44
|
}
|
|
43
45
|
}
|
|
44
46
|
|
|
@@ -47,43 +49,22 @@ export class ProjectWorkflowRunStore implements WorkflowRunStore {
|
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
async list(): Promise<WorkflowRunRecord[]> {
|
|
50
|
-
const
|
|
51
|
-
let files: string[];
|
|
52
|
-
try {
|
|
53
|
-
files = await readdir(dir);
|
|
54
|
-
} catch {
|
|
55
|
-
return [];
|
|
56
|
-
}
|
|
57
|
-
const records = await Promise.all(
|
|
58
|
-
files
|
|
59
|
-
.filter((file) => file.endsWith(WORKFLOW_RUN_RECORD_SUFFIX))
|
|
60
|
-
.map((file) => loadWorkflowRunRecord(join(dir, file))),
|
|
61
|
-
);
|
|
52
|
+
const records = await Promise.all((await this.recordPaths()).map(loadWorkflowRunRecord));
|
|
62
53
|
return records
|
|
63
54
|
.filter((record): record is WorkflowRunRecord => record !== undefined)
|
|
64
55
|
.sort((left, right) => right.updatedAt - left.updatedAt);
|
|
65
56
|
}
|
|
66
57
|
|
|
67
58
|
async prune(keep = WORKFLOW_RUN_RECORD_KEEP): Promise<void> {
|
|
68
|
-
const dir = join(this.cwd, WORKFLOW_RUNS_DIR);
|
|
69
|
-
let files: string[];
|
|
70
|
-
try {
|
|
71
|
-
files = await readdir(dir);
|
|
72
|
-
} catch {
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
59
|
const candidates = await Promise.all(
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
return undefined;
|
|
85
|
-
}
|
|
86
|
-
}),
|
|
60
|
+
(await this.recordPaths()).map(async (path) => {
|
|
61
|
+
try {
|
|
62
|
+
const info = await stat(path);
|
|
63
|
+
return { path, mtimeMs: info.mtimeMs };
|
|
64
|
+
} catch {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
}),
|
|
87
68
|
);
|
|
88
69
|
const stale = candidates
|
|
89
70
|
.filter((entry): entry is { readonly path: string; readonly mtimeMs: number } => entry !== undefined)
|
|
@@ -91,6 +72,17 @@ export class ProjectWorkflowRunStore implements WorkflowRunStore {
|
|
|
91
72
|
.slice(Math.max(0, keep));
|
|
92
73
|
await Promise.all(stale.map((entry) => rm(entry.path, { force: true }).catch(() => undefined)));
|
|
93
74
|
}
|
|
75
|
+
|
|
76
|
+
/** Record file paths in the runs directory; empty when the directory cannot be read. */
|
|
77
|
+
private async recordPaths(): Promise<string[]> {
|
|
78
|
+
const dir = join(this.cwd, WORKFLOW_RUNS_DIR);
|
|
79
|
+
try {
|
|
80
|
+
const files = await readdir(dir);
|
|
81
|
+
return files.filter((file) => file.endsWith(WORKFLOW_RUN_RECORD_SUFFIX)).map((file) => join(dir, file));
|
|
82
|
+
} catch {
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
}
|
|
94
86
|
}
|
|
95
87
|
|
|
96
88
|
/** Owns one run record and coalesces progress writes while a prior atomic save is in flight. */
|
|
@@ -110,6 +102,10 @@ export class DurableWorkflowRun {
|
|
|
110
102
|
this.queue(initial);
|
|
111
103
|
}
|
|
112
104
|
|
|
105
|
+
get state(): WorkflowRunState {
|
|
106
|
+
return this.record.state;
|
|
107
|
+
}
|
|
108
|
+
|
|
113
109
|
updateProgress(progress: WorkflowProgressSnapshot, at = Date.now()): void {
|
|
114
110
|
this.record = updateWorkflowRunProgress(this.record, progress, at);
|
|
115
111
|
this.queue(this.record);
|
|
@@ -13,7 +13,7 @@ interface ScheduledUsageLimitResume {
|
|
|
13
13
|
readonly handle: unknown;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
const defaultWorkflowUsageLimitSchedulerClock: WorkflowUsageLimitSchedulerClock = {
|
|
17
17
|
now: () => Date.now(),
|
|
18
18
|
setTimeout: (callback, delayMs) => setTimeout(callback, delayMs),
|
|
19
19
|
clearTimeout: (handle) => clearTimeout(handle as ReturnType<typeof setTimeout>),
|
|
@@ -73,8 +73,4 @@ export class WorkflowUsageLimitScheduler {
|
|
|
73
73
|
activateSession(ctx: Pick<ExtensionContext, "sessionManager">): void {
|
|
74
74
|
this.inactiveSessions.delete(ctx.sessionManager.getSessionId());
|
|
75
75
|
}
|
|
76
|
-
|
|
77
|
-
has(runId: string): boolean {
|
|
78
|
-
return this.scheduled.has(runId);
|
|
79
|
-
}
|
|
80
76
|
}
|
|
@@ -12,8 +12,9 @@ import type { WorkflowModule } from "./types.ts";
|
|
|
12
12
|
* pi's bundled `typebox` / SDK — guaranteeing schema validation works.
|
|
13
13
|
*
|
|
14
14
|
* To add a guaranteed workflow: drop a `.ts` in `workflows/`, import it here, and add
|
|
15
|
-
*
|
|
16
|
-
*
|
|
15
|
+
* a `defineBuiltinWorkflow(mod, "<name>.ts")` entry to `BUILTIN_WORKFLOW_DEFINITIONS`.
|
|
16
|
+
* (Drop-in workflows discovered at runtime are also supported — see discovery.ts — but
|
|
17
|
+
* the static registry is the always-correct path.)
|
|
17
18
|
*
|
|
18
19
|
* Do not replace these built-in imports with dynamic imports unless a dedicated test
|
|
19
20
|
* proves pi's jiti virtual modules preserve the same bundled `typebox` identity for
|
|
@@ -26,7 +27,6 @@ export interface BuiltinWorkflowDefinition {
|
|
|
26
27
|
readonly module: WorkflowModule;
|
|
27
28
|
readonly filename: string;
|
|
28
29
|
readonly path: string;
|
|
29
|
-
readonly root: string;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
export const BUILTIN_WORKFLOW_DEFINITIONS: readonly BuiltinWorkflowDefinition[] = [
|
|
@@ -36,15 +36,12 @@ export const BUILTIN_WORKFLOW_DEFINITIONS: readonly BuiltinWorkflowDefinition[]
|
|
|
36
36
|
defineBuiltinWorkflow(perfReview, "perf-review.ts"),
|
|
37
37
|
defineBuiltinWorkflow(research, "research.ts"),
|
|
38
38
|
];
|
|
39
|
-
export const BUILTIN_WORKFLOWS = BUILTIN_WORKFLOW_DEFINITIONS.map(({ module }) => module);
|
|
40
39
|
export const BUILTIN_WORKFLOW_FILES = new Set(BUILTIN_WORKFLOW_DEFINITIONS.map(({ filename }) => filename));
|
|
41
|
-
export const BUILTIN_WORKFLOW_NAMES = BUILTIN_WORKFLOWS.map((mod) => mod.meta.name);
|
|
42
40
|
|
|
43
41
|
function defineBuiltinWorkflow(module: WorkflowModule, filename: string): BuiltinWorkflowDefinition {
|
|
44
42
|
return {
|
|
45
43
|
module: { meta: module.meta, default: module.default },
|
|
46
44
|
filename,
|
|
47
45
|
path: fileURLToPath(new URL(`../workflows/${filename}`, import.meta.url)),
|
|
48
|
-
root: BUILTIN_SOURCE_ROOT,
|
|
49
46
|
};
|
|
50
47
|
}
|