@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.
Files changed (46) hide show
  1. package/SKILL.md +40 -4
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +265 -14
  4. package/dist/cli.js.map +1 -1
  5. package/dist/src/card-state.d.ts +20 -0
  6. package/dist/src/card-state.d.ts.map +1 -0
  7. package/dist/src/card-state.js +121 -0
  8. package/dist/src/card-state.js.map +1 -0
  9. package/dist/src/engine.d.ts +9 -2
  10. package/dist/src/engine.d.ts.map +1 -1
  11. package/dist/src/engine.js +68 -2
  12. package/dist/src/engine.js.map +1 -1
  13. package/dist/src/failure.d.ts +41 -0
  14. package/dist/src/failure.d.ts.map +1 -0
  15. package/dist/src/failure.js +122 -0
  16. package/dist/src/failure.js.map +1 -0
  17. package/dist/src/mermaid.d.ts.map +1 -1
  18. package/dist/src/mermaid.js +7 -4
  19. package/dist/src/mermaid.js.map +1 -1
  20. package/dist/src/parent-task-sync.d.ts +120 -0
  21. package/dist/src/parent-task-sync.d.ts.map +1 -0
  22. package/dist/src/parent-task-sync.js +307 -0
  23. package/dist/src/parent-task-sync.js.map +1 -0
  24. package/dist/src/retry-policy.d.ts +38 -0
  25. package/dist/src/retry-policy.d.ts.map +1 -0
  26. package/dist/src/retry-policy.js +140 -0
  27. package/dist/src/retry-policy.js.map +1 -0
  28. package/dist/src/run-manager.d.ts +35 -2
  29. package/dist/src/run-manager.d.ts.map +1 -1
  30. package/dist/src/run-manager.js +1189 -235
  31. package/dist/src/run-manager.js.map +1 -1
  32. package/dist/src/types.d.ts +39 -4
  33. package/dist/src/types.d.ts.map +1 -1
  34. package/dist/src/types.js +2 -0
  35. package/dist/src/types.js.map +1 -1
  36. package/dist/src/utils/output-format.d.ts +12 -0
  37. package/dist/src/utils/output-format.d.ts.map +1 -1
  38. package/dist/src/utils/output-format.js +103 -5
  39. package/dist/src/utils/output-format.js.map +1 -1
  40. package/dist/src/workflow-persistence.d.ts +48 -0
  41. package/dist/src/workflow-persistence.d.ts.map +1 -0
  42. package/dist/src/workflow-persistence.js +224 -0
  43. package/dist/src/workflow-persistence.js.map +1 -0
  44. package/dist/workflow-schema.json +34 -5
  45. package/package.json +14 -4
  46. 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: `npx crewx`) and executes the equivalent of:
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 미리승인한다` | Starts `rc-publish` with `state.input = "미리승인한다"` |
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 미리승인한다` stores the text in `state.input`; it does not approve publishing. Approval still requires the signed ChatCard action button.
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`) | `npx crewx` |
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":";AAgBA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE9C,OAAO,KAAK,EAAE,cAAc,EAAgB,YAAY,EAA+B,MAAM,aAAa,CAAC;AA6F3G,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;AAmxBD,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,CAsD9D"}
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(['help', 'json', 'mermaid', 'version', 'no-exec', 'auto']);
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(`\uD83D\uDCCB \uC6CC\uD06C\uD50C\uB85C \uBAA9\uB85D`);
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 mgr = new run_manager_1.RunManager();
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 (run.status) {
730
- console.log(` Status: ${run.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 ?? (outcome === 'paused' ? 'paused' : 'running');
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;