@crewx/workflow 0.3.22-rc.14 → 0.3.22-rc.141
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 +265 -14
- 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 +68 -2
- 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/mermaid.d.ts.map +1 -1
- package/dist/src/mermaid.js +7 -4
- package/dist/src/mermaid.js.map +1 -1
- package/dist/src/parent-task-sync.d.ts +120 -0
- package/dist/src/parent-task-sync.d.ts.map +1 -0
- package/dist/src/parent-task-sync.js +307 -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 +35 -2
- package/dist/src/run-manager.d.ts.map +1 -1
- package/dist/src/run-manager.js +1189 -235
- package/dist/src/run-manager.js.map +1 -1
- package/dist/src/types.d.ts +39 -4
- 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 +34 -5
- package/package.json +14 -4
- package/workflow-schema.json +34 -5
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":";AAkBA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAS9C,OAAO,KAAK,EAAE,cAAc,EAAgB,YAAY,EAA6C,MAAM,aAAa,CAAC;AAqHzH,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;AA2gCD,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
|
@@ -42,8 +42,12 @@ const fs = __importStar(require("fs"));
|
|
|
42
42
|
const path = __importStar(require("path"));
|
|
43
43
|
const child_process_1 = require("child_process");
|
|
44
44
|
const yaml = __importStar(require("js-yaml"));
|
|
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++) {
|
|
@@ -120,6 +126,20 @@ function getWorkflowConfig() {
|
|
|
120
126
|
return { paths: ['workflows'] };
|
|
121
127
|
}
|
|
122
128
|
}
|
|
129
|
+
function getNodeResultStatus(run, nodeId, nodeType, noExec) {
|
|
130
|
+
if (noExec)
|
|
131
|
+
return null;
|
|
132
|
+
const audit = [...(run.audit ?? [])]
|
|
133
|
+
.reverse()
|
|
134
|
+
.find((entry) => entry.node_id === nodeId && entry.type !== 'retry');
|
|
135
|
+
if (!audit)
|
|
136
|
+
return null;
|
|
137
|
+
if (audit.error || nodeType === 'error')
|
|
138
|
+
return 'failed';
|
|
139
|
+
if (nodeType === 'approval' || nodeType === 'parallel')
|
|
140
|
+
return 'paused';
|
|
141
|
+
return 'completed';
|
|
142
|
+
}
|
|
123
143
|
function validateSlugInput(nameOrPath) {
|
|
124
144
|
if (!nameOrPath.endsWith('.yaml') && !nameOrPath.endsWith('.yml')) {
|
|
125
145
|
if (/[/\\]|\.\./.test(nameOrPath)) {
|
|
@@ -182,6 +202,11 @@ function loadAllWorkflows(engine, paths) {
|
|
|
182
202
|
}
|
|
183
203
|
return all;
|
|
184
204
|
}
|
|
205
|
+
function resolveWorkflowSpecForRun(run, engine) {
|
|
206
|
+
const runFile = path.resolve(run.workflow_file);
|
|
207
|
+
const workflows = engine.loadWorkflows(path.dirname(runFile));
|
|
208
|
+
return workflows?.find((workflow) => path.resolve(workflow.filePath) === runFile && workflow.id === run.workflow_id)?.spec;
|
|
209
|
+
}
|
|
185
210
|
function printUsage() {
|
|
186
211
|
console.log(`
|
|
187
212
|
@crewx/workflow@${PKG_VERSION} - Workflow management for CrewX
|
|
@@ -199,13 +224,18 @@ Commands:
|
|
|
199
224
|
workflow run node <id> <node> Execute node (auto-invoke agent)
|
|
200
225
|
workflow run auto <id> Auto-run from current node until end/pause
|
|
201
226
|
workflow run reset <id> Reset execution
|
|
227
|
+
workflow run retry <id> Retry a failed run from the failed node (--no-run: transition only)
|
|
228
|
+
⚠️ re-executes the failed node; safe only if it is idempotent
|
|
202
229
|
workflow run list List all executions
|
|
230
|
+
workflow repair <run-id> Preview parent-task repair (use --apply to write)
|
|
203
231
|
workflow usage Show detailed guide
|
|
204
232
|
|
|
205
233
|
Options:
|
|
206
234
|
--json JSON output
|
|
207
235
|
--mermaid Output Mermaid diagram (show only)
|
|
208
236
|
--no-exec Move cursor only, skip agent execution (run node)
|
|
237
|
+
--dry-run Preview workflow parent repair without writes (default)
|
|
238
|
+
--apply Apply workflow parent repair under the run lock
|
|
209
239
|
--version Show version
|
|
210
240
|
--help, -h Show this help
|
|
211
241
|
|
|
@@ -289,19 +319,13 @@ function cmdList(engine, jsonMode) {
|
|
|
289
319
|
}, null, 2));
|
|
290
320
|
}
|
|
291
321
|
else {
|
|
292
|
-
console.log(
|
|
322
|
+
console.log('📋 Workflows');
|
|
293
323
|
workflows.forEach((wf, i) => {
|
|
294
324
|
const meta = wf.spec.metadata ?? {};
|
|
295
325
|
const nodeCount = wf.spec.nodes ? Object.keys(wf.spec.nodes).length : 0;
|
|
296
326
|
const descLine = meta.description ? `\n \uD83D\uDCDD ${meta.description.trim().split('\n')[0]}` : '';
|
|
297
327
|
console.log(` ${i + 1}. ${wf.id} \u2014 ${meta.name ?? wf.id} (${nodeCount} nodes)\n \uD83D\uDCC1 ${wf.filePath}${descLine}`);
|
|
298
328
|
});
|
|
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
329
|
const listCtx = {
|
|
306
330
|
commandId: 'workflow.list',
|
|
307
331
|
params: {},
|
|
@@ -514,7 +538,7 @@ function cmdValidate(engine, target, jsonMode) {
|
|
|
514
538
|
workflows: flatResults,
|
|
515
539
|
}, null, 2));
|
|
516
540
|
}
|
|
517
|
-
if (!hasErrors) {
|
|
541
|
+
if (!jsonMode && !hasErrors) {
|
|
518
542
|
const calName = resolvedSlug ?? firstValidName;
|
|
519
543
|
if (calName) {
|
|
520
544
|
const valCtx = {
|
|
@@ -531,10 +555,180 @@ function cmdValidate(engine, target, jsonMode) {
|
|
|
531
555
|
if (hasErrors)
|
|
532
556
|
process.exit(1);
|
|
533
557
|
}
|
|
558
|
+
function repairChangeForOutput(change) {
|
|
559
|
+
return {
|
|
560
|
+
task_id: change.id,
|
|
561
|
+
kind: change.kind,
|
|
562
|
+
before: {
|
|
563
|
+
status: change.before.status,
|
|
564
|
+
error: change.before.error,
|
|
565
|
+
completed_at: change.before.completedAt,
|
|
566
|
+
duration_ms: change.before.durationMs,
|
|
567
|
+
},
|
|
568
|
+
after: {
|
|
569
|
+
status: change.after.status,
|
|
570
|
+
error: change.after.error,
|
|
571
|
+
completed_at: change.after.completedAt,
|
|
572
|
+
duration_ms: change.after.durationMs,
|
|
573
|
+
},
|
|
574
|
+
reason: change.reason,
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
function auditSnapshotChangeForOutput(change) {
|
|
578
|
+
return {
|
|
579
|
+
instance_id: change.instanceId,
|
|
580
|
+
before: change.before,
|
|
581
|
+
after: change.after,
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
function reportRepair(preview, mode, jsonMode, projectionErrors = []) {
|
|
585
|
+
const changes = preview.parentChanges.map(repairChangeForOutput);
|
|
586
|
+
const auditCardChanges = preview.auditSnapshotChanges.map(auditSnapshotChangeForOutput);
|
|
587
|
+
const errors = [...preview.errors, ...projectionErrors];
|
|
588
|
+
if (jsonMode) {
|
|
589
|
+
console.log(JSON.stringify({
|
|
590
|
+
success: true,
|
|
591
|
+
exec_id: preview.runId,
|
|
592
|
+
mode,
|
|
593
|
+
dry_run: mode === 'dry-run',
|
|
594
|
+
applied: mode === 'apply',
|
|
595
|
+
run_status: preview.runStatus,
|
|
596
|
+
changes,
|
|
597
|
+
audit_card_changes: auditCardChanges,
|
|
598
|
+
...(errors.length > 0 ? { errors } : {}),
|
|
599
|
+
}, null, 2));
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
console.log(`🔧 Workflow repair (${mode}): ${preview.runId}`);
|
|
603
|
+
console.log(` Run status: ${preview.runStatus}`);
|
|
604
|
+
if (changes.length === 0 && auditCardChanges.length === 0) {
|
|
605
|
+
console.log(' No parent lifecycle changes needed.');
|
|
606
|
+
}
|
|
607
|
+
else {
|
|
608
|
+
for (const change of changes) {
|
|
609
|
+
console.log(` ${mode === 'apply' ? 'Updated' : 'Would update'} ${change.task_id} (${change.kind}): ${change.before.status} → ${change.after.status}`);
|
|
610
|
+
console.log(` Reason: ${change.reason}`);
|
|
611
|
+
}
|
|
612
|
+
for (const change of auditCardChanges) {
|
|
613
|
+
console.log(` ${mode === 'apply' ? 'Updated' : 'Would update'} audit card ${change.instance_id}: ${change.before.phase ?? '(unknown)'} → ${change.after.phase}`);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
for (const error of errors) {
|
|
617
|
+
console.error(` Warning: ${error}`);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
function validateRepairRun(run, execId) {
|
|
621
|
+
if (run.id !== execId) {
|
|
622
|
+
throw new Error(`Execution file ID mismatch: requested ${execId}, found ${run.id || '(missing)'}`);
|
|
623
|
+
}
|
|
624
|
+
if (!run.workflow_id || !run.workflow_file || !run.created_at || !run.updated_at
|
|
625
|
+
|| typeof run.current_node !== 'string'
|
|
626
|
+
|| !Array.isArray(run.completed_nodes)
|
|
627
|
+
|| !run.state || typeof run.state !== 'object'
|
|
628
|
+
|| !run.initial_state || typeof run.initial_state !== 'object') {
|
|
629
|
+
throw new Error(`Run "${execId}" is incomplete and cannot be repaired`);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
function cmdRepair(rest, jsonMode, apply, dryRun) {
|
|
633
|
+
const execId = rest[0];
|
|
634
|
+
if (!execId) {
|
|
635
|
+
const msg = 'Usage: workflow repair <run-id> [--dry-run|--apply]';
|
|
636
|
+
if (jsonMode)
|
|
637
|
+
console.log(JSON.stringify({ success: false, error: msg }));
|
|
638
|
+
else
|
|
639
|
+
console.error(`❌ ${msg}`);
|
|
640
|
+
process.exit(1);
|
|
641
|
+
}
|
|
642
|
+
if (apply && dryRun) {
|
|
643
|
+
const msg = 'Choose either --dry-run or --apply, not both';
|
|
644
|
+
if (jsonMode)
|
|
645
|
+
console.log(JSON.stringify({ success: false, error: msg }));
|
|
646
|
+
else
|
|
647
|
+
console.error(`❌ ${msg}`);
|
|
648
|
+
process.exit(1);
|
|
649
|
+
}
|
|
650
|
+
const projectRoot = getProjectRoot();
|
|
651
|
+
const workspaceId = (0, sdk_1.hashWorkspaceId)(projectRoot);
|
|
652
|
+
const taskRepo = new repository_1.TaskRepository();
|
|
653
|
+
const mgr = new run_manager_1.RunManager(undefined, projectRoot);
|
|
654
|
+
const engine = new engine_1.WorkflowEngine();
|
|
655
|
+
let uiInstanceStore;
|
|
656
|
+
let readOnlyUiInstanceStore;
|
|
657
|
+
const resolveWorkflowSpec = (run) => resolveWorkflowSpecForRun(run, engine);
|
|
658
|
+
const getUiInstanceStore = () => {
|
|
659
|
+
uiInstanceStore ?? (uiInstanceStore = new sdk_1.UiInstanceStore(taskRepo.resolveDbPath()));
|
|
660
|
+
return uiInstanceStore;
|
|
661
|
+
};
|
|
662
|
+
const getReadOnlyUiInstanceStore = () => {
|
|
663
|
+
readOnlyUiInstanceStore ?? (readOnlyUiInstanceStore = (0, sdk_1.openUiInstanceReadStore)(taskRepo.resolveDbPath()));
|
|
664
|
+
return readOnlyUiInstanceStore;
|
|
665
|
+
};
|
|
666
|
+
const persistenceProjection = (0, workflow_persistence_1.createWorkflowPersistenceProjection)({
|
|
667
|
+
taskRepo,
|
|
668
|
+
workspaceId,
|
|
669
|
+
resolveWorkflowSpec,
|
|
670
|
+
getUiInstanceStore,
|
|
671
|
+
});
|
|
672
|
+
const persistencePreviewOptions = {
|
|
673
|
+
taskRepo,
|
|
674
|
+
workspaceId,
|
|
675
|
+
resolveWorkflowSpec,
|
|
676
|
+
getUiInstanceStore: getReadOnlyUiInstanceStore,
|
|
677
|
+
};
|
|
678
|
+
try {
|
|
679
|
+
const observed = mgr.readRun(execId);
|
|
680
|
+
if (!observed)
|
|
681
|
+
throw new Error(`Execution not found: ${execId}`);
|
|
682
|
+
validateRepairRun(observed, execId);
|
|
683
|
+
if (!(0, parent_task_sync_1.deriveWorkflowParentLifecycle)(observed)) {
|
|
684
|
+
throw new Error(`Run "${execId}" has an unsupported or insufficient persisted status: ${observed.status ?? 'pending'}`);
|
|
685
|
+
}
|
|
686
|
+
if (!apply) {
|
|
687
|
+
reportRepair((0, workflow_persistence_1.previewWorkflowPersistenceProjection)(persistencePreviewOptions, observed), 'dry-run', jsonMode);
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
const result = mgr.withRunLock(execId, (latest) => {
|
|
691
|
+
validateRepairRun(latest, execId);
|
|
692
|
+
if (!(0, parent_task_sync_1.deriveWorkflowParentLifecycle)(latest)) {
|
|
693
|
+
throw new Error(`Run "${execId}" has an unsupported or insufficient persisted status: ${latest.status ?? 'pending'}`);
|
|
694
|
+
}
|
|
695
|
+
const preview = (0, workflow_persistence_1.previewWorkflowPersistenceProjection)(persistencePreviewOptions, latest);
|
|
696
|
+
const projection = persistenceProjection(latest);
|
|
697
|
+
return { preview, projection };
|
|
698
|
+
});
|
|
699
|
+
reportRepair(result.preview, 'apply', jsonMode, result.projection.errors);
|
|
700
|
+
}
|
|
701
|
+
catch (e) {
|
|
702
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
703
|
+
if (jsonMode)
|
|
704
|
+
console.log(JSON.stringify({ success: false, error: msg }));
|
|
705
|
+
else
|
|
706
|
+
console.error(`❌ ${msg}`);
|
|
707
|
+
process.exit(1);
|
|
708
|
+
}
|
|
709
|
+
finally {
|
|
710
|
+
uiInstanceStore?.close();
|
|
711
|
+
readOnlyUiInstanceStore?.close();
|
|
712
|
+
}
|
|
713
|
+
}
|
|
534
714
|
async function cmdRun(rest, rawArgs, jsonMode, engine) {
|
|
535
715
|
const runSub = rest[0];
|
|
536
716
|
const runArgs = rest.slice(1);
|
|
537
|
-
const
|
|
717
|
+
const projectRoot = getProjectRoot();
|
|
718
|
+
const taskRepo = new repository_1.TaskRepository();
|
|
719
|
+
let uiInstanceStore;
|
|
720
|
+
const persistenceProjection = (0, workflow_persistence_1.createWorkflowPersistenceProjection)({
|
|
721
|
+
taskRepo,
|
|
722
|
+
workspaceId: (0, sdk_1.hashWorkspaceId)(projectRoot),
|
|
723
|
+
resolveWorkflowSpec: (run) => resolveWorkflowSpecForRun(run, engine),
|
|
724
|
+
getUiInstanceStore: () => {
|
|
725
|
+
uiInstanceStore ?? (uiInstanceStore = new sdk_1.UiInstanceStore(taskRepo.resolveDbPath()));
|
|
726
|
+
return uiInstanceStore;
|
|
727
|
+
},
|
|
728
|
+
});
|
|
729
|
+
const mgr = new run_manager_1.RunManager(undefined, projectRoot, {
|
|
730
|
+
onRunPersisted: persistenceProjection,
|
|
731
|
+
});
|
|
538
732
|
const { paths } = getWorkflowConfig();
|
|
539
733
|
switch (runSub) {
|
|
540
734
|
case 'start': {
|
|
@@ -707,6 +901,7 @@ async function cmdRun(rest, rawArgs, jsonMode, engine) {
|
|
|
707
901
|
const run = noExec
|
|
708
902
|
? mgr.moveNode(execId, nodeId)
|
|
709
903
|
: await mgr.executeNode(execId, nodeId);
|
|
904
|
+
const nodeStatus = getNodeResultStatus(run, nodeId, mgr.getNodeType(run.workflow_file, run.workflow_id, nodeId), noExec);
|
|
710
905
|
if (jsonMode) {
|
|
711
906
|
console.log(JSON.stringify({
|
|
712
907
|
success: true,
|
|
@@ -726,8 +921,8 @@ async function cmdRun(rest, rawArgs, jsonMode, engine) {
|
|
|
726
921
|
console.log(` Completed: ${run.completed_nodes.join(' \u2192 ')}`);
|
|
727
922
|
}
|
|
728
923
|
console.log(` Current: ${run.current_node}`);
|
|
729
|
-
if (
|
|
730
|
-
console.log(` Status: ${
|
|
924
|
+
if (nodeStatus) {
|
|
925
|
+
console.log(` Status: ${nodeStatus}`);
|
|
731
926
|
}
|
|
732
927
|
}
|
|
733
928
|
let nextNode = null;
|
|
@@ -809,6 +1004,58 @@ async function cmdRun(rest, rawArgs, jsonMode, engine) {
|
|
|
809
1004
|
}
|
|
810
1005
|
break;
|
|
811
1006
|
}
|
|
1007
|
+
case 'retry': {
|
|
1008
|
+
const positional = runArgs.filter(a => !a.startsWith('--'));
|
|
1009
|
+
const execId = positional[0];
|
|
1010
|
+
if (!execId) {
|
|
1011
|
+
const msg = 'Usage: workflow run retry <exec-id> [--no-run]';
|
|
1012
|
+
if (jsonMode) {
|
|
1013
|
+
console.log(JSON.stringify({ success: false, error: msg }));
|
|
1014
|
+
}
|
|
1015
|
+
else {
|
|
1016
|
+
console.error(`❌ ${msg}`);
|
|
1017
|
+
}
|
|
1018
|
+
process.exit(1);
|
|
1019
|
+
}
|
|
1020
|
+
const noRun = rawArgs.includes('--no-run');
|
|
1021
|
+
try {
|
|
1022
|
+
const run = mgr.retry(execId, { autoRun: !noRun });
|
|
1023
|
+
if (noRun) {
|
|
1024
|
+
if (jsonMode) {
|
|
1025
|
+
console.log(JSON.stringify({
|
|
1026
|
+
success: true,
|
|
1027
|
+
exec_id: run.id,
|
|
1028
|
+
current_node: run.current_node,
|
|
1029
|
+
retry_count: run.retry_count,
|
|
1030
|
+
status: run.status,
|
|
1031
|
+
}, null, 2));
|
|
1032
|
+
}
|
|
1033
|
+
else {
|
|
1034
|
+
console.log(`✅ Retry transition: ${execId}`);
|
|
1035
|
+
console.log(` Resume node: ${run.current_node}`);
|
|
1036
|
+
console.log(` Retry count: ${run.retry_count}`);
|
|
1037
|
+
console.log(` (state transition only; run "workflow run auto ${execId}" to execute — re-runs the node, safe only if idempotent)`);
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
else {
|
|
1041
|
+
const result = await mgr.runAuto(run.id);
|
|
1042
|
+
reportAutoResult(result, jsonMode);
|
|
1043
|
+
if (result.outcome === 'failed')
|
|
1044
|
+
process.exit(1);
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
catch (e) {
|
|
1048
|
+
const msg = e.message;
|
|
1049
|
+
if (jsonMode) {
|
|
1050
|
+
console.log(JSON.stringify({ success: false, error: msg }));
|
|
1051
|
+
}
|
|
1052
|
+
else {
|
|
1053
|
+
console.error(`❌ ${msg}`);
|
|
1054
|
+
}
|
|
1055
|
+
process.exit(1);
|
|
1056
|
+
}
|
|
1057
|
+
break;
|
|
1058
|
+
}
|
|
812
1059
|
case 'list': {
|
|
813
1060
|
const runs = mgr.listRuns();
|
|
814
1061
|
if (jsonMode) {
|
|
@@ -871,7 +1118,7 @@ async function cmdRun(rest, rawArgs, jsonMode, engine) {
|
|
|
871
1118
|
break;
|
|
872
1119
|
}
|
|
873
1120
|
default: {
|
|
874
|
-
const msg = `Unknown run subcommand: ${runSub || '(none)'}\nUsage: workflow run <start|state|node|auto|reset|list> [args...]`;
|
|
1121
|
+
const msg = `Unknown run subcommand: ${runSub || '(none)'}\nUsage: workflow run <start|state|node|auto|reset|retry|list> [args...]`;
|
|
875
1122
|
if (jsonMode) {
|
|
876
1123
|
console.log(JSON.stringify({ success: false, error: msg }));
|
|
877
1124
|
}
|
|
@@ -884,7 +1131,7 @@ async function cmdRun(rest, rawArgs, jsonMode, engine) {
|
|
|
884
1131
|
}
|
|
885
1132
|
function reportAutoResult(result, jsonMode) {
|
|
886
1133
|
const { run, outcome, reason, executed } = result;
|
|
887
|
-
const status = run.status
|
|
1134
|
+
const status = run.status;
|
|
888
1135
|
if (jsonMode) {
|
|
889
1136
|
console.log(JSON.stringify({
|
|
890
1137
|
success: outcome !== 'failed',
|
|
@@ -1024,6 +1271,10 @@ async function main(inputArgs) {
|
|
|
1024
1271
|
await cmdRun(rest, args, jsonMode, engine);
|
|
1025
1272
|
break;
|
|
1026
1273
|
}
|
|
1274
|
+
case 'repair': {
|
|
1275
|
+
cmdRepair(rest, jsonMode, !!options.apply, !!options['dry-run']);
|
|
1276
|
+
break;
|
|
1277
|
+
}
|
|
1027
1278
|
case 'usage':
|
|
1028
1279
|
showDetailedUsage();
|
|
1029
1280
|
break;
|