@crewx/workflow 0.3.22-rc.18 → 0.3.22-rc.181
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/SKILL.md +40 -4
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +268 -86
- package/dist/cli.js.map +1 -1
- package/dist/src/card-state.d.ts +20 -0
- package/dist/src/card-state.d.ts.map +1 -0
- package/dist/src/card-state.js +121 -0
- package/dist/src/card-state.js.map +1 -0
- package/dist/src/engine.d.ts +9 -2
- package/dist/src/engine.d.ts.map +1 -1
- package/dist/src/engine.js +70 -22
- package/dist/src/engine.js.map +1 -1
- package/dist/src/failure.d.ts +41 -0
- package/dist/src/failure.d.ts.map +1 -0
- package/dist/src/failure.js +122 -0
- package/dist/src/failure.js.map +1 -0
- package/dist/src/list-view.d.ts +22 -0
- package/dist/src/list-view.d.ts.map +1 -0
- package/dist/src/list-view.js +188 -0
- package/dist/src/list-view.js.map +1 -0
- package/dist/src/mermaid.js +3 -3
- package/dist/src/mermaid.js.map +1 -1
- package/dist/src/parent-task-sync.d.ts +139 -0
- package/dist/src/parent-task-sync.d.ts.map +1 -0
- package/dist/src/parent-task-sync.js +445 -0
- package/dist/src/parent-task-sync.js.map +1 -0
- package/dist/src/retry-policy.d.ts +38 -0
- package/dist/src/retry-policy.d.ts.map +1 -0
- package/dist/src/retry-policy.js +140 -0
- package/dist/src/retry-policy.js.map +1 -0
- package/dist/src/run-manager.d.ts +36 -2
- package/dist/src/run-manager.d.ts.map +1 -1
- package/dist/src/run-manager.js +1195 -234
- package/dist/src/run-manager.js.map +1 -1
- package/dist/src/types.d.ts +37 -3
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -1
- package/dist/src/utils/output-format.d.ts +12 -0
- package/dist/src/utils/output-format.d.ts.map +1 -1
- package/dist/src/utils/output-format.js +103 -5
- package/dist/src/utils/output-format.js.map +1 -1
- package/dist/src/workflow-persistence.d.ts +48 -0
- package/dist/src/workflow-persistence.d.ts.map +1 -0
- package/dist/src/workflow-persistence.js +224 -0
- package/dist/src/workflow-persistence.js.map +1 -0
- package/dist/workflow-schema.json +29 -4
- package/package.json +23 -4
- package/workflow-schema.json +29 -4
package/SKILL.md
CHANGED
|
@@ -221,6 +221,42 @@ analyze:
|
|
|
221
221
|
next: calculate
|
|
222
222
|
```
|
|
223
223
|
|
|
224
|
+
#### agent_task — execution context & failure signaling
|
|
225
|
+
|
|
226
|
+
Every `agent_task` prompt is automatically prefixed with a `<crewx.workflow>` preamble
|
|
227
|
+
telling the agent it runs headless inside a workflow node (workflow ID, node ID, run ID),
|
|
228
|
+
that clarifying questions are impossible, and how to signal failure. Disable with
|
|
229
|
+
`CREWX_WORKFLOW_PREAMBLE=off`.
|
|
230
|
+
|
|
231
|
+
Two semantic-failure channels stop the run at the node (in addition to non-zero exit codes):
|
|
232
|
+
|
|
233
|
+
1. **`workflow_signal` (agent-initiated)** — the agent outputs exactly one JSON object:
|
|
234
|
+
|
|
235
|
+
```json
|
|
236
|
+
{"workflow_signal": "fail", "reason": "build is red — tsc reports 14 errors"}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
The engine detects it (pure JSON or embedded in prose), marks the node failed, and
|
|
240
|
+
records the reason in `run.error`. This is the agent's `exit 1`.
|
|
241
|
+
|
|
242
|
+
2. **`fail_when` (node-declared)** — a safe expression evaluated against state *after*
|
|
243
|
+
the node's output is stored. Truthy → node fails, run halts. The judged output is
|
|
244
|
+
still preserved in state for inspection. No separate `check_*` branch node needed.
|
|
245
|
+
|
|
246
|
+
```yaml
|
|
247
|
+
verify:
|
|
248
|
+
type: agent_task
|
|
249
|
+
agent: core_dev
|
|
250
|
+
output: verify_result
|
|
251
|
+
output_format: json
|
|
252
|
+
fail_when: 'state.verify_result.status !== "pass"'
|
|
253
|
+
next: smoke_test
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
`fail_when` works on `agent_task`, `skill_task`, and `shell_task`. It uses the same
|
|
257
|
+
safe expression subset as `branch` conditions (`includes()`, `===`, `&&`, `Number()`...).
|
|
258
|
+
Use `branch` when failure should route somewhere; use `fail_when` when failure should stop the run.
|
|
259
|
+
|
|
224
260
|
#### agent_task — JSON output mode
|
|
225
261
|
|
|
226
262
|
When `output_format: json`, the engine requires the agent to return a single JSON object.
|
|
@@ -298,7 +334,7 @@ extract_attachments:
|
|
|
298
334
|
| `output_schema` | object | No | Standard JSON Schema 2020-12 for parsed output. |
|
|
299
335
|
| `output_retry` | integer | No | Retry attempts if JSON parse or schema validation fails. |
|
|
300
336
|
|
|
301
|
-
`skill_task` uses `CREWX_CLI` (default: `
|
|
337
|
+
`skill_task` uses `CREWX_CLI` (default: `crewx`) and executes the equivalent of:
|
|
302
338
|
|
|
303
339
|
```bash
|
|
304
340
|
crewx skill <skill> ...args
|
|
@@ -561,7 +597,7 @@ Examples:
|
|
|
561
597
|
| Input | Run behavior |
|
|
562
598
|
|-------|--------------|
|
|
563
599
|
| `/wf rc-publish` | Starts `rc-publish` in auto mode |
|
|
564
|
-
| `/wf rc-publish
|
|
600
|
+
| `/wf rc-publish pre-approved` | Starts `rc-publish` with `state.input = "pre-approved"` |
|
|
565
601
|
| `/wf rc-publish --manual dry run` | Creates the run in manual mode with `state.input = "dry run"` |
|
|
566
602
|
| `/wf rc-publish --auto prepare RC notes` | Explicit auto mode; same as default |
|
|
567
603
|
|
|
@@ -589,7 +625,7 @@ Branches and templates may also read `state.input`:
|
|
|
589
625
|
condition: state.input.includes("dry run")
|
|
590
626
|
```
|
|
591
627
|
|
|
592
|
-
Important security rule: `state.input` is **not authority**. It may influence prompts, branches, and templates, but it must never bypass an `approval` node. For example, `/wf rc-publish
|
|
628
|
+
Important security rule: `state.input` is **not authority**. It may influence prompts, branches, and templates, but it must never bypass an `approval` node. For example, `/wf rc-publish pre-approved` stores the text in `state.input`; it does not approve publishing. Approval still requires the signed ChatCard action button.
|
|
593
629
|
|
|
594
630
|
### Stored ChatCard
|
|
595
631
|
|
|
@@ -645,7 +681,7 @@ Rules:
|
|
|
645
681
|
| Variable | Description | Default |
|
|
646
682
|
|----------|-------------|---------|
|
|
647
683
|
| `CREWX_WORKSPACE` | Workspace directory (where `workflows/` lives) | `.` |
|
|
648
|
-
| `CREWX_CLI` | CrewX CLI path (for `agent_task` and `skill_task`) | `
|
|
684
|
+
| `CREWX_CLI` | CrewX CLI path (for `agent_task` and `skill_task`) | `crewx` |
|
|
649
685
|
| `CREWX_WORKFLOW_TIMEOUT` | `agent_task` execution timeout (ms) | `1800000` (30 min) |
|
|
650
686
|
| `CREWX_WORKFLOW_SHELL` | First safety gate for `shell_task`; must be `1` | unset |
|
|
651
687
|
| `CREWX_CONFIG` | Custom config file path | `crewx.yaml` |
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../cli.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../cli.ts"],"names":[],"mappings":";AAmBA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAS9C,OAAO,KAAK,EAAE,cAAc,EAAgB,YAAY,EAA6C,MAAM,aAAa,CAAC;AA4GzH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAAC;CACrC;AAED,wBAAgB,eAAe,CAC7B,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,MAAM,EAAE,EACf,MAAM,EAAE,cAAc,EACtB,SAAS,CAAC,EAAE,cAAc,EAAE,GAC3B,gBAAgB,CAkDlB;AAg9BD,wBAAgB,kBAAkB,CAChC,WAAW,EAAE,MAAM,EACnB,cAAc,EAAE,MAAM,EACtB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,GAClC,MAAM,GAAG,IAAI,CAoBf;AAMD,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,GAAG,MAAM,EAAE,CAsB1E;AA4CD,wBAAsB,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA2D9D"}
|
package/dist/cli.js
CHANGED
|
@@ -39,11 +39,15 @@ exports.findNextNodeForCAL = findNextNodeForCAL;
|
|
|
39
39
|
exports.getFlowChain = getFlowChain;
|
|
40
40
|
exports.main = main;
|
|
41
41
|
const fs = __importStar(require("fs"));
|
|
42
|
+
const list_view_1 = require("./src/list-view");
|
|
42
43
|
const path = __importStar(require("path"));
|
|
43
44
|
const child_process_1 = require("child_process");
|
|
44
|
-
const
|
|
45
|
+
const sdk_1 = require("@crewx/sdk");
|
|
46
|
+
const repository_1 = require("@crewx/sdk/repository");
|
|
45
47
|
const engine_1 = require("./src/engine");
|
|
46
48
|
const run_manager_1 = require("./src/run-manager");
|
|
49
|
+
const workflow_persistence_1 = require("./src/workflow-persistence");
|
|
50
|
+
const parent_task_sync_1 = require("./src/parent-task-sync");
|
|
47
51
|
const knowledge_core_1 = require("@crewx/knowledge-core");
|
|
48
52
|
let PKG_VERSION = 'unknown';
|
|
49
53
|
try {
|
|
@@ -63,7 +67,9 @@ async function loadTracer() {
|
|
|
63
67
|
return null;
|
|
64
68
|
}
|
|
65
69
|
}
|
|
66
|
-
const BOOLEAN_FLAGS = new Set([
|
|
70
|
+
const BOOLEAN_FLAGS = new Set([
|
|
71
|
+
'help', 'json', 'mermaid', 'version', 'no-exec', 'auto', 'no-run', 'apply', 'dry-run',
|
|
72
|
+
]);
|
|
67
73
|
function parseArgs(args) {
|
|
68
74
|
const result = { positional: [], options: {} };
|
|
69
75
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -108,17 +114,21 @@ function getProjectRoot() {
|
|
|
108
114
|
}
|
|
109
115
|
}
|
|
110
116
|
function getWorkflowConfig() {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
return
|
|
121
|
-
|
|
117
|
+
return (0, list_view_1.readWorkflowConfig)(process.env.CREWX_CONFIG ?? path.join(getProjectRoot(), 'crewx.yaml'));
|
|
118
|
+
}
|
|
119
|
+
function getNodeResultStatus(run, nodeId, nodeType, noExec) {
|
|
120
|
+
if (noExec)
|
|
121
|
+
return null;
|
|
122
|
+
const audit = [...(run.audit ?? [])]
|
|
123
|
+
.reverse()
|
|
124
|
+
.find((entry) => entry.node_id === nodeId && entry.type !== 'retry');
|
|
125
|
+
if (!audit)
|
|
126
|
+
return null;
|
|
127
|
+
if (audit.error || nodeType === 'error')
|
|
128
|
+
return 'failed';
|
|
129
|
+
if (nodeType === 'approval' || nodeType === 'parallel')
|
|
130
|
+
return 'paused';
|
|
131
|
+
return 'completed';
|
|
122
132
|
}
|
|
123
133
|
function validateSlugInput(nameOrPath) {
|
|
124
134
|
if (!nameOrPath.endsWith('.yaml') && !nameOrPath.endsWith('.yml')) {
|
|
@@ -166,21 +176,12 @@ function resolveWorkflow(nameOrPath, paths, engine, preloaded) {
|
|
|
166
176
|
` Hint: use "workflow list" to see all workflow IDs`);
|
|
167
177
|
}
|
|
168
178
|
function loadAllWorkflows(engine, paths) {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
continue;
|
|
176
|
-
for (const wf of workflows) {
|
|
177
|
-
if (!seenIds.has(wf.id)) {
|
|
178
|
-
seenIds.add(wf.id);
|
|
179
|
-
all.push(wf);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
return all;
|
|
179
|
+
return (0, list_view_1.loadWorkflowPaths)(paths, process.cwd());
|
|
180
|
+
}
|
|
181
|
+
function resolveWorkflowSpecForRun(run, engine) {
|
|
182
|
+
const runFile = path.resolve(run.workflow_file);
|
|
183
|
+
const workflows = engine.loadWorkflows(path.dirname(runFile));
|
|
184
|
+
return workflows?.find((workflow) => path.resolve(workflow.filePath) === runFile && workflow.id === run.workflow_id)?.spec;
|
|
184
185
|
}
|
|
185
186
|
function printUsage() {
|
|
186
187
|
console.log(`
|
|
@@ -199,13 +200,18 @@ Commands:
|
|
|
199
200
|
workflow run node <id> <node> Execute node (auto-invoke agent)
|
|
200
201
|
workflow run auto <id> Auto-run from current node until end/pause
|
|
201
202
|
workflow run reset <id> Reset execution
|
|
203
|
+
workflow run retry <id> Retry a failed run from the failed node (--no-run: transition only)
|
|
204
|
+
⚠️ re-executes the failed node; safe only if it is idempotent
|
|
202
205
|
workflow run list List all executions
|
|
206
|
+
workflow repair <run-id> Preview parent-task repair (use --apply to write)
|
|
203
207
|
workflow usage Show detailed guide
|
|
204
208
|
|
|
205
209
|
Options:
|
|
206
210
|
--json JSON output
|
|
207
211
|
--mermaid Output Mermaid diagram (show only)
|
|
208
212
|
--no-exec Move cursor only, skip agent execution (run node)
|
|
213
|
+
--dry-run Preview workflow parent repair without writes (default)
|
|
214
|
+
--apply Apply workflow parent repair under the run lock
|
|
209
215
|
--version Show version
|
|
210
216
|
--help, -h Show this help
|
|
211
217
|
|
|
@@ -261,58 +267,7 @@ function formatFlow(nodes) {
|
|
|
261
267
|
}
|
|
262
268
|
function cmdList(engine, jsonMode) {
|
|
263
269
|
const { paths } = getWorkflowConfig();
|
|
264
|
-
|
|
265
|
-
if (workflows.length === 0) {
|
|
266
|
-
const msg = `No workflow YAML files found in: ${paths.join(', ')}`;
|
|
267
|
-
if (jsonMode) {
|
|
268
|
-
console.log(JSON.stringify({ success: true, message: msg, workflows: [] }));
|
|
269
|
-
}
|
|
270
|
-
else {
|
|
271
|
-
console.log(`\u2139\uFE0F ${msg}`);
|
|
272
|
-
}
|
|
273
|
-
return;
|
|
274
|
-
}
|
|
275
|
-
if (jsonMode) {
|
|
276
|
-
console.log(JSON.stringify({
|
|
277
|
-
success: true,
|
|
278
|
-
workflows: workflows.map(wf => {
|
|
279
|
-
const meta = wf.spec.metadata ?? {};
|
|
280
|
-
const nodeCount = wf.spec.nodes ? Object.keys(wf.spec.nodes).length : 0;
|
|
281
|
-
return {
|
|
282
|
-
id: wf.id,
|
|
283
|
-
name: meta.name ?? wf.id,
|
|
284
|
-
description: meta.description ?? '',
|
|
285
|
-
node_count: nodeCount,
|
|
286
|
-
file: wf.filePath,
|
|
287
|
-
};
|
|
288
|
-
}),
|
|
289
|
-
}, null, 2));
|
|
290
|
-
}
|
|
291
|
-
else {
|
|
292
|
-
console.log(`\uD83D\uDCCB \uC6CC\uD06C\uD50C\uB85C \uBAA9\uB85D`);
|
|
293
|
-
workflows.forEach((wf, i) => {
|
|
294
|
-
const meta = wf.spec.metadata ?? {};
|
|
295
|
-
const nodeCount = wf.spec.nodes ? Object.keys(wf.spec.nodes).length : 0;
|
|
296
|
-
const descLine = meta.description ? `\n \uD83D\uDCDD ${meta.description.trim().split('\n')[0]}` : '';
|
|
297
|
-
console.log(` ${i + 1}. ${wf.id} \u2014 ${meta.name ?? wf.id} (${nodeCount} nodes)\n \uD83D\uDCC1 ${wf.filePath}${descLine}`);
|
|
298
|
-
});
|
|
299
|
-
const firstId = workflows[0]?.id;
|
|
300
|
-
if (firstId) {
|
|
301
|
-
console.log(`\n${'━'.repeat(43)}`);
|
|
302
|
-
console.log(`🔍 워크플로 상세: npx workflow show ${firstId}`);
|
|
303
|
-
console.log(`🔀 워크플로 실행: npx workflow run start <id> --set key="value"`);
|
|
304
|
-
}
|
|
305
|
-
const listCtx = {
|
|
306
|
-
commandId: 'workflow.list',
|
|
307
|
-
params: {},
|
|
308
|
-
resultIds: workflows.slice(0, 3).map(w => w.id),
|
|
309
|
-
resultCount: workflows.length,
|
|
310
|
-
};
|
|
311
|
-
const listActions = (0, knowledge_core_1.generateNextActions)(listCtx);
|
|
312
|
-
const listBlock = (0, knowledge_core_1.formatActions)(listActions);
|
|
313
|
-
if (listBlock)
|
|
314
|
-
console.log(listBlock);
|
|
315
|
-
}
|
|
270
|
+
console.log((0, list_view_1.formatWorkflowList)({ paths, workflows: loadAllWorkflows(engine, paths), jsonMode }));
|
|
316
271
|
}
|
|
317
272
|
function cmdShow(engine, name, jsonMode, mermaidMode) {
|
|
318
273
|
const { paths } = getWorkflowConfig();
|
|
@@ -514,7 +469,7 @@ function cmdValidate(engine, target, jsonMode) {
|
|
|
514
469
|
workflows: flatResults,
|
|
515
470
|
}, null, 2));
|
|
516
471
|
}
|
|
517
|
-
if (!hasErrors) {
|
|
472
|
+
if (!jsonMode && !hasErrors) {
|
|
518
473
|
const calName = resolvedSlug ?? firstValidName;
|
|
519
474
|
if (calName) {
|
|
520
475
|
const valCtx = {
|
|
@@ -531,10 +486,180 @@ function cmdValidate(engine, target, jsonMode) {
|
|
|
531
486
|
if (hasErrors)
|
|
532
487
|
process.exit(1);
|
|
533
488
|
}
|
|
489
|
+
function repairChangeForOutput(change) {
|
|
490
|
+
return {
|
|
491
|
+
task_id: change.id,
|
|
492
|
+
kind: change.kind,
|
|
493
|
+
before: {
|
|
494
|
+
status: change.before.status,
|
|
495
|
+
error: change.before.error,
|
|
496
|
+
completed_at: change.before.completedAt,
|
|
497
|
+
duration_ms: change.before.durationMs,
|
|
498
|
+
},
|
|
499
|
+
after: {
|
|
500
|
+
status: change.after.status,
|
|
501
|
+
error: change.after.error,
|
|
502
|
+
completed_at: change.after.completedAt,
|
|
503
|
+
duration_ms: change.after.durationMs,
|
|
504
|
+
},
|
|
505
|
+
reason: change.reason,
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
function auditSnapshotChangeForOutput(change) {
|
|
509
|
+
return {
|
|
510
|
+
instance_id: change.instanceId,
|
|
511
|
+
before: change.before,
|
|
512
|
+
after: change.after,
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
function reportRepair(preview, mode, jsonMode, projectionErrors = []) {
|
|
516
|
+
const changes = preview.parentChanges.map(repairChangeForOutput);
|
|
517
|
+
const auditCardChanges = preview.auditSnapshotChanges.map(auditSnapshotChangeForOutput);
|
|
518
|
+
const errors = [...preview.errors, ...projectionErrors];
|
|
519
|
+
if (jsonMode) {
|
|
520
|
+
console.log(JSON.stringify({
|
|
521
|
+
success: true,
|
|
522
|
+
exec_id: preview.runId,
|
|
523
|
+
mode,
|
|
524
|
+
dry_run: mode === 'dry-run',
|
|
525
|
+
applied: mode === 'apply',
|
|
526
|
+
run_status: preview.runStatus,
|
|
527
|
+
changes,
|
|
528
|
+
audit_card_changes: auditCardChanges,
|
|
529
|
+
...(errors.length > 0 ? { errors } : {}),
|
|
530
|
+
}, null, 2));
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
console.log(`🔧 Workflow repair (${mode}): ${preview.runId}`);
|
|
534
|
+
console.log(` Run status: ${preview.runStatus}`);
|
|
535
|
+
if (changes.length === 0 && auditCardChanges.length === 0) {
|
|
536
|
+
console.log(' No parent lifecycle changes needed.');
|
|
537
|
+
}
|
|
538
|
+
else {
|
|
539
|
+
for (const change of changes) {
|
|
540
|
+
console.log(` ${mode === 'apply' ? 'Updated' : 'Would update'} ${change.task_id} (${change.kind}): ${change.before.status} → ${change.after.status}`);
|
|
541
|
+
console.log(` Reason: ${change.reason}`);
|
|
542
|
+
}
|
|
543
|
+
for (const change of auditCardChanges) {
|
|
544
|
+
console.log(` ${mode === 'apply' ? 'Updated' : 'Would update'} audit card ${change.instance_id}: ${change.before.phase ?? '(unknown)'} → ${change.after.phase}`);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
for (const error of errors) {
|
|
548
|
+
console.error(` Warning: ${error}`);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
function validateRepairRun(run, execId) {
|
|
552
|
+
if (run.id !== execId) {
|
|
553
|
+
throw new Error(`Execution file ID mismatch: requested ${execId}, found ${run.id || '(missing)'}`);
|
|
554
|
+
}
|
|
555
|
+
if (!run.workflow_id || !run.workflow_file || !run.created_at || !run.updated_at
|
|
556
|
+
|| typeof run.current_node !== 'string'
|
|
557
|
+
|| !Array.isArray(run.completed_nodes)
|
|
558
|
+
|| !run.state || typeof run.state !== 'object'
|
|
559
|
+
|| !run.initial_state || typeof run.initial_state !== 'object') {
|
|
560
|
+
throw new Error(`Run "${execId}" is incomplete and cannot be repaired`);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
function cmdRepair(rest, jsonMode, apply, dryRun) {
|
|
564
|
+
const execId = rest[0];
|
|
565
|
+
if (!execId) {
|
|
566
|
+
const msg = 'Usage: workflow repair <run-id> [--dry-run|--apply]';
|
|
567
|
+
if (jsonMode)
|
|
568
|
+
console.log(JSON.stringify({ success: false, error: msg }));
|
|
569
|
+
else
|
|
570
|
+
console.error(`❌ ${msg}`);
|
|
571
|
+
process.exit(1);
|
|
572
|
+
}
|
|
573
|
+
if (apply && dryRun) {
|
|
574
|
+
const msg = 'Choose either --dry-run or --apply, not both';
|
|
575
|
+
if (jsonMode)
|
|
576
|
+
console.log(JSON.stringify({ success: false, error: msg }));
|
|
577
|
+
else
|
|
578
|
+
console.error(`❌ ${msg}`);
|
|
579
|
+
process.exit(1);
|
|
580
|
+
}
|
|
581
|
+
const projectRoot = getProjectRoot();
|
|
582
|
+
const workspaceId = (0, sdk_1.hashWorkspaceId)(projectRoot);
|
|
583
|
+
const taskRepo = new repository_1.TaskRepository();
|
|
584
|
+
const mgr = new run_manager_1.RunManager(undefined, projectRoot);
|
|
585
|
+
const engine = new engine_1.WorkflowEngine();
|
|
586
|
+
let uiInstanceStore;
|
|
587
|
+
let readOnlyUiInstanceStore;
|
|
588
|
+
const resolveWorkflowSpec = (run) => resolveWorkflowSpecForRun(run, engine);
|
|
589
|
+
const getUiInstanceStore = () => {
|
|
590
|
+
uiInstanceStore ?? (uiInstanceStore = new sdk_1.UiInstanceStore(taskRepo.resolveDbPath()));
|
|
591
|
+
return uiInstanceStore;
|
|
592
|
+
};
|
|
593
|
+
const getReadOnlyUiInstanceStore = () => {
|
|
594
|
+
readOnlyUiInstanceStore ?? (readOnlyUiInstanceStore = (0, sdk_1.openUiInstanceReadStore)(taskRepo.resolveDbPath()));
|
|
595
|
+
return readOnlyUiInstanceStore;
|
|
596
|
+
};
|
|
597
|
+
const persistenceProjection = (0, workflow_persistence_1.createWorkflowPersistenceProjection)({
|
|
598
|
+
taskRepo,
|
|
599
|
+
workspaceId,
|
|
600
|
+
resolveWorkflowSpec,
|
|
601
|
+
getUiInstanceStore,
|
|
602
|
+
});
|
|
603
|
+
const persistencePreviewOptions = {
|
|
604
|
+
taskRepo,
|
|
605
|
+
workspaceId,
|
|
606
|
+
resolveWorkflowSpec,
|
|
607
|
+
getUiInstanceStore: getReadOnlyUiInstanceStore,
|
|
608
|
+
};
|
|
609
|
+
try {
|
|
610
|
+
const observed = mgr.readRun(execId);
|
|
611
|
+
if (!observed)
|
|
612
|
+
throw new Error(`Execution not found: ${execId}`);
|
|
613
|
+
validateRepairRun(observed, execId);
|
|
614
|
+
if (!(0, parent_task_sync_1.deriveWorkflowParentLifecycle)(observed)) {
|
|
615
|
+
throw new Error(`Run "${execId}" has an unsupported or insufficient persisted status: ${observed.status ?? 'pending'}`);
|
|
616
|
+
}
|
|
617
|
+
if (!apply) {
|
|
618
|
+
reportRepair((0, workflow_persistence_1.previewWorkflowPersistenceProjection)(persistencePreviewOptions, observed), 'dry-run', jsonMode);
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
const result = mgr.withRunLock(execId, (latest) => {
|
|
622
|
+
validateRepairRun(latest, execId);
|
|
623
|
+
if (!(0, parent_task_sync_1.deriveWorkflowParentLifecycle)(latest)) {
|
|
624
|
+
throw new Error(`Run "${execId}" has an unsupported or insufficient persisted status: ${latest.status ?? 'pending'}`);
|
|
625
|
+
}
|
|
626
|
+
const preview = (0, workflow_persistence_1.previewWorkflowPersistenceProjection)(persistencePreviewOptions, latest);
|
|
627
|
+
const projection = persistenceProjection(latest);
|
|
628
|
+
return { preview, projection };
|
|
629
|
+
});
|
|
630
|
+
reportRepair(result.preview, 'apply', jsonMode, result.projection.errors);
|
|
631
|
+
}
|
|
632
|
+
catch (e) {
|
|
633
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
634
|
+
if (jsonMode)
|
|
635
|
+
console.log(JSON.stringify({ success: false, error: msg }));
|
|
636
|
+
else
|
|
637
|
+
console.error(`❌ ${msg}`);
|
|
638
|
+
process.exit(1);
|
|
639
|
+
}
|
|
640
|
+
finally {
|
|
641
|
+
uiInstanceStore?.close();
|
|
642
|
+
readOnlyUiInstanceStore?.close();
|
|
643
|
+
}
|
|
644
|
+
}
|
|
534
645
|
async function cmdRun(rest, rawArgs, jsonMode, engine) {
|
|
535
646
|
const runSub = rest[0];
|
|
536
647
|
const runArgs = rest.slice(1);
|
|
537
|
-
const
|
|
648
|
+
const projectRoot = getProjectRoot();
|
|
649
|
+
const taskRepo = new repository_1.TaskRepository();
|
|
650
|
+
let uiInstanceStore;
|
|
651
|
+
const persistenceProjection = (0, workflow_persistence_1.createWorkflowPersistenceProjection)({
|
|
652
|
+
taskRepo,
|
|
653
|
+
workspaceId: (0, sdk_1.hashWorkspaceId)(projectRoot),
|
|
654
|
+
resolveWorkflowSpec: (run) => resolveWorkflowSpecForRun(run, engine),
|
|
655
|
+
getUiInstanceStore: () => {
|
|
656
|
+
uiInstanceStore ?? (uiInstanceStore = new sdk_1.UiInstanceStore(taskRepo.resolveDbPath()));
|
|
657
|
+
return uiInstanceStore;
|
|
658
|
+
},
|
|
659
|
+
});
|
|
660
|
+
const mgr = new run_manager_1.RunManager(undefined, projectRoot, {
|
|
661
|
+
onRunPersisted: persistenceProjection,
|
|
662
|
+
});
|
|
538
663
|
const { paths } = getWorkflowConfig();
|
|
539
664
|
switch (runSub) {
|
|
540
665
|
case 'start': {
|
|
@@ -707,6 +832,7 @@ async function cmdRun(rest, rawArgs, jsonMode, engine) {
|
|
|
707
832
|
const run = noExec
|
|
708
833
|
? mgr.moveNode(execId, nodeId)
|
|
709
834
|
: await mgr.executeNode(execId, nodeId);
|
|
835
|
+
const nodeStatus = getNodeResultStatus(run, nodeId, mgr.getNodeType(run.workflow_file, run.workflow_id, nodeId), noExec);
|
|
710
836
|
if (jsonMode) {
|
|
711
837
|
console.log(JSON.stringify({
|
|
712
838
|
success: true,
|
|
@@ -726,8 +852,8 @@ async function cmdRun(rest, rawArgs, jsonMode, engine) {
|
|
|
726
852
|
console.log(` Completed: ${run.completed_nodes.join(' \u2192 ')}`);
|
|
727
853
|
}
|
|
728
854
|
console.log(` Current: ${run.current_node}`);
|
|
729
|
-
if (
|
|
730
|
-
console.log(` Status: ${
|
|
855
|
+
if (nodeStatus) {
|
|
856
|
+
console.log(` Status: ${nodeStatus}`);
|
|
731
857
|
}
|
|
732
858
|
}
|
|
733
859
|
let nextNode = null;
|
|
@@ -809,6 +935,58 @@ async function cmdRun(rest, rawArgs, jsonMode, engine) {
|
|
|
809
935
|
}
|
|
810
936
|
break;
|
|
811
937
|
}
|
|
938
|
+
case 'retry': {
|
|
939
|
+
const positional = runArgs.filter(a => !a.startsWith('--'));
|
|
940
|
+
const execId = positional[0];
|
|
941
|
+
if (!execId) {
|
|
942
|
+
const msg = 'Usage: workflow run retry <exec-id> [--no-run]';
|
|
943
|
+
if (jsonMode) {
|
|
944
|
+
console.log(JSON.stringify({ success: false, error: msg }));
|
|
945
|
+
}
|
|
946
|
+
else {
|
|
947
|
+
console.error(`❌ ${msg}`);
|
|
948
|
+
}
|
|
949
|
+
process.exit(1);
|
|
950
|
+
}
|
|
951
|
+
const noRun = rawArgs.includes('--no-run');
|
|
952
|
+
try {
|
|
953
|
+
const run = mgr.retry(execId, { autoRun: !noRun });
|
|
954
|
+
if (noRun) {
|
|
955
|
+
if (jsonMode) {
|
|
956
|
+
console.log(JSON.stringify({
|
|
957
|
+
success: true,
|
|
958
|
+
exec_id: run.id,
|
|
959
|
+
current_node: run.current_node,
|
|
960
|
+
retry_count: run.retry_count,
|
|
961
|
+
status: run.status,
|
|
962
|
+
}, null, 2));
|
|
963
|
+
}
|
|
964
|
+
else {
|
|
965
|
+
console.log(`✅ Retry transition: ${execId}`);
|
|
966
|
+
console.log(` Resume node: ${run.current_node}`);
|
|
967
|
+
console.log(` Retry count: ${run.retry_count}`);
|
|
968
|
+
console.log(` (state transition only; run "workflow run auto ${execId}" to execute — re-runs the node, safe only if idempotent)`);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
else {
|
|
972
|
+
const result = await mgr.runAuto(run.id);
|
|
973
|
+
reportAutoResult(result, jsonMode);
|
|
974
|
+
if (result.outcome === 'failed')
|
|
975
|
+
process.exit(1);
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
catch (e) {
|
|
979
|
+
const msg = e.message;
|
|
980
|
+
if (jsonMode) {
|
|
981
|
+
console.log(JSON.stringify({ success: false, error: msg }));
|
|
982
|
+
}
|
|
983
|
+
else {
|
|
984
|
+
console.error(`❌ ${msg}`);
|
|
985
|
+
}
|
|
986
|
+
process.exit(1);
|
|
987
|
+
}
|
|
988
|
+
break;
|
|
989
|
+
}
|
|
812
990
|
case 'list': {
|
|
813
991
|
const runs = mgr.listRuns();
|
|
814
992
|
if (jsonMode) {
|
|
@@ -871,7 +1049,7 @@ async function cmdRun(rest, rawArgs, jsonMode, engine) {
|
|
|
871
1049
|
break;
|
|
872
1050
|
}
|
|
873
1051
|
default: {
|
|
874
|
-
const msg = `Unknown run subcommand: ${runSub || '(none)'}\nUsage: workflow run <start|state|node|auto|reset|list> [args...]`;
|
|
1052
|
+
const msg = `Unknown run subcommand: ${runSub || '(none)'}\nUsage: workflow run <start|state|node|auto|reset|retry|list> [args...]`;
|
|
875
1053
|
if (jsonMode) {
|
|
876
1054
|
console.log(JSON.stringify({ success: false, error: msg }));
|
|
877
1055
|
}
|
|
@@ -884,7 +1062,7 @@ async function cmdRun(rest, rawArgs, jsonMode, engine) {
|
|
|
884
1062
|
}
|
|
885
1063
|
function reportAutoResult(result, jsonMode) {
|
|
886
1064
|
const { run, outcome, reason, executed } = result;
|
|
887
|
-
const status = run.status
|
|
1065
|
+
const status = run.status;
|
|
888
1066
|
if (jsonMode) {
|
|
889
1067
|
console.log(JSON.stringify({
|
|
890
1068
|
success: outcome !== 'failed',
|
|
@@ -1024,6 +1202,10 @@ async function main(inputArgs) {
|
|
|
1024
1202
|
await cmdRun(rest, args, jsonMode, engine);
|
|
1025
1203
|
break;
|
|
1026
1204
|
}
|
|
1205
|
+
case 'repair': {
|
|
1206
|
+
cmdRepair(rest, jsonMode, !!options.apply, !!options['dry-run']);
|
|
1207
|
+
break;
|
|
1208
|
+
}
|
|
1027
1209
|
case 'usage':
|
|
1028
1210
|
showDetailedUsage();
|
|
1029
1211
|
break;
|