@workflow-manager/runner 0.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/README.md +196 -0
- package/dist/engine.d.ts +2 -0
- package/dist/engine.js +217 -0
- package/dist/events.d.ts +7 -0
- package/dist/events.js +21 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +413 -0
- package/dist/mockExecutor.d.ts +2 -0
- package/dist/mockExecutor.js +110 -0
- package/dist/opencodeExecutor.d.ts +3 -0
- package/dist/opencodeExecutor.js +174 -0
- package/dist/parser.d.ts +5 -0
- package/dist/parser.js +101 -0
- package/dist/remote/api.d.ts +99 -0
- package/dist/remote/api.js +81 -0
- package/dist/remote/commands.d.ts +8 -0
- package/dist/remote/commands.js +183 -0
- package/dist/remote/config.d.ts +8 -0
- package/dist/remote/config.js +39 -0
- package/dist/remote/telemetry.d.ts +12 -0
- package/dist/remote/telemetry.js +69 -0
- package/dist/remote/types.d.ts +7 -0
- package/dist/remote/types.js +1 -0
- package/dist/types.d.ts +122 -0
- package/dist/types.js +1 -0
- package/man/wfm.1 +96 -0
- package/package.json +65 -0
- package/skills/README.md +63 -0
- package/skills/workflow-manager-cli/README.md +11 -0
- package/skills/workflow-manager-cli/SKILL.md +111 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
|
+
import { parseWorkflowFile, validateWorkflow } from "./parser.js";
|
|
6
|
+
import { runWorkflow } from "./engine.js";
|
|
7
|
+
import { cmdAuth, cmdPublish, cmdPull, cmdRemoteInfo, cmdSearch } from "./remote/commands.js";
|
|
8
|
+
import { emitRunTelemetryBestEffort } from "./remote/telemetry.js";
|
|
9
|
+
const DISCOVERY_QUESTIONS = [
|
|
10
|
+
"1) What set of workflow objectives should be tracked per run (one or many)?",
|
|
11
|
+
"2) Which steps require human validation vs external validation?",
|
|
12
|
+
"3) Which approvals are mandatory and who can confirm each approval?",
|
|
13
|
+
"4) Which steps require explicit confirmation before proceeding?",
|
|
14
|
+
"5) For each step, which adapter should run it (opencode, codex, claude-code, mock)?",
|
|
15
|
+
"6) What per-step initialization is needed (context, skills, MCPs, system prompts, model)?",
|
|
16
|
+
"7) What output format should this session produce (JSON run report, markdown summary, event timeline)?",
|
|
17
|
+
"8) What retry/rollback policy should be default and where should exceptions apply?",
|
|
18
|
+
];
|
|
19
|
+
function usage() {
|
|
20
|
+
console.log(`wfm commands:
|
|
21
|
+
questions
|
|
22
|
+
scaffold [path] [--format markdown|json]
|
|
23
|
+
validate <workflow.md|workflow.json>
|
|
24
|
+
run <workflow.md|workflow.json> [--input input.json] [--objective "string"] [--confirm stepA,stepB:human] [--auto-confirm-all]
|
|
25
|
+
auth <login|whoami|logout> [--token <token>]
|
|
26
|
+
publish <workflow.md|workflow.json> [--slug slug] [--title title] [--description text] [--visibility public|private] [--version version] [--tag a,b] [--draft]
|
|
27
|
+
pull <owner/slug> [--version version] [--output path]
|
|
28
|
+
search [query]
|
|
29
|
+
remote info <owner/slug>
|
|
30
|
+
man`);
|
|
31
|
+
}
|
|
32
|
+
function getFlag(name) {
|
|
33
|
+
const idx = process.argv.indexOf(name);
|
|
34
|
+
if (idx >= 0 && idx + 1 < process.argv.length)
|
|
35
|
+
return process.argv[idx + 1];
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
function hasFlag(name) {
|
|
39
|
+
return process.argv.includes(name);
|
|
40
|
+
}
|
|
41
|
+
function cmdQuestions() {
|
|
42
|
+
console.log("Project definition questions:");
|
|
43
|
+
for (const q of DISCOVERY_QUESTIONS)
|
|
44
|
+
console.log(`- ${q}`);
|
|
45
|
+
console.log("\nExpected output of this session:");
|
|
46
|
+
console.log("- Finalized workflow file (markdown or json) with per-step objectives");
|
|
47
|
+
console.log("- Validation/approval confirmation map per step");
|
|
48
|
+
console.log("- Adapter init map (opencode/codex/claude-code with context+skills+mcps)");
|
|
49
|
+
console.log("- Runnable CLI command examples + run JSON output");
|
|
50
|
+
}
|
|
51
|
+
const WORKFLOW_SCAFFOLD_JSON = {
|
|
52
|
+
key: "workflow-manager-sample",
|
|
53
|
+
title: "Workflow Manager Sample",
|
|
54
|
+
description: "Workflow definition with per-step objectives and confirmations",
|
|
55
|
+
objectives: ["deliver a working implementation", "ensure validation and approvals are explicit"],
|
|
56
|
+
inputSchema: {
|
|
57
|
+
type: "object",
|
|
58
|
+
properties: {
|
|
59
|
+
ticket: { type: "string" },
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
outputSchema: {
|
|
63
|
+
type: "object",
|
|
64
|
+
},
|
|
65
|
+
defaultRetryPolicy: {
|
|
66
|
+
maxAttempts: 2,
|
|
67
|
+
},
|
|
68
|
+
steps: [
|
|
69
|
+
{
|
|
70
|
+
key: "discover",
|
|
71
|
+
kind: "task",
|
|
72
|
+
objective: "Understand requirements and constraints",
|
|
73
|
+
dependsOn: [],
|
|
74
|
+
validation: { mode: "human", required: true, autoConfirm: false },
|
|
75
|
+
taskSpec: {
|
|
76
|
+
adapterKey: "opencode",
|
|
77
|
+
init: {
|
|
78
|
+
context: { repo: "example/repo" },
|
|
79
|
+
skills: ["architecture", "planning"],
|
|
80
|
+
mcps: ["mcp://github", "mcp://docs"],
|
|
81
|
+
systemPrompts: ["Focus on architecture trade-offs"],
|
|
82
|
+
model: "openrouter/anthropic/claude-sonnet-4",
|
|
83
|
+
},
|
|
84
|
+
payload: { mockResult: "success" },
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
key: "qa_gate",
|
|
89
|
+
kind: "approval",
|
|
90
|
+
objective: "Human product review approval",
|
|
91
|
+
dependsOn: ["discover"],
|
|
92
|
+
approvalSpec: {
|
|
93
|
+
autoApprove: false,
|
|
94
|
+
validation: { mode: "human", required: true, autoConfirm: false },
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
key: "implement",
|
|
99
|
+
kind: "task",
|
|
100
|
+
objective: "Implement agreed changes",
|
|
101
|
+
dependsOn: ["qa_gate"],
|
|
102
|
+
validation: { mode: "external", required: true, autoConfirm: false },
|
|
103
|
+
retryPolicy: { maxAttempts: 2 },
|
|
104
|
+
taskSpec: {
|
|
105
|
+
adapterKey: "codex",
|
|
106
|
+
init: {
|
|
107
|
+
context: { language: "typescript" },
|
|
108
|
+
skills: ["coding", "testing"],
|
|
109
|
+
mcps: ["mcp://repo", "mcp://ci"],
|
|
110
|
+
systemPrompts: ["Write tests with implementation"],
|
|
111
|
+
},
|
|
112
|
+
payload: { mockResult: "success" },
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
key: "hardening",
|
|
117
|
+
kind: "task",
|
|
118
|
+
objective: "Final hardening checks with Claude Code",
|
|
119
|
+
dependsOn: ["implement"],
|
|
120
|
+
validation: { mode: "external", required: true, autoConfirm: false },
|
|
121
|
+
taskSpec: {
|
|
122
|
+
adapterKey: "claude-code",
|
|
123
|
+
init: {
|
|
124
|
+
context: { quality: "high" },
|
|
125
|
+
skills: ["security-review", "refactoring"],
|
|
126
|
+
mcps: ["mcp://security"],
|
|
127
|
+
systemPrompts: ["Prioritize correctness and readability"],
|
|
128
|
+
},
|
|
129
|
+
payload: { mockResult: "success" },
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
],
|
|
133
|
+
};
|
|
134
|
+
const WORKFLOW_SCAFFOLD_MARKDOWN = `---
|
|
135
|
+
key: workflow-manager-sample
|
|
136
|
+
title: Workflow Manager Sample
|
|
137
|
+
description: Workflow definition with per-step objectives and confirmations
|
|
138
|
+
objectives:
|
|
139
|
+
- deliver a working implementation
|
|
140
|
+
- ensure validation and approvals are explicit
|
|
141
|
+
inputSchema:
|
|
142
|
+
type: object
|
|
143
|
+
properties:
|
|
144
|
+
ticket:
|
|
145
|
+
type: string
|
|
146
|
+
outputSchema:
|
|
147
|
+
type: object
|
|
148
|
+
defaultRetryPolicy:
|
|
149
|
+
maxAttempts: 2
|
|
150
|
+
steps:
|
|
151
|
+
- key: discover
|
|
152
|
+
kind: task
|
|
153
|
+
objective: Understand requirements and constraints
|
|
154
|
+
dependsOn: []
|
|
155
|
+
validation:
|
|
156
|
+
mode: human
|
|
157
|
+
required: true
|
|
158
|
+
autoConfirm: false
|
|
159
|
+
taskSpec:
|
|
160
|
+
adapterKey: opencode
|
|
161
|
+
init:
|
|
162
|
+
context:
|
|
163
|
+
repo: example/repo
|
|
164
|
+
skills: [architecture, planning]
|
|
165
|
+
mcps: [mcp://github, mcp://docs]
|
|
166
|
+
systemPrompts: [Focus on architecture trade-offs]
|
|
167
|
+
model: openrouter/anthropic/claude-sonnet-4
|
|
168
|
+
payload:
|
|
169
|
+
mockResult: success
|
|
170
|
+
- key: qa_gate
|
|
171
|
+
kind: approval
|
|
172
|
+
objective: Human product review approval
|
|
173
|
+
dependsOn: [discover]
|
|
174
|
+
approvalSpec:
|
|
175
|
+
autoApprove: false
|
|
176
|
+
validation:
|
|
177
|
+
mode: human
|
|
178
|
+
required: true
|
|
179
|
+
autoConfirm: false
|
|
180
|
+
- key: implement
|
|
181
|
+
kind: task
|
|
182
|
+
objective: Implement agreed changes
|
|
183
|
+
dependsOn: [qa_gate]
|
|
184
|
+
validation:
|
|
185
|
+
mode: external
|
|
186
|
+
required: true
|
|
187
|
+
autoConfirm: false
|
|
188
|
+
retryPolicy:
|
|
189
|
+
maxAttempts: 2
|
|
190
|
+
taskSpec:
|
|
191
|
+
adapterKey: codex
|
|
192
|
+
init:
|
|
193
|
+
context:
|
|
194
|
+
language: typescript
|
|
195
|
+
skills: [coding, testing]
|
|
196
|
+
mcps: [mcp://repo, mcp://ci]
|
|
197
|
+
systemPrompts: [Write tests with implementation]
|
|
198
|
+
payload:
|
|
199
|
+
mockResult: success
|
|
200
|
+
- key: hardening
|
|
201
|
+
kind: task
|
|
202
|
+
objective: Final hardening checks with Claude Code
|
|
203
|
+
dependsOn: [implement]
|
|
204
|
+
validation:
|
|
205
|
+
mode: external
|
|
206
|
+
required: true
|
|
207
|
+
autoConfirm: false
|
|
208
|
+
taskSpec:
|
|
209
|
+
adapterKey: claude-code
|
|
210
|
+
init:
|
|
211
|
+
context:
|
|
212
|
+
quality: high
|
|
213
|
+
skills: [security-review, refactoring]
|
|
214
|
+
mcps: [mcp://security]
|
|
215
|
+
systemPrompts: [Prioritize correctness and readability]
|
|
216
|
+
payload:
|
|
217
|
+
mockResult: success
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
# Workflow Notes
|
|
221
|
+
|
|
222
|
+
Edit frontmatter to configure orchestration behavior.
|
|
223
|
+
`;
|
|
224
|
+
function resolveScaffoldFormat(targetPath, explicitFormat) {
|
|
225
|
+
if (explicitFormat === "markdown" || explicitFormat === "json") {
|
|
226
|
+
return explicitFormat;
|
|
227
|
+
}
|
|
228
|
+
return path.extname(targetPath).toLowerCase() === ".json" ? "json" : "markdown";
|
|
229
|
+
}
|
|
230
|
+
function parseScaffoldArgs(args) {
|
|
231
|
+
let targetPath;
|
|
232
|
+
let format;
|
|
233
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
234
|
+
const arg = args[i];
|
|
235
|
+
if (arg === "--format") {
|
|
236
|
+
format = args[i + 1];
|
|
237
|
+
i += 1;
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
if (!arg.startsWith("-") && !targetPath) {
|
|
241
|
+
targetPath = arg;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return { targetPath, format };
|
|
245
|
+
}
|
|
246
|
+
function cmdScaffold(targetPath, format) {
|
|
247
|
+
const resolvedPath = targetPath ? path.resolve(targetPath) : path.resolve("./example-workflow.md");
|
|
248
|
+
const normalizedFormat = format?.toLowerCase();
|
|
249
|
+
const resolvedFormat = resolveScaffoldFormat(resolvedPath, normalizedFormat);
|
|
250
|
+
if (normalizedFormat && resolvedFormat !== normalizedFormat) {
|
|
251
|
+
console.error(`Invalid --format value: ${format}. Use markdown or json.`);
|
|
252
|
+
return 1;
|
|
253
|
+
}
|
|
254
|
+
const template = resolvedFormat === "json"
|
|
255
|
+
? `${JSON.stringify(WORKFLOW_SCAFFOLD_JSON, null, 2)}\n`
|
|
256
|
+
: WORKFLOW_SCAFFOLD_MARKDOWN;
|
|
257
|
+
fs.writeFileSync(resolvedPath, template, "utf-8");
|
|
258
|
+
console.log(`Scaffolded ${resolvedFormat} workflow: ${resolvedPath}`);
|
|
259
|
+
return 0;
|
|
260
|
+
}
|
|
261
|
+
function cmdMan() {
|
|
262
|
+
const manPagePath = path.resolve("./man/wfm.1");
|
|
263
|
+
if (!fs.existsSync(manPagePath)) {
|
|
264
|
+
console.error(`Man page not found at ${manPagePath}`);
|
|
265
|
+
return 1;
|
|
266
|
+
}
|
|
267
|
+
const result = spawnSync("man", [manPagePath], { stdio: "inherit" });
|
|
268
|
+
if (result.status === 0) {
|
|
269
|
+
return 0;
|
|
270
|
+
}
|
|
271
|
+
console.log("\n' man ' command unavailable, printing page contents:\n");
|
|
272
|
+
console.log(fs.readFileSync(manPagePath, "utf-8"));
|
|
273
|
+
return 0;
|
|
274
|
+
}
|
|
275
|
+
function cmdValidate(filePath) {
|
|
276
|
+
try {
|
|
277
|
+
const workflow = parseWorkflowFile(path.resolve(filePath));
|
|
278
|
+
const errors = validateWorkflow(workflow);
|
|
279
|
+
if (errors.length > 0) {
|
|
280
|
+
console.log("Validation failed:");
|
|
281
|
+
for (const e of errors)
|
|
282
|
+
console.log(`- ${e}`);
|
|
283
|
+
return 1;
|
|
284
|
+
}
|
|
285
|
+
console.log("Validation OK");
|
|
286
|
+
return 0;
|
|
287
|
+
}
|
|
288
|
+
catch (err) {
|
|
289
|
+
console.error(`Validation error: ${err.message}`);
|
|
290
|
+
return 1;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
async function cmdRun(filePath) {
|
|
294
|
+
const resolvedPath = path.resolve(filePath);
|
|
295
|
+
const startedAt = Date.now();
|
|
296
|
+
let workflow;
|
|
297
|
+
try {
|
|
298
|
+
workflow = parseWorkflowFile(resolvedPath);
|
|
299
|
+
const errors = validateWorkflow(workflow);
|
|
300
|
+
if (errors.length > 0) {
|
|
301
|
+
console.error(`Invalid workflow: ${errors.join("; ")}`);
|
|
302
|
+
await emitRunTelemetryBestEffort({
|
|
303
|
+
definition: workflow,
|
|
304
|
+
sourceFilePath: resolvedPath,
|
|
305
|
+
durationMs: Date.now() - startedAt,
|
|
306
|
+
failureReason: errors.join("; "),
|
|
307
|
+
});
|
|
308
|
+
return 1;
|
|
309
|
+
}
|
|
310
|
+
const objective = getFlag("--objective");
|
|
311
|
+
const inputPath = getFlag("--input");
|
|
312
|
+
const input = inputPath ? JSON.parse(fs.readFileSync(path.resolve(inputPath), "utf-8")) : {};
|
|
313
|
+
const confirmRaw = getFlag("--confirm") ?? "";
|
|
314
|
+
const confirmations = confirmRaw
|
|
315
|
+
.split(",")
|
|
316
|
+
.map((x) => x.trim())
|
|
317
|
+
.filter(Boolean);
|
|
318
|
+
const result = runWorkflow(workflow, {
|
|
319
|
+
objective,
|
|
320
|
+
input,
|
|
321
|
+
confirmations,
|
|
322
|
+
autoConfirmAll: hasFlag("--auto-confirm-all"),
|
|
323
|
+
});
|
|
324
|
+
console.log(JSON.stringify(result, null, 2));
|
|
325
|
+
await emitRunTelemetryBestEffort({
|
|
326
|
+
definition: workflow,
|
|
327
|
+
sourceFilePath: resolvedPath,
|
|
328
|
+
durationMs: Date.now() - startedAt,
|
|
329
|
+
result,
|
|
330
|
+
failureReason: result.status === "failed" ? "run failed" : result.status === "waiting_for_approval" ? "confirmation required" : undefined,
|
|
331
|
+
});
|
|
332
|
+
return result.status === "succeeded" ? 0 : 2;
|
|
333
|
+
}
|
|
334
|
+
catch (err) {
|
|
335
|
+
console.error(`Run error: ${err.message}`);
|
|
336
|
+
if (workflow) {
|
|
337
|
+
await emitRunTelemetryBestEffort({
|
|
338
|
+
definition: workflow,
|
|
339
|
+
sourceFilePath: resolvedPath,
|
|
340
|
+
durationMs: Date.now() - startedAt,
|
|
341
|
+
failureReason: err.message,
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
return 1;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
async function main() {
|
|
348
|
+
const cmd = process.argv[2];
|
|
349
|
+
if (!cmd || cmd === "-h" || cmd === "--help") {
|
|
350
|
+
usage();
|
|
351
|
+
process.exit(0);
|
|
352
|
+
}
|
|
353
|
+
if (cmd === "questions") {
|
|
354
|
+
cmdQuestions();
|
|
355
|
+
process.exit(0);
|
|
356
|
+
}
|
|
357
|
+
if (cmd === "scaffold") {
|
|
358
|
+
const { targetPath, format } = parseScaffoldArgs(process.argv.slice(3));
|
|
359
|
+
process.exit(cmdScaffold(targetPath, format));
|
|
360
|
+
}
|
|
361
|
+
if (cmd === "man") {
|
|
362
|
+
process.exit(cmdMan());
|
|
363
|
+
}
|
|
364
|
+
if (cmd === "validate") {
|
|
365
|
+
const file = process.argv[3];
|
|
366
|
+
if (!file) {
|
|
367
|
+
usage();
|
|
368
|
+
process.exit(1);
|
|
369
|
+
}
|
|
370
|
+
process.exit(cmdValidate(file));
|
|
371
|
+
}
|
|
372
|
+
if (cmd === "run") {
|
|
373
|
+
const file = process.argv[3];
|
|
374
|
+
if (!file) {
|
|
375
|
+
usage();
|
|
376
|
+
process.exit(1);
|
|
377
|
+
}
|
|
378
|
+
process.exit(await cmdRun(file));
|
|
379
|
+
}
|
|
380
|
+
if (cmd === "auth") {
|
|
381
|
+
process.exit(await cmdAuth(process.argv.slice(3)));
|
|
382
|
+
}
|
|
383
|
+
if (cmd === "search") {
|
|
384
|
+
process.exit(await cmdSearch(process.argv.slice(3)));
|
|
385
|
+
}
|
|
386
|
+
if (cmd === "publish") {
|
|
387
|
+
const file = process.argv[3];
|
|
388
|
+
if (!file) {
|
|
389
|
+
usage();
|
|
390
|
+
process.exit(1);
|
|
391
|
+
}
|
|
392
|
+
process.exit(await cmdPublish(file, process.argv.slice(4)));
|
|
393
|
+
}
|
|
394
|
+
if (cmd === "pull") {
|
|
395
|
+
const reference = process.argv[3];
|
|
396
|
+
if (!reference) {
|
|
397
|
+
usage();
|
|
398
|
+
process.exit(1);
|
|
399
|
+
}
|
|
400
|
+
process.exit(await cmdPull(reference, process.argv.slice(4)));
|
|
401
|
+
}
|
|
402
|
+
if (cmd === "remote" && process.argv[3] === "info") {
|
|
403
|
+
const reference = process.argv[4];
|
|
404
|
+
if (!reference) {
|
|
405
|
+
usage();
|
|
406
|
+
process.exit(1);
|
|
407
|
+
}
|
|
408
|
+
process.exit(await cmdRemoteInfo(reference));
|
|
409
|
+
}
|
|
410
|
+
usage();
|
|
411
|
+
process.exit(1);
|
|
412
|
+
}
|
|
413
|
+
void main();
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
function asRecord(value) {
|
|
2
|
+
return value && typeof value === "object" ? value : {};
|
|
3
|
+
}
|
|
4
|
+
function chapterOutputs(previousOutput) {
|
|
5
|
+
return Object.values(previousOutput)
|
|
6
|
+
.map((entry) => asRecord(entry))
|
|
7
|
+
.filter((entry) => typeof entry.chapterMarkdown === "string");
|
|
8
|
+
}
|
|
9
|
+
export function executeMockStep(step, input, attempt) {
|
|
10
|
+
const start = Date.now();
|
|
11
|
+
const payload = asRecord(step.taskSpec?.payload);
|
|
12
|
+
const mockResult = String(payload.mockResult ?? "success").toLowerCase();
|
|
13
|
+
const feedback = String(payload.feedback ?? "");
|
|
14
|
+
const make = (status, action, extraPayload = {}, feedbackReason = feedback) => ({
|
|
15
|
+
step_id: step.key,
|
|
16
|
+
execution_status: status,
|
|
17
|
+
qa_routing: {
|
|
18
|
+
action,
|
|
19
|
+
feedback_reason: feedbackReason,
|
|
20
|
+
},
|
|
21
|
+
mutated_payload: {
|
|
22
|
+
stepKey: step.key,
|
|
23
|
+
attempt,
|
|
24
|
+
objective: input.step_context.step_objective,
|
|
25
|
+
adapter: input.priming_configuration.adapter ?? "mock",
|
|
26
|
+
priming: {
|
|
27
|
+
skills: input.priming_configuration.required_skills,
|
|
28
|
+
mcps: input.priming_configuration.mcp_endpoints,
|
|
29
|
+
},
|
|
30
|
+
mockResult,
|
|
31
|
+
...extraPayload,
|
|
32
|
+
},
|
|
33
|
+
metadata: {
|
|
34
|
+
execution_time_ms: Date.now() - start,
|
|
35
|
+
external_intervention_required: status === "YIELD_EXTERNAL",
|
|
36
|
+
intervention_details: status === "YIELD_EXTERNAL" ? { reason: "Awaiting human/external validation" } : undefined,
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
if (step.kind === "approval") {
|
|
40
|
+
const autoApprove = step.approvalSpec?.autoApprove ?? false;
|
|
41
|
+
return autoApprove ? make("SUCCESS", "PROCEED") : make("YIELD_EXTERNAL", "PROCEED");
|
|
42
|
+
}
|
|
43
|
+
if (typeof payload.storyChapter === "number") {
|
|
44
|
+
const chapterNumber = payload.storyChapter;
|
|
45
|
+
const prompt = String(input.global_context.global_state.storyRequest ?? input.global_context.primary_objective ?? "a bunny story");
|
|
46
|
+
const paragraph = chapterNumber === 1
|
|
47
|
+
? `A curious bunny set out at sunrise to solve the puzzle in ${prompt}.`
|
|
48
|
+
: `By sunset, the bunny used what it learned to finish ${prompt} with courage and kindness.`;
|
|
49
|
+
return make("SUCCESS", "PROCEED", {
|
|
50
|
+
chapterNumber,
|
|
51
|
+
chapterTitle: `Chapter ${chapterNumber}`,
|
|
52
|
+
paragraph,
|
|
53
|
+
chapterMarkdown: `## Chapter ${chapterNumber}\n\n${paragraph}`,
|
|
54
|
+
storyPrompt: prompt,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
if (payload.validateStory === true) {
|
|
58
|
+
const expectedChapters = Number(payload.requiredChapters ?? 2);
|
|
59
|
+
const previousOutput = asRecord(input.step_context.previous_output);
|
|
60
|
+
const chapters = chapterOutputs(previousOutput)
|
|
61
|
+
.map((entry) => String(entry.chapterMarkdown))
|
|
62
|
+
.join("\n\n");
|
|
63
|
+
const chapterMatches = chapters.match(/^## Chapter\s+\d+/gm) ?? [];
|
|
64
|
+
const hasBunnyTheme = /bunny/i.test(chapters);
|
|
65
|
+
const isValid = chapterMatches.length === expectedChapters && hasBunnyTheme;
|
|
66
|
+
if (!isValid) {
|
|
67
|
+
return make("QA_REJECTED", "RETRY_CURRENT", {
|
|
68
|
+
expectedChapters,
|
|
69
|
+
foundChapters: chapterMatches.length,
|
|
70
|
+
hasBunnyTheme,
|
|
71
|
+
validationPassed: false,
|
|
72
|
+
}, `Story must include exactly ${expectedChapters} chapters and bunny-themed content`);
|
|
73
|
+
}
|
|
74
|
+
return make("SUCCESS", "PROCEED", {
|
|
75
|
+
expectedChapters,
|
|
76
|
+
foundChapters: chapterMatches.length,
|
|
77
|
+
hasBunnyTheme,
|
|
78
|
+
validationPassed: true,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
if (payload.renderStoryMarkdown === true) {
|
|
82
|
+
const previousOutput = asRecord(input.step_context.previous_output);
|
|
83
|
+
const chapters = chapterOutputs(previousOutput)
|
|
84
|
+
.sort((a, b) => Number(a.chapterNumber ?? 0) - Number(b.chapterNumber ?? 0))
|
|
85
|
+
.map((entry) => String(entry.chapterMarkdown));
|
|
86
|
+
if (chapters.length === 0) {
|
|
87
|
+
return make("QA_REJECTED", "RETRY_CURRENT", { chapterCount: 0 }, "No chapters available to render");
|
|
88
|
+
}
|
|
89
|
+
const title = String(payload.storyTitle ?? input.global_context.global_state.storyRequest ?? "Bunny Story");
|
|
90
|
+
const storyMarkdown = `# ${title}\n\n${chapters.join("\n\n")}`;
|
|
91
|
+
return make("SUCCESS", "PROCEED", {
|
|
92
|
+
chapterCount: chapters.length,
|
|
93
|
+
storyMarkdown,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
switch (mockResult) {
|
|
97
|
+
case "retry":
|
|
98
|
+
return make("QA_REJECTED", "RETRY_CURRENT");
|
|
99
|
+
case "rollback":
|
|
100
|
+
return make("QA_REJECTED", "ROLLBACK_PREVIOUS");
|
|
101
|
+
case "restart":
|
|
102
|
+
return make("QA_REJECTED", "RESTART_ALL");
|
|
103
|
+
case "yield":
|
|
104
|
+
return make("YIELD_EXTERNAL", "PROCEED");
|
|
105
|
+
case "fail":
|
|
106
|
+
return make("FAILED", "PROCEED");
|
|
107
|
+
default:
|
|
108
|
+
return make("SUCCESS", "PROCEED");
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { InputEnvelope, OutputEnvelope, StepDefinition } from "./types.js";
|
|
2
|
+
export declare function shouldUseRealOpencode(step: StepDefinition): boolean;
|
|
3
|
+
export declare function executeOpencodeStep(step: StepDefinition, input: InputEnvelope, attempt: number): OutputEnvelope;
|