@workflow-manager/runner 0.1.0 → 0.3.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.
Files changed (40) hide show
  1. package/README.md +123 -12
  2. package/dist/adapters.d.ts +4 -0
  3. package/dist/adapters.js +11 -0
  4. package/dist/claudeCodeExecutor.d.ts +4 -0
  5. package/dist/claudeCodeExecutor.js +173 -0
  6. package/dist/cliRunRenderer.d.ts +36 -0
  7. package/dist/cliRunRenderer.js +286 -0
  8. package/dist/engine.d.ts +4 -2
  9. package/dist/engine.js +622 -44
  10. package/dist/events.d.ts +1 -1
  11. package/dist/events.js +4 -2
  12. package/dist/index.js +371 -41
  13. package/dist/manPage.d.ts +1 -0
  14. package/dist/manPage.js +147 -0
  15. package/dist/mockExecutor.d.ts +2 -2
  16. package/dist/mockExecutor.js +62 -13
  17. package/dist/opencodeExecutor.d.ts +2 -2
  18. package/dist/opencodeExecutor.js +101 -138
  19. package/dist/parser.js +98 -2
  20. package/dist/piAgentExecutor.d.ts +4 -0
  21. package/dist/piAgentExecutor.js +298 -0
  22. package/dist/remote/api.d.ts +1 -0
  23. package/dist/remote/commands.d.ts +2 -0
  24. package/dist/remote/commands.js +76 -4
  25. package/dist/runnerApi.d.ts +7 -0
  26. package/dist/runnerApi.js +221 -0
  27. package/dist/runnerSession.d.ts +62 -0
  28. package/dist/runnerSession.js +260 -0
  29. package/dist/runtimePreflight.d.ts +16 -0
  30. package/dist/runtimePreflight.js +189 -0
  31. package/dist/skillResolver.d.ts +6 -0
  32. package/dist/skillResolver.js +75 -0
  33. package/dist/types.d.ts +148 -2
  34. package/man/wfm.1 +54 -4
  35. package/package.json +28 -4
  36. package/skills/commit-discipline/SKILL.md +109 -0
  37. package/skills/doc-sync/SKILL.md +83 -0
  38. package/skills/repo-hygiene/SKILL.md +70 -0
  39. package/skills/spec-driven-development/SKILL.md +33 -0
  40. package/skills/workflow-manager-cli/SKILL.md +14 -9
@@ -0,0 +1,286 @@
1
+ function isTerminalStatus(status) {
2
+ return status === "succeeded" || status === "failed" || status === "cancelled";
3
+ }
4
+ function formatCount(value, singular, plural) {
5
+ return `${value} ${value === 1 ? singular : plural}`;
6
+ }
7
+ function formatDurationMs(value) {
8
+ if (!Number.isFinite(value) || value < 0) {
9
+ return "0s";
10
+ }
11
+ if (value < 1000) {
12
+ return `${Math.max(0, Math.round(value))}ms`;
13
+ }
14
+ const seconds = value / 1000;
15
+ if (seconds < 60) {
16
+ return `${seconds.toFixed(seconds < 10 ? 1 : 0)}s`;
17
+ }
18
+ const minutes = Math.floor(seconds / 60);
19
+ const remainingSeconds = Math.round(seconds % 60);
20
+ return `${minutes}m ${remainingSeconds}s`;
21
+ }
22
+ function elapsedFrom(iso) {
23
+ if (!iso) {
24
+ return "0s";
25
+ }
26
+ const startedAt = Date.parse(iso);
27
+ if (Number.isNaN(startedAt)) {
28
+ return "0s";
29
+ }
30
+ return formatDurationMs(Date.now() - startedAt);
31
+ }
32
+ function splitLines(value) {
33
+ const normalized = value.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
34
+ const parts = normalized.split("\n");
35
+ const remainder = parts.pop() ?? "";
36
+ return { lines: parts, remainder };
37
+ }
38
+ function describeAgentStatus(status) {
39
+ switch (status) {
40
+ case "running":
41
+ return "started";
42
+ case "succeeded":
43
+ return "succeeded";
44
+ case "failed":
45
+ return "failed";
46
+ case "waiting_for_approval":
47
+ return "waiting for approval";
48
+ case "cancelled":
49
+ return "cancelled";
50
+ default:
51
+ return status.replace(/_/g, " ");
52
+ }
53
+ }
54
+ export class CliRunRenderer {
55
+ verbose;
56
+ stream;
57
+ heartbeatMs;
58
+ steps = new Map();
59
+ lineBuffers = new Map();
60
+ lastSnapshot = null;
61
+ lastStepDetails = new Map();
62
+ heartbeat = null;
63
+ promptPauseCount = 0;
64
+ started = false;
65
+ closed = false;
66
+ constructor(options) {
67
+ this.verbose = options.verbose;
68
+ this.stream = options.stream ?? process.stderr;
69
+ this.heartbeatMs = options.heartbeatMs ?? 1000;
70
+ options.workflow.steps.forEach((step, index) => {
71
+ this.steps.set(step.key, { index, label: stepLabel(step) });
72
+ });
73
+ }
74
+ onSnapshot(snapshot, stepDetails) {
75
+ if (this.closed) {
76
+ return;
77
+ }
78
+ const previous = this.lastSnapshot;
79
+ this.lastSnapshot = snapshot;
80
+ this.lastStepDetails = new Map(stepDetails.map((detail) => [detail.stepKey, detail]));
81
+ if ((snapshot.status === "running" || snapshot.status === "waiting_for_approval") && !this.started) {
82
+ this.started = true;
83
+ this.write(`▶ Running workflow "${snapshot.workflowTitle}" (${formatCount(snapshot.steps.length, "step", "steps")})`);
84
+ }
85
+ this.syncHeartbeat(snapshot.status);
86
+ for (const step of snapshot.steps) {
87
+ const prevStatus = previous?.steps.find((entry) => entry.stepKey === step.stepKey)?.status;
88
+ if (prevStatus !== step.status) {
89
+ this.renderStepStatus(snapshot, step.stepKey, step.status);
90
+ }
91
+ }
92
+ if (previous?.status !== snapshot.status && snapshot.status === "waiting_for_approval" && snapshot.waitingForApproval) {
93
+ const waiting = snapshot.waitingForApproval;
94
+ const stepSummary = this.stepSummary(snapshot, waiting.stepKey);
95
+ const validation = waiting.validation ? ` (${waiting.validation})` : "";
96
+ this.write(`◌ ${stepSummary} waiting for approval${validation} - ${this.remainingText(snapshot)}`);
97
+ if (waiting.preview) {
98
+ this.write(` Approval summary: ${waiting.preview.summary}`);
99
+ for (const item of waiting.preview.items) {
100
+ const status = item.status ? ` [${item.status}]` : "";
101
+ this.write(` - ${item.title}${status}: ${item.summary}`);
102
+ }
103
+ }
104
+ this.write(" Resolve it in the terminal prompt or with `wfm approve` / `wfm cancel` using the attach API shown above.");
105
+ }
106
+ if (isTerminalStatus(snapshot.status)) {
107
+ this.stopHeartbeat();
108
+ this.flushBuffers();
109
+ this.closed = true;
110
+ }
111
+ }
112
+ onEvent(event) {
113
+ if (this.closed || !this.verbose) {
114
+ return;
115
+ }
116
+ if (event.type === "agent.started") {
117
+ const stepSummary = this.stepSummary(this.lastSnapshot, event.stepRunId);
118
+ const command = typeof event.payload.command === "string" ? event.payload.command : "agent";
119
+ const args = Array.isArray(event.payload.args) ? event.payload.args.map((value) => String(value)).join(" ") : "";
120
+ const model = typeof event.payload.model === "string" ? ` model=${event.payload.model}` : "";
121
+ this.write(` ${stepSummary} agent started: ${command}${args ? ` ${args}` : ""}${model}`);
122
+ return;
123
+ }
124
+ if (event.type === "agent.finished") {
125
+ const stepSummary = this.stepSummary(this.lastSnapshot, event.stepRunId);
126
+ const executionStatus = typeof event.payload.executionStatus === "string" ? ` ${event.payload.executionStatus}` : "";
127
+ const exitStatus = typeof event.payload.exitStatus === "number" || event.payload.exitStatus === null
128
+ ? ` exit=${String(event.payload.exitStatus)}`
129
+ : "";
130
+ const timedOut = event.payload.timedOut === true ? " timedOut=true" : "";
131
+ this.write(` ${stepSummary} agent finished:${executionStatus}${exitStatus}${timedOut}`);
132
+ return;
133
+ }
134
+ if (event.type === "step.execution_finished") {
135
+ const stepSummary = this.stepSummary(this.lastSnapshot, event.stepRunId);
136
+ const status = typeof event.payload.status === "string" ? event.payload.status : "unknown";
137
+ const action = typeof event.payload.action === "string" ? ` action=${event.payload.action}` : "";
138
+ const feedbackReason = typeof event.payload.feedbackReason === "string" && event.payload.feedbackReason.length > 0
139
+ ? ` reason=${event.payload.feedbackReason}`
140
+ : "";
141
+ this.write(` ${stepSummary} execution finished: ${status}${action}${feedbackReason}`);
142
+ return;
143
+ }
144
+ if (event.type === "step.retried") {
145
+ const stepSummary = this.stepSummary(this.lastSnapshot, event.stepRunId);
146
+ const attempt = typeof event.payload.attempt === "number" ? ` next attempt ${event.payload.attempt}` : " retry queued";
147
+ this.write(` ${stepSummary}${attempt}`);
148
+ }
149
+ }
150
+ onLog(log) {
151
+ if (this.closed || !this.verbose) {
152
+ return;
153
+ }
154
+ const stepKey = log.stepKey ?? "workflow";
155
+ const bufferKey = `${stepKey}:${log.stream}`;
156
+ const existing = this.lineBuffers.get(bufferKey) ?? "";
157
+ const { lines, remainder } = splitLines(existing + log.text);
158
+ this.lineBuffers.set(bufferKey, remainder);
159
+ for (const line of lines) {
160
+ this.write(` [${stepKey} ${log.stream}] ${line}`);
161
+ }
162
+ }
163
+ close() {
164
+ if (this.closed) {
165
+ return;
166
+ }
167
+ this.stopHeartbeat();
168
+ this.flushBuffers();
169
+ this.closed = true;
170
+ }
171
+ pauseHeartbeat() {
172
+ this.promptPauseCount += 1;
173
+ this.stopHeartbeat();
174
+ }
175
+ resumeHeartbeat() {
176
+ this.promptPauseCount = Math.max(0, this.promptPauseCount - 1);
177
+ if (this.promptPauseCount === 0 && this.lastSnapshot) {
178
+ this.syncHeartbeat(this.lastSnapshot.status);
179
+ }
180
+ }
181
+ renderStepStatus(snapshot, stepKey, status) {
182
+ if (!this.verbose && status === "runnable") {
183
+ return;
184
+ }
185
+ if (!this.verbose && status === "pending") {
186
+ return;
187
+ }
188
+ const summary = this.stepSummary(snapshot, stepKey);
189
+ const detail = this.lastStepDetails.get(stepKey);
190
+ if (status === "running") {
191
+ const adapter = detail?.adapter ? ` (${detail.adapter})` : "";
192
+ this.write(`→ ${summary} ${describeAgentStatus(status)}${adapter} - ${this.remainingText(snapshot)}`);
193
+ return;
194
+ }
195
+ if (status === "succeeded") {
196
+ const duration = detail?.startedAt ? ` in ${elapsedFrom(detail.startedAt)}` : "";
197
+ this.write(`✓ ${summary} ${describeAgentStatus(status)}${duration} - ${this.remainingText(snapshot)}`);
198
+ return;
199
+ }
200
+ if (status === "failed") {
201
+ const duration = detail?.startedAt ? ` after ${elapsedFrom(detail.startedAt)}` : "";
202
+ this.write(`✗ ${summary} ${describeAgentStatus(status)}${duration} - ${this.remainingText(snapshot)}`);
203
+ return;
204
+ }
205
+ if (status === "cancelled") {
206
+ this.write(`✗ ${summary} ${describeAgentStatus(status)} - ${this.remainingText(snapshot)}`);
207
+ return;
208
+ }
209
+ if (status === "waiting_for_approval") {
210
+ this.write(`◌ ${summary} ${describeAgentStatus(status)} - ${this.remainingText(snapshot)}`);
211
+ return;
212
+ }
213
+ this.write(`… ${summary} ${describeAgentStatus(status)} - ${this.remainingText(snapshot)}`);
214
+ }
215
+ stepSummary(snapshot, stepKey) {
216
+ if (!stepKey) {
217
+ return "[workflow]";
218
+ }
219
+ const descriptor = this.steps.get(stepKey);
220
+ const index = descriptor?.index ?? Math.max(0, snapshot?.steps.findIndex((step) => step.stepKey === stepKey) ?? 0);
221
+ const total = snapshot?.steps.length ?? this.steps.size;
222
+ const label = descriptor?.label ?? stepKey;
223
+ return `[${index + 1}/${Math.max(total, 1)}] ${stepKey}${label === stepKey ? "" : ` - ${label}`}`;
224
+ }
225
+ remainingText(snapshot) {
226
+ const completed = snapshot.steps.filter((step) => step.status === "succeeded").length;
227
+ const remaining = Math.max(snapshot.steps.length - completed, 0);
228
+ return `${formatCount(remaining, "step remaining", "steps remaining")} - workflow ${elapsedFrom(snapshot.startedAt)}`;
229
+ }
230
+ syncHeartbeat(status) {
231
+ if (this.promptPauseCount > 0) {
232
+ this.stopHeartbeat();
233
+ return;
234
+ }
235
+ if (status === "running" || status === "waiting_for_approval") {
236
+ if (!this.heartbeat) {
237
+ this.heartbeat = setInterval(() => {
238
+ this.renderHeartbeat();
239
+ }, this.heartbeatMs);
240
+ }
241
+ return;
242
+ }
243
+ this.stopHeartbeat();
244
+ }
245
+ stopHeartbeat() {
246
+ if (!this.heartbeat) {
247
+ return;
248
+ }
249
+ clearInterval(this.heartbeat);
250
+ this.heartbeat = null;
251
+ }
252
+ renderHeartbeat() {
253
+ if (!this.lastSnapshot || this.closed) {
254
+ return;
255
+ }
256
+ const snapshot = this.lastSnapshot;
257
+ if (snapshot.status === "running" && snapshot.currentStepKey) {
258
+ this.write(`.. ${this.stepSummary(snapshot, snapshot.currentStepKey)} running - ${this.remainingText(snapshot)}`);
259
+ return;
260
+ }
261
+ if (snapshot.status === "waiting_for_approval" && snapshot.waitingForApproval) {
262
+ const waiting = snapshot.waitingForApproval;
263
+ this.write(`.. ${this.stepSummary(snapshot, waiting.stepKey)} waiting - ${this.remainingText(snapshot)}`);
264
+ return;
265
+ }
266
+ if (snapshot.status === "running") {
267
+ this.write(`.. workflow running - ${this.remainingText(snapshot)}`);
268
+ }
269
+ }
270
+ flushBuffers() {
271
+ for (const [bufferKey, text] of this.lineBuffers.entries()) {
272
+ if (!text) {
273
+ continue;
274
+ }
275
+ const [stepKey, stream] = bufferKey.split(":");
276
+ this.write(` [${stepKey} ${stream}] ${text}`);
277
+ }
278
+ this.lineBuffers.clear();
279
+ }
280
+ write(line) {
281
+ this.stream.write(`${line}\n`);
282
+ }
283
+ }
284
+ function stepLabel(step) {
285
+ return step.title ?? step.objective ?? step.key;
286
+ }
package/dist/engine.d.ts CHANGED
@@ -1,2 +1,4 @@
1
- import type { RunOptions, RunResult, WorkflowDefinition } from "./types.js";
2
- export declare function runWorkflow(definition: WorkflowDefinition, options?: RunOptions): RunResult;
1
+ import type { ApprovalPreview, ApprovalDecisionPayload, RunOptions, RunResult, StepDefinition, ValidationMode, WorkflowDefinition } from "./types.js";
2
+ export declare function canUseInteractiveConfirmation(step: StepDefinition): boolean;
3
+ export declare function promptForApprovalDecision(stepKey: string, reason: string, validation: ValidationMode, preview: ApprovalPreview | null, actor: string, signal?: AbortSignal): Promise<ApprovalDecisionPayload | null>;
4
+ export declare function runWorkflow(definition: WorkflowDefinition, options?: RunOptions): Promise<RunResult>;